GJOI 10.9 题解
1.占卜
题意
2≤n≤10182\le n\le 10^{18}2≤n≤1018,∑n≤1018\sum n\le 10^{18}∑n≤1018,1≤m≤3×1051\le m\le 3\times 10^51≤m≤3×105,∑m≤3×105\sum m\le 3\times 10^5∑m≤3×105。
思路
赛时一车人讨论这是异或还是且,这难道不是且吗?
首先当 xix_ixi 出现 444 次即以上,iii 肯定会被满足,只要 00
、01
、10
、11
都试一次就能满足。
考虑出现四次的本质:
4
2 2 3 3
比如给两个 222 分配 (a,b)=(1,0)(a,b)=(1,0)(a,b)=(1,0) 和 (0,0)(0,0)(0,0),如果 s3=0s_3=0s3=0 那肯定有一对是猜对了的,因为把 s2=0/1s_2=0/1s2=0/1 都试了一遍。
如果不幸的,s3=1s_3=1s3=1,前面试了两次都错了,那就直接给两个 333 分配 (1,0)(1,0)(1,0) 和 (1,1)(1,1)(1,1) 了,这两对中也肯定有一对是猜对了的,因为把 s4=0/1s_4=0/1s4=0/1 都试了一遍。
即,xxx 出现两次,就能确定 x+1x+1x+1 具体是哪一个数了,只要后面连续,并且还有一个出现 222 次即以上的 x′x'x′ 来“接应”,就肯定可以构造!尝试验证这个结论:
7
2 2 3 4 5 6 6
同样先给 222 分配 (0,0)(0,0)(0,0) 和 (1,0)(1,0)(1,0),最劣时 s3=1s_3=1s3=1。但是 333 只有 111 个,就没法确定 444 了,不过可以确定给 333 分配 (1,?)(1,?)(1,?)。
考虑先看 666。给 666 分配 (0,0)(0,0)(0,0) 和 (0,1)(0,1)(0,1),最劣时 s6=0s_6=0s6=0,那么在 555 时肯定分配 (?,0)(?,0)(?,0)。
现在不确定 4,54,54,5。假如 333 那里是 (1,0)(1,0)(1,0),最劣时 s4=1s_4=1s4=1;给 444 分配 (0,0)(0,0)(0,0),最劣时 s5=1s_5=1s5=1,因为已经确定 555 要被分配 (?,0)(?,0)(?,0),所以直接给 555 分配 (1,0)(1,0)(1,0),满足条件结束。
这个过程看似是无穷无尽的,但是因为后面有某个位被确定了(即出现次数 ≥2\ge 2≥2,形成“接应”),于是这个结论被简易验证了。
那就拿一个桶维护出现次数,验证是否出现连续 x,x+1,x+2,...,x′x,x+1,x+2,...,x'x,x+1,x+2,...,x′,且 xxx 和 x′x'x′ 出现次数均为 222 次即以上即可。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=3e5+9;
ll Q,n,m;
ll a[N];
ll b[N],val[N];
unordered_map<ll,ll>mp;
int main()
{freopen("divination.in","r",stdin);freopen("divination.out","w",stdout);scanf("%lld",&Q);while(Q--){scanf("%lld%lld",&n,&m);for(int i=1;i<=m;i++)scanf("%lld",&a[i]);sort(a+1,a+m+1);bool flag=0;ll tot=1,cnt=0;for(int i=1;i<=m;i++){if(a[i]==a[i+1])tot++;else {if(tot>=4)flag=1;b[++cnt]=tot;val[cnt]=a[i];tot=1;}}if(flag){puts("Yes");continue;}for(int l=1;l<=cnt;){ll r=l;if(b[l]>=2){while(r<=cnt&&val[r]+1==val[r+1]){r++;//b[r]>0,可以接续 if(b[r]>=2){flag=1;break;}}}if(flag)break;l=r+1;}if(flag)puts("Yes");else puts("No");}return 0;
}
2.CF2006C Eri and Expanded Sets
题意
1≤n≤2×1051\le n\le 2\times 10^51≤n≤2×105,∑n≤2×105\sum n\le 2\times 10^5∑n≤2×105,ai∈[1,1018]a_i\in[1,10^{18}]ai∈[1,1018]。
CF2006C 数据:1≤n≤4×1051\le n\le 4\times 10^51≤n≤4×105,a∈[1,109]a\in[1,10^9]a∈[1,109]。
两个数据范围做法本质相同。
思路
钦定 ci=ai+1−ai,m=n−1c_i=a_{i+1}-a_i,m=n-1ci=ai+1−ai,m=n−1,记 g=gcdi=lr−1cig=\displaystyle\gcd_{i=l}^{r-1} c_ig=i=lgcdr−1ci,定义 n2(x)n2(x)n2(x) 操作表示,对 xxx 一直除以 222 直到不能整除。
整道题围绕着一个结论:
- 若 ggg 是 222 的幂或者 111,区间 [l,r][l,r][l,r] 的数就可以被削成 al,al+1,...,ara_l,a_l+1,...,a_ral,al+1,...,ar。
因为一直 x+y2=x+y−x2\frac{x+y}{2}=x+\frac{y-x}{2}2x+y=x+2y−x,可以把 ci=y−xc_i=y-xci=y−x 的 222 的因子消耗掉。而如果出现其它质因子就肯定消不掉,导致最后处在公差为 n2(g)n2(g)n2(g) 的等差数列。
题目问区间个数,考虑固定一个左端点,计算有多少右端点 rrr。我们只需要找到最小的 r0≥lr_0\ge lr0≥l,使得 n2(g)=n2(gcdi=lr0−1ci)=1n2(g)=n2\left(\displaystyle\gcd_{i=l}^{r_0-1} c_i\right)=1n2(g)=n2(i=lgcdr0−1ci)=1,因为 r0r_0r0 往后再和 ggg 取 gcd\gcdgcd 必然不大于 ggg,即依然是 111。同时显然的,gcd\gcdgcd 从 l∼nl\sim nl∼n 是单调递减的,于是 r0r_0r0 的位置是可二分的。
bool check(ll nl,ll mid)//区间gcd(已经进行剥离2因子操作)
{return query(nl,mid)==1;
}
...
for(int i=1;i<=m;i++)
{ll l=i,r=m,j=m;bool flag=0;//是否有合法的右界出现while(l<=r){ll mid=(l+r)>>1;if(check(i,mid))j=mid,r=mid-1,flag=1;//相等不能当做存在多个,只能算作1个 else l=mid+1;}if(flag)ans+=n-j;//j=r0-1
}
那么我们需要快速获取区间 gcd\gcdgcd,因为可以区间合并,于是可以使用线段树或者 ST 表,又因为不带修于是 ST 表了。注意 ST 表的下标是间隔编号,不是元素编号。左端点 lll 是 ala_lal 右边的间隔 clc_lcl,上面二分得到 jjj 的其实是 r0−1r_0-1r0−1。
ll no2(ll x)//可以直接x/lowbit(x)
{if(x==0)return 0;while(x%2==0)x/=2;return x;
}
ll fgcd[N][M],lg2[N];
void init()
{lg2[1]=0;lg2[2]=1;for(int i=3;i<N;i++)lg2[i]=lg2[i/2]+1;
}
void getGCD()
{for(int i=1;i<=m;i++)fgcd[i][0]=no2(c[i]);//提前剥离2因子不影响单调性,依然可以合并 for(int j=1;j<=lg2[m];j++)for(int i=1;i+(1<<j)-1<=m;i++)fgcd[i][j]=__gcd(fgcd[i][j-1],fgcd[i+(1<<(j-1))][j-1]);
}
ll query(ll l,ll r)
{ll s=lg2[r-l+1];return __gcd(fgcd[l][s],fgcd[r-(1<<s)+1][s]);
}
但有个点要注意,如果遇到相邻的数,会出现 ci=0c_i=0ci=0,众所周知 C++ 的 __gcd(x,y)
遇到其中一个数是 x=0x=0x=0 是并不会返回 000 而是返回 yyy,所以遇到 lll 开始一堆 ci=0c_i=0ci=0、后面又有数的就不单调了。但是二分 check
的条件是区间 gcd=1\gcd=1gcd=1,一串相同的数出现(出现 gcd=0\gcd=0gcd=0 只会缩进左界)不影响答案。于是相同的数找到连续若干个 ci=0c_i=0ci=0 再算进贡献。
二分答案那里 O(nlogn)O(n\log n)O(nlogn),预处理 ST 表因为 gcd\gcdgcd 也带个 log\loglog,于是总体是 O(nlog2n)O(n\log^2 n)O(nlog2n) 的。
别忘了只加一个数亦可以。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=4e5+9,M=20,inf=3e14;
ll Q,n,m;
ll a[N],c[N];
ll no2(ll x)
{if(x==0)return 0;while(x%2==0)x/=2;return x;
}
ll cal(ll len)
{return len*(len+1)/2;
}
ll fgcd[N][M],lg2[N];
void init()
{lg2[1]=0;lg2[2]=1;for(int i=3;i<N;i++)lg2[i]=lg2[i/2]+1;
}
void getGCD()
{for(int i=1;i<=m;i++)fgcd[i][0]=no2(c[i]);//提前剥离2因子不影响单调性,依然可以合并 for(int j=1;j<=lg2[m];j++)for(int i=1;i+(1<<j)-1<=m;i++)fgcd[i][j]=__gcd(fgcd[i][j-1],fgcd[i+(1<<(j-1))][j-1]);
}
ll query(ll l,ll r)
{ll s=lg2[r-l+1];return __gcd(fgcd[l][s],fgcd[r-(1<<s)+1][s]);
}
bool check(ll nl,ll mid)
{return query(nl,mid)==1;
}
int main()
{freopen("set.in","r",stdin);freopen("set.out","w",stdout);init();scanf("%lld",&Q);while(Q--){scanf("%lld",&n);for(int i=1;i<=n;i++){scanf("%lld",&a[i]);if(i>1)c[i-1]=abs(a[i]-a[i-1]);}m=n-1;getGCD();ll ans=0,l0=0;c[n]=-1;for(int i=1;i<=n;i++){if(c[i]==0)l0++;else {ans+=cal(l0);l0=0;}}for(int i=1;i<=m;i++){ll l=i,r=m,j=m;bool flag=0;while(l<=r){ll mid=(l+r)>>1;if(check(i,mid))j=mid,r=mid-1,flag=1;//相等不能当做存在多个,只能算作1个 else l=mid+1;}if(flag)ans+=n-j;}printf("%lld\n",ans+n);//记得加上单个}return 0;
}
3.AT_arc178_d Delete Range Mex
题意
给定一个正整数 NNN 和一个长度为 MMM 的非负整数序列 AAA。AAA 的所有元素都 ∈[0,N)\in[0,N)∈[0,N),并且互不相同。
请计算有多少个排列 BBB 满足以下条件,并输出答案对 998244353998244353998244353 取模后的结果。将排列 BBB 通过任意次数以下操作变为 AAA:
- 选择满足 1≤l≤r≤∣B∣1\leq l\leq r\leq |B|1≤l≤r≤∣B∣ 的 l,rl,rl,r,如果 mex({Bl,Bl+1,…,Br})\mathrm{mex}(\{B_{l},B_{l+1},\dots,B_{r}\})mex({Bl,Bl+1,…,Br}) 在 BBB 中出现,则将其从 BBB 中删除。
- BBB 的所有元素都 ∈[0,N)\in[0,N)∈[0,N)。
mex(X)\mathrm{mex}(X)mex(X) 的定义如下:对于由非负整数组成的有限集合 XXX,mex(X)\mathrm{mex}(X)mex(X) 是满足 x∉Xx\notin Xx∈/X 的最小非负整数 xxx。
1≤M≤N≤5001\le M\le N\le 5001≤M≤N≤500,Ai∈[0,N)A_i\in[0,N)Ai∈[0,N)。
南海云课堂题意:Ai∈[1,n]A_i\in[1,n]Ai∈[1,n],将 AiA_iAi 全部 −1-1−1 就是相同题意。
思路
思路和实现参考这篇博客,有点贺了呜呜呜。
在区间 [l,r][l,r][l,r] 成功删掉 x=mexl,rx=\text{mex}_{l,r}x=mexl,r,需要满足,[l,r][l,r][l,r] 包含 0∼x−10\sim x-10∼x−1 的数且不包含 xxx。
原排列 BBB 删掉一些数得到 AAA,令删除数形成的有序序列为 DDD。删除的数字按顺序来看必须是从大到小的,否则 xxx 不能成为 mex\text{mex}mex。
同时在原序列中,比 xxx 小的数都在某个区间 [l,r][l,r][l,r] 中,于是区间 [l,r][l,r][l,r] 要么在 xxx 左边要么右边。
考虑倒着插回去,从小到大插入 AAA 得到若干合法的 BBB。∣A∣=M|A|=M∣A∣=M 于是有 M+1M+1M+1 个间隔可以插数,一个间隔可以插若干数。设 fx,l,rf_{x,l,r}fx,l,r 表示插入了 0∼x0\sim x0∼x,枚举占用了 [l,r][l,r][l,r] 区间((l,r)(l,r)(l,r) 空隙),剩余 [1,l]∪[r,n][1,l]\cup[r,n][1,l]∪[r,n] 空隙可以插入数的方案数。
若 x∈Ax\in Ax∈A,记 xxx 的出现位置为 posxpos_xposx,出现在 posx∼posx+1pos_x\sim pos_x+1posx∼posx+1 区间当中,以后要填的 x′>xx'>xx′>x 就不能插在同一个地方了。于是:
fx,min(posx,l),max(posx+1,r)←fx−1,l,rf_{x,\min(pos_x,l),\max(pos_x+1,r)}\leftarrow f_{x-1,l,r}fx,min(posx,l),max(posx+1,r)←fx−1,l,r
xxx 没有在 AAA 中出现过,就插在比它小的数的左侧或者右侧,即分别枚举左右端点 fx,l,r←∑i=lrfx−1,i,rf_{x,l,r}\leftarrow \sum_{i=l}^rf_{x-1,i,r}fx,l,r←∑i=lrfx−1,i,r 和 fx,l,r←∑i=lrfx−1,l,if_{x,l,r}\leftarrow \sum_{i=l}^rf_{x-1,l,i}fx,l,r←∑i=lrfx−1,l,i。这个可以前缀和优化,枚举左右端点的时候就能做完。
要特别讨论 000,若 000 存在当然 f0,posx,posx+1=1f_{0,pos_x,pos_x+1}=1f0,posx,posx+1=1;否则 000 不存在,随便选一个区间,都能把 xxx 删掉,此时 f0,i,i=1f_{0,i,i}=1f0,i,i=1。这就是初始化。
最后答案就是 fn−1,1,m+1f_{n-1,1,m+1}fn−1,1,m+1。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=502,mod=998244353;
ll n,m;
ll a[N];
ll pos[N];
ll f[N][N][N];
int main()
{
// freopen("heritage.in","r",stdin);
// freopen("heritage.out","w",stdout);scanf("%lld%lld",&n,&m);for(int i=1;i<=m;i++){scanf("%lld",&a[i]);pos[a[i]]=i;}if(!pos[0]){for(int i=1;i<=m+1;i++)f[i][i][0]=1;}else f[pos[0]][pos[0]+1][0]=1;for(int x=1;x<n;x++){if(!pos[x]){for(int j=1;j<=m+1;j++){ll s=0;for(int i=j;i>=1;i--){s=(s+f[i][j][x-1])%mod;f[i][j][x]=(f[i][j][x]+s)%mod; }}for(int i=1;i<=m+1;i++){ll s=0;for(int j=i;j<=m+1;j++){s=(s+f[i][j][x-1])%mod;f[i][j][x]=(f[i][j][x]+s)%mod;}}}else {for(ll i=1;i<=m+1;i++){for(ll j=i;j<=m+1;j++){ll l=min(pos[x],i),r=max(pos[x]+1,j);f[l][r][x]=(f[l][r][x]+f[i][j][x-1])%mod;}}}}printf("%lld",f[1][m+1][n-1]);return 0;
}
南海云课堂题面敬请自行修改。