首页 > 其他分享 >UNR #8 部分题题解

UNR #8 部分题题解

时间:2024-07-17 11:10:25浏览次数:5  
标签:UNR le int 题解 cnt cdot maxn res 部分

由于博主水平有限,目前只有 \(A,B,D\) 题题解。

A.最酷的排列

题目描述

\(T\) 组数据,给定长为 \(n\) 的序列 \(a\) 和下标集合 \(S\) ,你可以对序列进行任意次操作,每次选择一个区间并将其中元素升序排序。

求 \(\sum\limits_{i\in S}a_i\) 的最小可能值。

数据范围

  • \(1\le n,\sum n\le 2\cdot 10^5,1\le a_i\le 10^9\)。

时间限制 \(\texttt{1s}\) ,空间限制 \(\texttt{1024MB}\)。

分析

注意到对一个区间排序等价于进行若干次冒泡,因此只需选择长为 \(2\) 的区间。

根据 \(\texttt{Subtask3}\) 的提示,从后往前扫描,最终某个位置的数不可能比已经扫描过的位置更小,即 \(a'_i\ge \min\limits_{i\le j\le n}a_j\)。

小根堆维护已经扫描且尚未使用过的数,每次碰到集合 \(S\) 中的下标,弹出其中的最小值,这是答案的下界。

再来证明上述方法得到的答案可以取到,对于 \(S\) 中的两个相邻下标 \(i,j\) ,如果在 \(i\) 处弹出的数(记为 \(b_i\))比 \(j\) 处(记为 \(b_j\))大,每次交换两个数可以让 \(a'_i=b_j,a'_j=b_i\) ,即完成 \(S\) 内部的冒泡。

时间复杂度 \(\mathcal O(\sum n\log n)\)。

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
int n,t;
long long res;
int a[maxn];
char s[maxn];
int main()
{
    for(scanf("%d",&t);t--;)
    {
        scanf("%d",&n),res=0;
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        scanf("%s",s+1);
        priority_queue<int,vector<int>,greater<int>> q;
        for(int i=n;i>=1;i--)
        {
            q.push(a[i]);
            if(s[i]=='1') res+=q.top(),q.pop();
        }
        printf("%lld\n",res);
    }
    return 0;
}

B.里外一致

题目描述

给定长为 \(n\) 的序列 \(a\) , \(q\) 次询问,每次给定 \(l,r\) ,求有多少 \(U=\{l,\cdots,r\}\) 的子集 \(S\) 满足 \(|\{a_i|i\in S\}|=|\{a_i|i\in U/S\}|\) ,对 \(2^{64}\) 取模。

数据范围

\(1\le n\le 3\cdot 10^5,1\le q\le 10^5,1\le a_i\le n\)。

时间限制 \(\texttt{2s}\) ,空间限制 \(\texttt{1024MB}\) 。

分析

先考虑 \(q=1\) 怎么做,假设不同数字在序列 \(a\) 中的出现次数分别为 \(cnt_1,\cdots,cnt_m\)。

\(f_{i,j}\) 表示考虑前 \(i\) 个 \(cnt\) ,\(|\{a_i|i\in S\}|-|\{a_i|i\in U/S\}|=j\) 的方案数。

容易写出转移方程:

\[f_{i,j}=f_{i-1,j-1}+f_{i-1,j}\cdot (2^{cnt_i}-2)+f_{i-1,j+1} \]

用生成函数的视角理解,答案为:

\[[x^0]\prod_{i=1}^m(x+2^{cnt_i}-2+\frac 1x) \]

本题非常 \(\texttt{tricky}\) 的一点是,由于答案对 \(2^{64}\) 取模,所以如果与\(2^{cnt_i}\)有关的项取太多对答案没有任何影响。

将 \(x-2+\frac 1x\) 和 \(2^{cnt_i}\) 拆开来看,注意到:

\[V_k=[x^0](x-2+\frac 1x)^k=[x^0](\sqrt x-\sqrt\frac 1x)^{2k}=(-1)^k\binom{2k}k \]

\(g_{i,j}\) 表示考虑前 \(i\) 个 \(cnt\) ,有 \(j\) 项没选 \(2^{cnt_i}\) 的系数之和,那么答案为 \(\sum g_{m,j}V_{m-j}\) 。

转移方程 \(g_{i,j}=g_{i-1,j}+2^{cnt_i}\cdot g_{i-1,j-1}\) ,注意到第二维上限为 \(63\) ,否则对答案没有贡献。

预处理 \(V_k\) 即可获得一个 \(\mathcal O(64nq)\) 的做法,link。具体的,预处理阶乘中 \(2\) 的幂次,奇数部分的逆元可以用欧拉定理求出。

注意到 \(g\) 的转移本质上是背包,而背包问题可以支持撤回,用莫队维护 \(cnt\) 和 \(g\) 数组,可以做到 \(\mathcal O(64n\sqrt q)\)。

由于 \(q\) 很小,并且 \(cnt\ge 64\) 时没有贡献,我们希望将所有相同的 \(cnt\) 放在一起转移,这样可以把 \(64\) 的常数从 \(n\sqrt q\) 上拿下来。

记 \(cnt_i=x\) 出现了 \(c_x\) 次,将 \(g\) 的定义修改为考虑所有 \(cnt\le i\) 的情况,转移方程为 \(g_{i,k}\gets\binom{c_x}{k-j}\cdot 2^{i\cdot (k-j)}\cdot g_{i-1,j}\) 。

时间复杂度 \(\mathcal O(n\sqrt q+64\cdot 273\cdot q)\) ,由于卡不满,可以通过。

这里 \(273=\sum_{i=1}^{63}\lfloor\frac{63}i\rfloor\),而且过的比较惊险,如果被叉了或者能证明出更优复杂度可以叫我

注意非常重要的一点,下面代码中第 \(62\) 行 \(x\) 一定要倒序枚举,这样可以大幅减少转移次数。

#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int B=1000,maxn=6e5+5;
int l,n,q,r;
int a[maxn],c[65],pw[maxn],bel[maxn],cnt[maxn];
ull fac[maxn],inv[maxn];
ull g[64],v[maxn],res[maxn];
struct node
{
    int l,r,id;
    bool operator<(const node &a)
    {
        return bel[l]!=bel[a.l]?l<a.l:r<a.r;
    }
}f[maxn];
ull qpow(ull a,ull k)
{
    ull res=1;
    for(;k;a=a*a,k>>=1) if(k&1) res=res*a;
    return res;
}
ull get_c(int n,int m)
{
    int x=pw[n]-pw[m]-pw[n-m];
    return x<64?(fac[n]*inv[m]*inv[n-m])<<x:0;
}
void init(int n)
{
    fac[0]=1;
    for(int i=1,j=0;i<=n;i++)
    {
        for(j=i,pw[i]=pw[i-1];!(j&1);j>>=1,pw[i]++) ; 
        fac[i]=fac[i-1]*j;
    }
    inv[n]=qpow(fac[n],(1ll<<63)-1);
    for(int i=n,j=0;i>=1;i--)
    {
        for(j=i;!(j&1);j>>=1) ;
        inv[i-1]=inv[i]*j;
    }
    for(int i=0;i<=(n>>1);i++) v[i]=(i&1?-1:1)*get_c(i<<1,i);
}
void add(int x,int v)
{
    c[min(cnt[x],64)]--,cnt[x]+=v,c[min(cnt[x],64)]++;
}
int main()
{
    scanf("%d%d",&n,&q),init(n<<1);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),bel[i]=(i-1)/B+1;
    for(int i=1;i<=q;i++) scanf("%d%d",&f[i].l,&f[i].r),f[i].id=i;
    sort(f+1,f+q+1);
    for(int i=1,l=1,r=0;i<=q;i++)
    {
        while(l>f[i].l) add(a[--l],1);
        while(r<f[i].r) add(a[++r],1);
        while(l<f[i].l) add(a[l++],-1);
        while(r>f[i].r) add(a[r--],-1);
        int m=c[64];
        for(int j=0;j<64;j++) g[j]=!j;
        for(int x=63;x>=1;x--)
        {
            m+=c[x];
            for(int j=63;j>=0;j--)
                if(g[j]) for(int k=min({c[x],63-j,63/x});k>=1;k--)
                    g[j+k]+=(get_c(c[x],k)*g[j])<<(k*x);
        }
        for(int j=0;j<=min(m,63);j++) res[f[i].id]+=g[j]*v[m-j];
    }
    for(int i=1;i<=q;i++) printf("%llu\n",res[i]);
    return 0;
}

D.兵棋

题目描述

对于长为 \(n\) 的 \(01\) 串 \(s\) ,定义一次操作为,删除所有满足 \(i\ge 2,s_i\neq s_{i-1}\) 的位置,并将剩余位置拼接起来。

给定长为 \(n\) 的由 \(0,1,?\) 组成的字符串 \(s\),对于所有将 \(?\) 替换成 \(0\) 或 \(1\) 得到的字符串,求其经过 \(k\) 次操作后序列长度之和,对 \(998244353\) 取模。

数据范围

\(1\le n\le 10^5,1\le k\le 200\)。

时间限制 \(\texttt{2s}\) ,空间限制 \(\texttt{1024MB}\) 。

分析

对于一个已知的 \(01\) 串 \(s\) ,我们需要比模拟更快的方法求出其经过 \(k\) 次操作后的长度。

先考虑 \(k=1\) 的情况,容易发现这是将除第一段以外的每个连续段长度减一,但是可能会引起连续段合并。

这启发我们用 +1/-1 来代替 0/1 ,具体地,有以下结论:

记上一个未被删除的字符为 \(x\) ,将 \(x\) 赋值为 \(-1\) , \(1-x\) 赋值为 \(+1\) ,如果权值到达 \(-1\),那么下一个未被删除的字符为 \(x\);如果权值到达 \(k+1\) ,那么下一个未被删除的字符为 \(1-x\) ,最后将权值清零。

接下来可以动态规划。

记 \(f_{i,x,j}\) 为考虑前 \(i\) 个字符,上一个未被删除的字符为 \(x\) ,当前权值为 \(j\) 的序列个数与长度之和,用二元组存储。

对于普通转移,我们只需要让序列个数和权值之和分别相加即可;对于涉及到新字符的转移,原先的每个序列都会产生 \(1\) 的贡献,将前者的序列个数加到后者的长度之和中即可。

时间复杂度 \(\mathcal O(nk)\)。

#include<bits/stdc++.h>
#define fi first
#define se second
#define mp make_pair
#define pii pair<int,int>
using namespace std;
const int maxn=1e5+5,mod=998244353;
int k,n,res;
char s[maxn];
pii f[2][2][205];
void add(int &a,int b)
{
    if((a+=b)>=mod) a-=mod;
}
void tran(pii &a,pii &b,bool op)
{
    add(b.fi,a.fi),add(b.se,a.se);
    if(op) add(b.se,a.fi);
}
int main()
{
    scanf("%d%d%s",&n,&k,s+1);
    if(s[1]!='1') f[1][0][0]=mp(1,1);
    if(s[1]!='0') f[1][1][0]=mp(1,1);
    for(int i=2;i<=n;i++)
    {
        memset(f[i&1],0,sizeof(f[i&1]));
        for(int x=0;x<=1;x++)
            for(int j=0;j<=k;j++)
            {
                pii &v=f[i-1&1][x][j];
                if(!v.fi&&!v.se) continue;
                if(s[i]-'0'!=(x^1)) j>0?tran(v,f[i&1][x][j-1],0):tran(v,f[i&1][x][0],1);
                if(s[i]-'0'!=x) j<k?tran(v,f[i&1][x][j+1],0):tran(v,f[i&1][x^1][0],1);
            }
    }
    for(int x=0;x<=1;x++) for(int j=0;j<=k;j++) add(res,f[n&1][x][j].se);
    printf("%d\n",res);
    return 0;
}

标签:UNR,le,int,题解,cnt,cdot,maxn,res,部分
From: https://www.cnblogs.com/peiwenjun/p/18306875

相关文章

  • 启动应用程序出现mfc80u.dll找不到问题解决
    其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题,如果是新手第一时间会认为是软件或游戏出错了,其实并不是这样,其主要原因就是你电脑系统的该dll文件丢失了或没有安装一些系统软件平台所需要的动态链接库,这时你可以下载这个mfc80u.dll文件(挑选合适的版本文件)把它放......
  • 启动应用程序出现mfc90u.dll找不到问题解决
    其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题,如果是新手第一时间会认为是软件或游戏出错了,其实并不是这样,其主要原因就是你电脑系统的该dll文件丢失了或没有安装一些系统软件平台所需要的动态链接库,这时你可以下载这个mfc90u.dll文件(挑选合适的版本文件)把它放......
  • 2024牛客暑期多校训练营1 I.Mirror Maze(题解)
    题意给一个\(n\timesm\)的二维char数组,由4种镜子组成,'\','/','-','|',镜面反射规则就是根据光线的方向和镜面的角度符合直觉的反射,然后有多组询问\(q\leq10^6\),每次给定起始位置和光线方向,求该光会经过多少面不同的镜子的反射。思路首先根据数据范围,发现肯定需要预处......
  • ARC_069 D - Menagerie 题解
    atcoder一道很有意思的模拟题啊。思路很重要。首先,我们只要知道连续两只动物的身份,就可以根据\(s\)推出所有动物的身份。不妨假设我们知道第一只和第二只动物的身份,一共有几种情况呢?用\(1\)代表羊,\(0\)代表狼。那么,共有\(2^2=4\)种情况,分别为:00011011然后我......
  • 题解|2024暑期牛客多校01
    【原文链接】太菜了就先挂4题,其他的写出来了再回来补QwQA.ABitCommon组合数学题目大意题目概括:给定两个整数nnn和m......
  • [题解]POJ2074 Line of Sight
    POJ2074LineofSight题意简述多测。给定若干条线段,全部与\(x\)轴平行。其中有\(2\)条线段表示房子和人行道(虽然翻译不是人行道就是了),保证房子在人行道上面。其他线段表示障碍物(不保证在房子和人行道之间)。请找出人行道上最长的连续部分,使得在这中间可以完整地看到房子的全......
  • Intel Management Engine WMI Provider 2408.5.4.0 20240221 驱动程序 Intel管理引擎
    驱动程序"IntelManagementEngineWMIProvider2408.5.4.0"是指Intel管理引擎的一部分,它通过Windows管理仪表(WMI)提供对管理引擎功能的访问和管理。这些驱动程序通常用于管理和配置Intel管理引擎的功能,包括安全功能、远程访问以及系统监控等。如果您需要安装或更新这个驱......
  • CF825F String Compression题解
    思路容易想到是个动态规划。首先设\(f_i\)表示字符串前\(i\)个字符所组成的字符串的答案。状态定义好了,接下来就是考虑如何转移了。因为由\(f_i\)可以得到所有\(f_j\),其中\(i+j\lelen\),转移方程为\(f_i=f_j+x\),其中\(x\)为字符串\(i+1\)至\(j\)的最优压缩。接......
  • [ABC347E] Set Add Query题解
    思路通过读题发现,每个数变化当且仅当这个数在集合内。所以不妨设它被添加进来的时间点为\(L_i\),它被删除的时间点为\(R_i\),所以它被增加的数量就是这段时间内集合数量之和。所以用一个变量\(cnt\)模拟当前集合内有多少个数,前缀和维护即可。具体实现参见代码。代码#include<......
  • CF1898D Absolute Beauty 题解
    思路容易发现,如果\(a_i>b_i\)则将\(a_i\)和\(b_i\)交换。在数轴上标出要交换的四个数的位置若线段\(a_ib_i\)和线段\(a_jb_j\)互不相交,此时交换比两条线段处于其他位置时更优。具体证明这里就不再赘述,其他题解讲的已经很清楚了。所以只需交换最大的\(a_i\)和最小......