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 赢。
    • \(B\) 所在的一个环与大环有公共边。
      • 假设这条边为 \((x,y)\),若 \(A\) 不在 \(x\)\(y\) 上,且顺时针(逆时针同理)走一步能到达 \(x\)\(y\)
        • 若 B 在顺时针方向上满足条件 \((0)\)\((2)\),B 赢。
        • 若 B 顺时针走一步可以到达大环,B 赢。
        • 否则 A 赢。
      • 否则 B 能赢。
  • 图上存在不止一个满足条件的环。把上面『满足条件的环』改成『所有满足条件的环』就行了。只要有一个环满足条件 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\)

然后就是推式子:

\[\sum_{i=1}^n(k1r^i+k2s^i)^k \]

\[=\sum_{i=1}^n\sum_{j=0}^kC_{k}^j(k1r^i)^j\times (k2s^i)^{k-j} \]

\[=\sum_{i=1}^n\sum_{j=0}^kC_{k}^j{k1}^j{k2}^{k-j}r^{ij}s^{i(k-j)} \]

\[=\sum_{j=0}^k(C_{k}^j{k1}^j{k2}^{k-j}\sum_{i=1}^nr^{ij}s^{i(k-j)}) \]

\(t=r^{j}s^{k-j}\),则原式等于:

\[\sum_{j=0}^k(C_{k}^j{k1}^j{k2}^{k-j}\sum_{i=1}^nt^i) \]

\[=\sum_{j=0}^kC_{k}^j{k1}^j{k2}^{k-j}\frac{t(t^n-1)}{t-1} \]

当然这之前要特判掉 \(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;
}
posted @ 2025-02-09 19:36  Milthm  阅读(44)  评论(0)    收藏  举报