CSP2025 游记

游记

为什么停课还要上语文英语。
为什么我语文英语这么差。

2025.10.28

模拟赛。
T1 使,T2 使,T3 使,T4 使。
T1 打表,T2 卡 set,T3 不卡暴力,T4 不会。
你数据 252 个点,\(n,c \le 1000\),卡不掉 \(O(nc^2)\)

2025.10.29

???
模拟赛。
15+0+35+0=50,no。
T1 是橙题改,T2 是蓝题,T4 是邪恶数据结构。
怎么一题不会。

题解

2025.10.27

AT_arc149_d

维护 \(<0\)\(>0\) 两段。那么一次 \(D\) 操作会有:

  1. 对于 \(<0\) 的段。
    1. \(a_i <0-d\),仍然 \(<0\)
    2. \(a_i=0-d\),在这个时间结束。
    3. \(a_i > 0-d\),变为 \(>0\)
  2. 对于 \(<0\) 的段。
    1. \(a_i <d\),变为 \(<0\)
    2. \(a_i=d\),在这个时间结束。
    3. \(a_i > d\),仍然 \(>0\)

发现每次就是把两段分裂成 \(O(1)\) 段,然后重新合并或者删除。

平衡树维护。时间复杂度 \(O(n\log^2 n)\)

这里的合并需要使用有交合并,且并查集优化。

AT_arc112_e

简单观察发现,没被选择的点在 \(a\) 序列中对应一个区间。如果这个区间为 \([l,r]\),那么 \(a_{1\dots l-1}\)\(a_{r+1\dots n}\) 的操作顺序一定。

同时,如果 \(x\) 这个值先操作一次,然后又操作一次,那么之前的那次操作显然被覆盖掉了。也就是我们只在意每个数最后的操作。

定义状态函数 \(f_{i,j}\) 表示后面 \(i\) 次操作中,有 \(j\) 次操作是某个数的最后一次操作的方案数。则 \(f_{i,j}=2\times j\times f_{i-1,j}+f_{i-1,j-1}\)

那么答案就是 \(\sum\limits_{l=0}^{n}\sum\limits_{r=0}^{n-l}C_{l+r}^l\times f_{m,l+r}\)。时间复杂度 \(O(n^2)\)

AT_agc074_b

首先如果 \(A\) 能变成 \(B\),那么 \(n\) 次操作一定可以。

具体地,维护出若干个区间,满足每个区间 \([l,r]\)\(a,b\) 都恰好有一个 \(1\),且它们的位置不同。那么选择两个区间交换(交换的长度为它们的最小值),一定能使其中一个匹配。例如:

100 0001
001 1000

可以交换得到:

001 0100
001 1000

那不超过 \(\frac{n}{2}\) 次交换怎么做?简单观察发现,实际上上面的操作次数是 \(O(cnt_1)\) 的。那么由于 \(cnt_0+cnt_1=n\),所以 \(\min(cnt_0,cnt_1) \le \frac{n}{2}\)。在 \(cnt_1>cnt_0\) 的时候 \(01\) 反转一下即可。

对于无解的判断,就是 \(a,b\)\(1\) 的数量不同。或者最后剩下的都是形如:

100..0
000..1

000..1
100..0

的区间。

P14316

区间数颜色问题。

考虑扫描线,扫 \(r\) 维护 \(pre_i\) 表示 \(\lfloor\frac{a_x}{a_y}\rfloor=i\)\(\min(x,y)\) 的最大值。显然 \(i\) 只有 \(O(V)\) 种。加进来一个 \(a_r\)

  1. \(r=y\)

    根号分治。

    1. \(a_r < B\)。在加进来 \(a_x\) 的时候枚举 \(a_r=y\),那么就可以知道 \(\lfloor\frac{a_x}{y}\rfloor=i\)。那么在 \(x\) 后面第一个 \(a_r=y\) 时遍历所有的 \(i\) 即可,这样一个 \(x\) 只会对应一个 \(y\)。修改时间复杂度 \(O(n B)\)。查询时间复杂度 \(O(1)\)
    2. \(a_r \ge B\)。枚举 \(\lfloor \frac{x}{a_r}\rfloor=i\)。那么对于每个 \(i\)\(x\) 是一段区间 \([l,r]\)。需要维护区间 \(\max\)。注意到对于同一个 \(r\)\(\bigcup\limits_{}^{}[l_i,r_i]=[1,V]\)。则维护块内前缀,后缀,整体 \(\max\) 后总复杂度 \(O(\sqrt{V})\)。查询时间复杂度 \(O(n\frac{V}{B}+n\sqrt{V})\),修改时间复杂度 \(O(n\sqrt{V})\)

    平衡后可以做到 \(O(n\sqrt{V})\)

  2. \(r=x\)

    \(|\{\lfloor\frac{w}{i}\rfloor| 1 \le i \le w\}|\)\(O(\sqrt{w})\) 级别的。那么对 \(\lfloor \frac{a_r}{y} \rfloor=i\) 整除分块。需要维护区间 \(\max\)。同上。时间复杂度 \(O(n\sqrt{V})\)

那么 \(pre_i\) 一共会有 \(O(n\sqrt{V})\) 次修改,查询次数 \(O(n)\)。维护 \(O(1)-O(\sqrt{V})\) 的区间加分块即可。

时间复杂度 \(O(n\sqrt{V})\)

注意到 \(1.1\) 的那个暴力枚举的空间复杂度是 \(O(n\sqrt{V})\) 的,被卡了。考虑暴力枚举 \(a_x\)。因为如果 \(x < lst_r\) 时,\(x\)\(lst_r\)\(r\) 的贡献相同。所以没用。那么每个 \(x\) 还是只会对 \(O(B)\) 个数每个数贡献一次。空间复杂度变为 \(O(n)\)

可恶,卡场。发现正解和这个差不多,但是对于 \(2\) 情况,数量实际上是 \(2\sqrt{V}\) 级别的,开 4s 和一位。

AT_arc128_d

删的数是若干个区间。

那么定义状态函数 \(f_{i}\) 表示最后一个数为 \(i\) 的方案数。则:\(f_i=\sum f_j [c(j+1,i-1)]\)

那么问题就是一个区间能否删掉。

进一步发现,依赖 \(j\) 的数和依赖 \(i\) 的数形成了区间的前后缀。也就是说,如果区间能删完,一定存在 \(A_k \ne A_{j} \ne A_i\),使得 \([j,k]\) 中相邻两个数不同,\([k,i]\) 中相邻两个数不同。

\(pre_i\)\(i\) 最远能到的位置 \(j\),使得 \([j,i]\) 中相邻两个数不同。那么只要 \(pre_i \le j\)\([j+1,i-1]\) 中存在一个 \(k\),有 \(A_{k}\ne A_j \ne A_i\),就可以转移。

注意到如果 \([j+1,i-1]\) 不存在 \(k\),那么 \([j,i]\) 的颜色数量 \(\le 2\)。维护 \(lst_i\)\(i\) 能到最远的位置 \(j\),是的 \([j,i]\) 中颜色数量 \(\le 2\)

则:\(f_i =\sum\limits_{j=pre_i}^{lst_i-1}f_j\)。前缀和优化即可。

对于 \([lst_i,i-2]\)\(j\),序列一定长成:\(0101010101...01010\)。发现这个时候只有 \(j=i-2\) 时可能能够转移。条件是 \(i-1\) 为另一种颜色。

对于 \(j=i-1\),显然可以转移。

时间复杂度 \(O(n)\)

AT_arc127_d

如果求 \(\sum\limits_{i=1}^{n}\min(x \oplus A_i,y \oplus B_i)\) 怎么做。

枚举 \((x \oplus A_i)\)\((y \oplus B_i)\)\(LCP\)。那么如果 \((x\oplus A_i)\)\((y\oplus B_i)\) 的前 \(j\) 项相同,则有 \(x,y\) 的第 \(1\sim j\) 项是否相同的限制。若相同则为 \(0\),否则为 \(1\)

那么记 \(f_{s,0/1,0/1}\) 表示当限制为 \(s\) 且第一位没有限制的位 \(A_{i,j}=0/1,B_{i,j}=0/1\) 时的贡献。那么枚举 \(i\),维护 \(f_{s,0/1,0/1}\) 的时间复杂度 \(O(n\log V)\)。查询的时间复杂度 \(O(\log V)\)

现在转到 \(\min(A_{i} \oplus A_j,B_i \oplus B_j)\),一样处理即可。时间复杂度 \(O(n\log V)\)

拥有使代码:

il void solve(){
	n=rd; int idx=0;
	for(re int i=1;i<=n;++i) a[i]=(rd<<1)+1;
	for(re int i=1;i<=n;++i) b[i]=(rd<<1);
	for(re int i=1;i<=n;++i){
		int s=0;
		for(re int j=18;j>=0;--j){
			int x=((a[i]>>j)&1);
			int y=((b[i]>>j)&1);
			if(!id[j][s]) id[j][s]=++idx;
			if(x==y){
				if(x==0){
					f[id[j][s]][1][0][1].push_back(b[i]);
					f[id[j][s]][0][1][0].push_back(a[i]);
				}
				else{
					f[id[j][s]][0][1][1].push_back(b[i]);
					f[id[j][s]][1][0][0].push_back(a[i]);					
				}
				s=(s<<1);
			}
			else if(x==1){
				f[id[j][s]][1][1][0].push_back(a[i]);
				f[id[j][s]][0][0][1].push_back(b[i]);
				s=(s<<1)+1;
			}
			else{
				f[id[j][s]][1][1][1].push_back(b[i]);
				f[id[j][s]][0][0][0].push_back(a[i]);				
				s=(s<<1)+1;
			}
		}
	}
	int res=0;
	for(re int i=1;i<=n;++i){
		int s=0;a[i]-=1;
		for(re int j=18;j>=0;--j){
			int x=((a[i]>>j)&1);
			int y=((b[i]>>j)&1);
			if(!id[j][s]) id[j][s]=++idx;
			g[id[j][s]][x][y][0].push_back(a[i]);
			g[id[j][s]][x][y][1].push_back(b[i]);
			if(x==y) s=(s<<1);
			else s=(s<<1)+1;			
		}
	}
	for(re int Id=1;Id<=idx;++Id)
	for(re int i=0;i<=1;++i)
	for(re int j=0;j<=1;++j)
	for(re int k=0;k<=1;++k){
		if(!f[Id][i][j][k].size()) continue;
		if(!g[Id][i][j][k].size()) continue;
		for(re int I=0;I<=1;++I)
		for(re int J=0;J<=18;++J) cnt[I][J]=0;
		int n1=(int)(f[Id][i][j][k].size());
		int n2=(int)(g[Id][i][j][k].size());
		for(auto x:f[Id][i][j][k]){
			for(re int J=0;J<=18;++J){
				int xx=((x>>J)&1);
				if(xx) ++cnt[0][J];
			}
		}
		for(auto x:g[Id][i][j][k]){
			for(re int J=0;J<=18;++J){
				int xx=((x>>J)&1);
				if(xx) ++cnt[1][J];
			}
		}
		for(re int J=1;J<=18;++J){
			int val=(1ll<<J-1);
			int c=cnt[0][J]*(n2-cnt[1][J])+(n1-cnt[0][J])*cnt[1][J];
			res=res+val*c;
		}	
	} cout<<res/2<<"\n";
    return ;
}

2025.10.28

CF1765H

考虑二分。

那么现在问题变成,每个点有个截止时间 \(p_i\)\(u\) 需要在 \(v\) 之前访问,求是否可行。

有个显然的贪心是把图建出来,记 \(mi_i\)\(i\) 能到的点中,\(p_j\) 的最小值。然后跑拓扑排序,队列中元素按照 \(mi_i\) 升序。

这个的时间复杂度 \(O(n(n+m)\log^2n)\),显然炸。

简单观察,发现 \(mi_i \ge \max(mi_j[j \to i])\),也就是每次加进去的 \(mi_v\) 不比当前的 \(mi_u\) 小。那么维护队列里 \(mi_i=x\)\(i\) 的集合,每次只要集合中没有数就往大的找。时间复杂度 \(O(n(n+m)\log n)\)

AT_arc122_d

困难。

如果最优决策下,\((i,j)\) 为一对。那么无论 A 怎么选,\(B\) 都能配对。答案也就是 \(\max (i \oplus j)\)。所以问题等价于,求最小的 \(x\),使得将 \(A\) 分成 \(n\) 组后,\(\max (A_i \oplus A_j) \le x\)。不能二分。

从高往低考虑。对于 \(x\)。记 \(s1\) 为前缀为 \(x\) 的数中,下一位为 \(1\) 的数的集合,\(s0\) 为下一位为 \(0\) 的数的集合。

如果 \(s1\) 是偶数,那么可以通过 \(s0\) 中任意配对,\(s1\) 中任意配对使下一位为 \(0\)。且 \(s0\)\(s1\) 配对一定会使下一位为 \(1\),不优。那么拆成两个新的 \(x1,x2\) 与两个新的集合 \(s1,s2\)。继续执行。

如果 \(s1\) 是奇数。那么必然会有一个和 \(s0\) 中的数配对。对于剩下的数,因为下一位是 \(0\),所以不可能大于 \(s1\)\(s0\) 中两个数配对得到的异或和。那么只需要让这两个数的异或和最小。Trie 维护。

由于每个数对多插入或查询一次,所以时间复杂度 \(O(n\log V)\)

2025.10.29

AT_arc117_d

如果是一条链怎么做。

那么就是对于任意 \((i,j)\)\(|e_i-e_j|\ge |i-j|\)。构造 \(1,2,\dots, n\) 即可。

好像没用。如果不是链怎么做。

\(dis(u,v)=dep_u+dep_v-2dep_{lca}\)。那么有:

\[e_u-dep_u \ge dep_v+e_v-2dep_{lca}\\ e_v-dep_v\ge dep_u-e_u-2dep_{lca}\\ \]

显然答案上界是 \(\max (dep_u)\)。因为 \(e_u=dep_u\) 一定行。

\(e_i\) 从小到大排序。

\(dis(a,b)+dis(b,c) \ge dis(a,c)\),在 \(b \in P(a,c)\) 时取等。而 \((e_i-e_{i-1})+(e_{i+1}-e_i)=e_{i+1}-e_{i-1}\),所以只需要满足相邻两个就行了。

问题变成,构造 \(e\),使得 \(e_{k_i}-e_{k_{i-1}}\ge dis(k_i,k_{i-1})\),且 \(e_{k_n}\) 最小。

发现实际上是从 \(k_1\) 出发,走到 \(k_n\)。也就是说,\(\sum dis(k_i,k_{i+1})\) 不小于 \(2(n-1)-dis(k_1,k_n)\)。那么 \(\sum e_{k_i}-e_{k_{i-1}}=e_{k_n}-e_{k_1} \ge 2(n-1)-dis(k_1,k_n)\)。在 \(k_{1}\dots k_n\) 构成 DFS 序取等。

显然 \(e_{k_1}=1\)。则 \(e_{k_n}= 2(n-1)-dis(k_1,k_n)+1\)。要让 \(e_{k_n}\) 小,就要让 \(dis(k_1,k_n)\) 大。\(dis(k_1,k_n)\) 不大于树的直径。

那么就是求直径上两个点,然后构造 DFS 序,使得直径上两点分别为起点,终点。时间复杂度 \(O(n)\)

AT_arc112_c

定义状态函数 \(f_u\) 先手在 \(u\) 为根的子树中能得到的最多金币数量。

那么 \(u\) 这个点会先贡献 \(1\)。变成后手操作。

对于一棵子树 \(u\),从 \(u\) 开始到回到 \(u\),中途会有 \(2(siz_u-1)+siz_u\) 次操作。也就说,如果已知进入时是先手还是后手,那么就可以知道返回时是先手还是后手。具体地,有:

  1. \(siz_v\) 是偶数。如果当前在 \(u\) 的是先手,那么后手走到 \(v\),变成先手,操作偶数次,回到 \(v\) 是先手,回到 \(u\) 是后手。如果是后手,那么回到 \(u\) 是先手。
  2. \(siz_v\) 是奇数。如果当前是先手,结束是先手;如果当前是后手,结束是后手。

现在在 \(u\) 的时候是后手先走,那么如果存在 \(siz_v\) 是偶数,先走不劣,因为可以继续得到 \(f_v\)。当然也可能走一个 \(siz_v\) 是奇数,但 \(f_{v}\) 很大的。发现后面那个显然可以在走完偶数后再走。则现在变成先后手交替的过程了。每个人都会选择当前剩下的点中 \(f_{v}\) 最大的。优先队列或排序后维护即可。时间复杂度 \(O(n\log n)\)

哦,有点问题。这个只考虑了对决策者自己的贡献,没考虑另一个人得到的硬币数量。对于 \(siz_v\) 是偶数,自己可以获得 \(f_{v}\),另一个人会获得 \(siz_v-f_v\)。有:

  1. \(2f_v \ge siz_v\),延续之前的决策,后手直接选完。
  2. \(2f_{v}<siz_v\),让另一个人选自己可以获得更多。这个时候只有通过 \(siz_{v}\) 是奇数的点来交替。但是发现如果后手交换成先手,先手也会在存在 \(siz_v\) 是奇数的点的情况下继续交替。

可以发现,在选完 \(siz_v\) 是偶数且 \(2f_v\ge siz_v\)\(siz_v\) 是奇数的点后才会给某一个人剩下所有点。因为如果当前存在 \(siz_v\) 是奇数,且 \(2f_v \ge siz_v\),选则不劣。如果不存在,当存在 \(siz_v\) 是奇数时(\(2f_v < siz_v\)),如果选择那些点,那么要么剩下的所有点都选在选 \(siz_v\) 是奇数的,要么选一部分再通过 \(siz_v\) 是奇数的交换。显然无论哪种都是不优于直接选 \(siz_v\) 是奇数的。

那么决策如:

  1. 后手将所有 \(siz_v \bmod 2=0 \land 2f_v \ge siz_v\) 的选完。
  2. 后手,先手,后手……轮流选,每次选择 \(siz_v\bmod 2=1\)\(siz_v-2f_v\) 尽量大的,因为尽量保持差距不劣。
  3. 剩下所有 \(siz_v \bmod 2=0 \land 2f_v < siz_v\) 的点给当前决策者。

用优先队列或排序维护即可。时间复杂度 \(O(n\log n)\)

哦,进到 \(v\) 的点是先手不是后手,因为后手是走到 \(v\),不过没关系。

il void dfs(int u){
	int st=0,x=1,y=0;
	int rx=0,ry=0;
	siz[u]=1;
	for(auto v:e[u]) dfs(v),siz[u]+=siz[v];
	priority_queue<pii> qu;
	for(auto v:e[u]){
		if(siz[v]&1) qu.push({siz[v]-2*f[v],v});
		else{
			if(2*f[v]<=siz[v]) x+=f[v],y+=siz[v]-f[v];
			else rx+=f[v],ry+=siz[v]-f[v];
		}
	}
	while(!qu.empty()){
		int v=qu.top().y;qu.pop();
		if(st==0) x+=f[v],y+=siz[v]-f[v];
		else y+=f[v],x+=siz[v]-f[v];
		st^=1;
	}
	if(st==0) x+=rx,y+=ry;
	else y+=rx,x+=ry;
	f[u]=x;
	return ;
}

2025.10.30

CF864F

*2700 和一位。

倍增,对于终点相同的,暴力处理每个点走 \(2^i\) 步后会到哪个点。那么无解的情况就是走若干步后仍然不停下。时间复杂度 \(O(n^2\log n)\)

AT_arc115_d

如果是棵树怎么做。打暴力 DP 发现 \(n\) 个点的树,有 \(k\) 个奇数点的方案数是 \(C_{n}^k\)。为啥呢,因为如果先确定一些点是奇数点。那么从叶子开始保留边,如果叶子是奇数点,则需要保留与其父亲的边,否则不能。在继续往上推。那么每条的状态就是一定的。

那如果是无向图呢。考虑一个联通块。还是依据树来做吧。因为在点状态一定的时候,一定可以通过上面的模拟得到一组唯一的构造。所以对于非树边,如果我们确定那 \(k\) 个奇数点,再确定哪些边要保留,就变成确定 \(k'\) 个奇数点的情况了。所以答案是 \(2^{m-(n-1)}C_{n}^k\)

如果不是一个联通块。考虑 DP。定义状态函数 \(f_{i,j}\) 表示前 \(i\) 个联通块,有 \(j\) 个奇数点的方案数。时间复杂度 \(O(n^3)\)。真的吗?注意到联通块大小和 \(\sum siz_i=n\),那么每次 \(O(siz_i \times n)\) 转移实际上是 \(O(n^2)\) 的。要注意,选不出奇数个奇数点,因为度数和为偶数。

posted @ 2025-10-27 21:51  harmis_yz  阅读(15)  评论(1)    收藏  举报