cbc010&crc001 总结
cbc010
本次比赛共 \(45\) 人报名,\(18\) 人提交,\(17\) 人有分,最高分为 @luogu___official 的 \(1996\) 分(六题)。恭喜 @Loser_syx 第一个 AC 六题,获得 \(5\) 元奖励。没有人获得 \(10\) 元奖励,也没有人获得 G 题结尾的神秘奖励。
至于为什么最高分不是 \(2050\) 分的 @Priestess_SLG,我们发现她的所有代码都和 @luogu___official 是一样的,所以鉴定为多号提交,该号的成绩不计入排名。
cbc 和 crc 的所有题解点赞均已送出。
A
简单的不能再简单了。
B
取反所有 \(0\) 连续段一定不劣。
C
第 \(n\) 位若产生 \(1\) 的贡献,那么 \(2\sim n-1\) 位随便放即可,第一位必须放 \(1\),共 \(2^{n-2}\) 的贡献。
第 \(n-1\) 位若产生 \(1\) 的贡献,那么 \(2\sim n-2\) 位随便放即可,第一位必须放 \(1\),第 \(n\) 位必须为 \(0\),共 \(2^{n-3}\) 的贡献。
同理可得,答案即为 \(2^0+2^1+\cdots +2^{n-2}=2^{n-1}-1\)。
D
设 \(dp_{i,j}\) 表示以 \(i\) 为结尾,最后一段连续段的积为 \(j\) 的答案,则有:
\(dp_{i,j}=\max(dp_{k,l}+j,k<i,\prod\limits_{x=k}^{i-1}a_x=j)\)
注意到第二维只有 \(3\) 种可能,分别记录三个出现过的最大值即可做到 \(O(n)\) 转移。
#include<bits/stdc++.h>
#define N 100005
using namespace std;
int n,a[N],dp[N];
map<int,int>qwq;
int main(){
cin>>n;
for(int i=1;i<=n;++i)cin>>a[i],dp[i]=-1e9;qwq[1]=qwq[-1]=-1e9;
for(int i=1;i<=n;++i){
if(a[i]==0)qwq[0]=max({qwq[0],qwq[1],qwq[-1]}),qwq[1]=qwq[-1]=-1e9;
else if(a[i]==-1)swap(qwq[-1],qwq[1]);
qwq[a[i]]=max(qwq[a[i]],dp[i-1]);
dp[i]=max({qwq[0],qwq[-1]-1,qwq[1]+1});
}
cout<<dp[n];
return 0;
}
我们甚至以为我们的数据错了,因为前两天的时候这题的通过情况和难度严重不符。
E
建议改为:【模板】基环树最短路。
考虑找环之后断掉一条边,那么答案即为 树上最短路 和 强制走这条边的最短路 的最小值。所以直接 LCA 求树上路径长度即可。
F
byd 没有一个人写正解,全是乱搞过的。
如果值域很大的话这是一个典型的不可做问题,所以必须从值域入手。
首先枚举 \(n\) 个数,这样问题转化为了在序列里找另一个数使得或起来最大。
考虑贪心地把答案从高位往低位填,那么我们需要快速判断这个位置能否填上 \(1\),这看上去比较困难。我们可以分讨一下:
-
该位为 \(1\),显然选什么或起来都是 \(1\),直接填 \(1\)。
-
该位为 \(0\),设当前答案为 \(ans\),那么需要判断是否存在一个数,使得与这个数或起来之后,得到的数的前面所有位与 \(ans\) 相同,该位为 \(1\),后面随便。因为 \(ans\) 已经保证了前面尽可能大了,所以可以转化为:
求序列中是否存在一个数,使得每一个二进制位都大于等于给定数的对应位。
如果转化为集合来看,这相当于该数为给定数的超集,结合不大的值域,可以采用 sosdp 解决。
总的时间复杂度为 \(O((n+V)\log V)\)。
#include<bits/stdc++.h>
#define N 2000005
using namespace std;
int n,a[N],f[N],m=20,ans;
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;++i)cin>>a[i],f[a[i]]++;
for(int i=1;i<=m;++i){
for(int j=0;j<(1<<m);++j){
if((j&(1<<i-1))==0)f[j]+=f[j^(1<<i-1)];
}
}
for(int i=1;i<=n;++i){
int sum=0,lxy=0;
for(int j=m;j>=1;--j){
if(!(a[i]&(1<<j-1))){
int cnt=f[lxy|(1<<j-1)];
if((a[i]&(lxy|(1<<j-1)))==(lxy|(1<<j-1)))cnt--;
if(cnt>0)sum+=1<<j-1,lxy+=1<<j-1;
}
else sum+=1<<j-1;
}
ans=max(ans,sum);
}
cout<<ans;
return 0;
}
接下来请我们欣赏人类群星闪耀时:
cin>>n;
for(int i=1;i<=n;++i){
cin>>a[i];
box[a[i]]=1;
for(int j=20;~j;--j)
if(a[i]>>j&1)b[j]=max(b[j],a[i]);
}
for(int i=1;i<=n;++i)
for(int j=20;~j;--j)mx=max(mx,a[i]|b[j]);
int idx=0;
for (int i=1000000;i;--i) {
if(box[i]&&mx<i)mx=i;
for(int j=0;j<box[i];++j)a[++idx]=i;
}
for(int i=1;i<=400;++i)
for(int j=1;j<=n;++j)mx=max(mx,a[i]|a[j]);
for(int i=0;i<64;++i){
int x=(seed*=1145141u)%n+1;
for(int j=1;j<=n;++j)mx=max(mx,a[j]|a[x]);
}
cout<<mx<<'\n';
(节选自 @luogu___official 的代码)
int n = read();
for (int i=1;i<=n;++i) read(a[i]);
int ans=0; sort(a+1,a+1+n); n=unique(a+1,a+1+n)-a-1;
for (int i=max(1LL,n-1000);i<=n;++i) {
for (int j=1;j<=i;++j) smax(ans,a[i]|a[j]);
} write(ans);
(节选自 @Loser_syx 的代码)
G
注:本题题解由 @Double_Light 提供,我们不保证这种做法分讨完全,但已经足以通过题目的数据。
考虑分类讨论。
以下称 \(A\) 表示 A 初始所在的位置,\(B\) 为 B 初始所在的位置,环内没有多余的边。
假如存在边 \((A,B)\),那么 A 可以顺着这条边仅用 \(1\) 步赢得游戏。
否则假如 \(B\) 在一个 \(n\) 元环(\(n\geq5\))中,此时 A 有两种策略:
-
在环上追逐 B,此时 B 只需要顺着 A 的方向走一步,A 永远追不上 B。
-
走环外的道路,容易证明 B 如果也走同样的环外道路,A 永远追不上 B。
假如 \(B\) 在一个四元环中,考虑继续分情况讨论:
- A 无法花奇数步到达 \(B\),此时 B 只需要在环上绕圈就可以赢。
- A 可以花奇数步到达 \(B\)(这意味着图上一定有一个奇环),此时 B 不能一直在环上绕圈。
假如 \(B\) 在一个三元环中,此时 B 一定不能在环上绕圈,因为一旦 A 走到环上 B 就需要从环上出去。
那么现在有两种情况没有解决,也就是 \(B\) 在四元环中且 A 可以花奇数步到达 \(B\) 以及 \(B\) 在三元环中。对于这两种情况,B 都需要寻找一个环,到达环上且一直在环上绕圈。根据刚才的分析,这个环应该满足如下条件之一:
- 环上点的个数 \(\geq5\)。(条件 \(1\))
- 是一个四元环且 A 无法花奇数步到达 B 可以在奇数步中到达的点。(条件 \(2\))
经过简单的分析可以得到条件 \(2\) 是不可能达到的。假如 A 无法花奇数步到达 B 可以在奇数步中到达的点,就意味着图上不存在点数是奇数的环,也就是图中只存在四元环或这满足 \(n\geq5\) 的 \(n\) 元环。在这两种情况下,B 只需要在环上绕圈就可以赢。
所以 B 需要找一个满足条件 \(1\) 的环(以下称之为大环)。分如下几种情况:
- 图上不存在满足条件的环,此时 B 必输。
- 图上存在一个满足条件的环。
- \(A\) 不在大环上。
- \(A\) 一步可以到达中心点。A 赢。
- \(A\) 一步不能到达中心点且 B 一步可以到达环上。B 赢。
- \(A\) 一步不能到达中心点且 B 一步不能到达环上。A 赢。
- \(B\) 所在的所有环都与大环没有公共边。
- \(A\) 离中心点的距离大于 \(B\) 离中心点距离 \(+2\),A 必输。
- \(A\) 离大环两边与中心点连边的点距离都为 \(2\):
- 如果 B 顺时针与逆时针都能满足条件 \((1)^*\) 或条件 \((3)\) 则 B 赢。
- 否则 B 会输。
- 否则不妨设 A 顺时针(逆时针同理)走距离大环中与中心点连边的点更近。
- 若 \(A\) 离中心点距离为 \(1\)。
- 若 B 在顺时针或逆时针上满足条件 \((1)\) 则 B 赢。
- 否则 A 赢。
- 若 \(A\) 离中心点距离为 \(2\)。
- 若 B 在顺时针或逆时针上满足条件 \((0)\) 或条件 \((2)\) 则 B 赢。
- 若 B 顺时针走一步可以到达大环则 B 赢。
- 否则 A 赢。
- 若 \(A\) 离中心点距离为 \(3\)。
- 若 B 在顺时针或逆时针上满足条件 \((1)\) 或条件 \((3)\) 则 B 赢。
- 若 B 顺时针走一步或两步可以到达大环则 B 赢。
- 否则 A 赢。
- 若 \(A\) 离中心点距离为 \(1\)。
- \(B\) 所在的一个环与大环有公共边。
- 假设这条边为 \((x,y)\),若 \(A\) 不在 \(x\) 或 \(y\) 上,且顺时针(逆时针同理)走一步能到达 \(x\) 或 \(y\)。
- 若 B 在顺时针方向上满足条件 \((0)\) 或 \((2)\),B 赢。
- 若 B 顺时针走一步可以到达大环,B 赢。
- 否则 A 赢。
- 否则 B 能赢。
- 假设这条边为 \((x,y)\),若 \(A\) 不在 \(x\) 或 \(y\) 上,且顺时针(逆时针同理)走一步能到达 \(x\) 或 \(y\)。
- \(A\) 不在大环上。
- 图上存在不止一个满足条件的环。把上面『满足条件的环』改成『所有满足条件的环』就行了。只要有一个环满足条件 B 就能赢。
\(*\):若 \(B\) 沿某方向走 \(i\) 步可以到达不与中心点连边的点,则称其在该方向上满足条件 \((i)\)。
后来 @Double_Light 给了一种更简单的做法,就是直接写有向图博弈,如果 \(k\) 步以内追不上判无解,其中 \(k\) 是一个比较小的数。
我们发现题面最结尾的空白处有一段话,如果知道为什么比赛在 \(2\) 月 \(8\) 日结束就可以获得神秘奖励,但没有人猜中。
答案是,\(2\) 月 \(8\) 日是 cbc 的生日,也是鸽游的生日。祝它们生日快乐!
crc001
共 \(15\) 人报名,\(5\) 人有分,最高分为 @Loser_syx 的 \(943\) 分。
C
为了方便,下文把题目中的两个变量 \(d,q\) 改名成 \(c1,c2\)。
我们可以证明,\(f_n\) 一定可以表示为 \(k_1r^n+k_2s^n\) 的形式。其中 \(r=\frac{c1+\sqrt{{c1}^2-4c2}}2\),\(s=\frac{c1-\sqrt{{c1}^2-4c2}}2\),证明请参考 link。
因为我们知道前两项,所以可以解方程组算出 \(k_1,k_2\)。注意因为前面的 \(r,s\) 带有根号,所以我们一整个过程都需要进行括域处理,即将每个数表示为 \(a+b\sqrt{Base}\),其中 \(Base={c1}^2-4c2\)。
然后就是推式子:
令 \(t=r^{j}s^{k-j}\),则原式等于:
当然这之前要特判掉 \(t=1\)。
接下来我们发现,这个式子可以在 \(O(k\log n)\) 的时间复杂度内求出,所以我们就做完了……吗?
注意到括域过程中涉及除法,这之中很有可能出现除以一个取模之后得到的零的情况,因为它没有逆元,就会出现错误。
但是注意到除法的过程中分母是 \(a^2-b^2Base\) 的形式,而这个为 \(0\) 相当于是 \(a^2\equiv b^2Base\),这相当于 \(Base\) 是模 \(p\) 意义下的二次剩余!所以直接用 Cipolla 算法求出二次剩余的解就不用括域了。这样我们就完全解决了这道题目。
注意到其实还有一种 \(r\equiv s\) 的情况,但因为本题 \(d,q\le 10^4\) 的限制,这是不可能出现的。并且如果存在这种情况,那么本题可能无法在给定的数据范围下求解。
D
首先一眼先把 \(b\) 前缀和。
如果没有修改的话,相当于两个多项式相乘,我们直接用 NTT 就可以求出答案。
加上修改之后,我们发现几乎没有办法维护,于是直接暴力对操作分块,每 \(B\) 次操作重构一次,令 \(n,q\) 同阶,时间复杂度 \(O(\frac{n^2\log n}B+nB)\)。取 \(B=\sqrt{n\log n}\),可得最优时间复杂度 \(O(n\sqrt{n \log n})\)。
#include<bits/stdc++.h>
#define int long long
#define N 270005
using namespace std;
const int B=7000,mod=998244353;
int n,q,b[N],f[N],g[N],S[N],rev[N],k=1,qwq,ans[N];
int rr[N][2],t,sg[N];
int qpow(int x,int y){
int ans=1;
while(y){
if(y&1)ans=ans*x%mod;
x=x*x%mod;y>>=1;
}
return ans;
}
int qm(int x){
x>mod?x-=mod:0;
return x;
}
void NTT(int n,int *a,int op){
for(int i=0;i<n;++i){
if(i<rev[i])swap(a[i],a[rev[i]]);
}
for(int len=1;len<=n/2;len*=2){
int u=qpow(op==1?3:332748118,(mod-1)/(len*2));
for(int i=0;i<=n-len*2;i+=len*2){
int w=1;
for(int j=0;j<len;++j){
int x=a[i+j],y=w*a[i+j+len]%mod;
a[i+j]=qm(x+y),a[i+j+len]=qm(x-y+mod);
w=w*u%mod;
}
}
}
}
signed main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n>>q;
for(int i=1;i<=n;++i){
int x;cin>>x;S[i]=qm(S[i-1]+x);
}
for(int i=0;i<n;++i){
cin>>g[i];if(i>0)g[i]=qm(g[i]+g[i-1]);
sg[i+1]=qm(sg[i]+g[i]);
}
while(k<=n*2)++qwq,k*=2;
for(int i=1;i<k;++i)rev[i]=(rev[i>>1]>>1)|((i&1)<<(qwq-1));
NTT(k,g,1);
for(int c=1;c<=q;++c){
int op,l,r;cin>>op>>l>>r;
if(c%B==0){
NTT(k,f,1);
for(int i=0;i<=k;++i)ans[i]=f[i]*g[i]%mod;
NTT(k,ans,-1);int inv=qpow(k,mod-2);
for(int i=0;i<n;++i)ans[i]=(ans[i]+mod)*inv%mod;
for(int i=0;i<n;++i)ans[i+1]=qm(ans[i+1]+ans[i]);
for(int i=0;i<n;++i)S[i+1]=qm(S[i+1]+ans[i]);
memset(f,0,sizeof(f));t=0;
}
if(op==1)f[l-1]=qm(f[l-1]+r),rr[++t][0]=l,rr[t][1]=r;
else{
int ans=0;
for(int i=1;i<=t;++i){
int ql=max(l,rr[i][0]),qr=r;
if(ql>qr)continue;
if(qr-rr[i][0]+1>0)ans+=(sg[qr-rr[i][0]+1]-sg[ql-rr[i][0]])*rr[i][1]%mod;
}
ans+=S[r]-S[l-1];
cout<<(ans%mod+mod)%mod<<'\n';
}
}
return 0;
}