4月CWOI杂题

tips:为了避免一不留神题目就被邪恶的 o 老师隐藏,题面文件在 cnblogs 上有备份。

upd:后面没有了。太多了。

C0216 【0407 C组】模拟测试

这场比赛四道题代码加起来长度不超过 1500 个字符,赢!(223+399+330+541=1493)

A 【1231 C组】数组计数

定义 \(f_{i,j}\) 表示前 \(i\) 个数,和为 \(j\) 的方案数,前缀和优化转移。时间复杂度 \(\text{O}(nk)\)

B 【1231 C组】旅行

因为终点是固定的 \(a_i\),考虑把路径转化成找到一个以 1 为起点和终点的环再减去从 \(a_i\) 到 1 的部分。发现这样问题就等价于给你一些点,找到一个最短的联通所有点的边集,这个环会经过其中每条边两次。每次加点的时候一直向上跳到已有的路径上。因为每个点只经过一次,时间复杂度 \(\text{O}(n)\)

C 【1231 C组】进化

因为数据给了 \(T=2^d\) 的部分分,这使我们联想到如果能在 \(\text{O}(n)\) 的时间内解决 \(T=2^d\) 的情况,就可以做到 \(\text{O}(n\log T)\) 了。

首先 \(b_i=(a_{i-1}+a_{i+1})\bmod 2=a_{i-1}\oplus a_{i+1}\)。一次进化后,\(a_i\) 会对 \(a_{i-1},a_{i+1}\) 产生贡献;两次进化后,\(a_i\) 会对 \(a_{i-2},a_{i-1},a_i,a_{i+1},a_{i+2}\) 产生贡献 \(\ldots\) 也就是说,“\(a_i\)\(T\) 次进化后会对 \(b_j\) 造成贡献的次数”等于“令 \(s_k=\sum\limits_{t=1}^kx_i\),满足 \(i+s_k\in[1,n]\) 的,长度为 \(T\) 且只包含 \(1,-1\) 的,\(i+s_T=j\) 的数列 \(x\) 的个数。

发现这个 \(i+s_k\in[1,n]\) 的限制很麻烦,考虑把 \(a_i\) 变成一个如图所示的环:

\(c_0=c_{n+1}=0,c_i=c_{2n+2-i}=a_i\)。发现对 \(c\) 进化和对 \(a\) 进化是一样的,这很显然。

\(m=2n+2\)\(c_i\) 真正会对 \(c_{(i+x-(T-x))\bmod m}\) 产生影响当且仅当 \(c_i\times\binom{T}{x}\equiv 1\pmod 2\),即 \(c_i=1,\binom{T}{x}\equiv 1\pmod 2\)。因为我们此时的 \(T=2^d\),由 Lucas 定理 可知只有当 \(x=0,T\) 时才满足上述条件,即 \(c_i\) 只会影响 \(c_{(i+T)\bmod m},c_{(i-T)\bmod m}\)。一次操作时间复杂度 \(\text{O}(n)\),总时间复杂度 \(\text{O}(n\log T)\)

D 【1231 C组】Y老板的别墅

定义 \(f_{i,j}\) 表示只考虑 \([i,j]\) 间的房子(可以认为在 \(i-1,j+1\) 处各有一栋无限高的房子)的方案数。转移就是枚举当前范围内最高的楼 \(k\),满足 \(r_k=\min(k-i,j-k)\)\(f_{i,j}\leftarrow f_{i,j}+\binom{k-i}{j-i}\times f_{i,k-1}f_{k+1,j}\)。解释一下:乘组合数是因为我们只规定了范围内每栋房子的相对高度。

发现对于一个 \(k\),满足 \(r_k=\min(k-i,j-k)\)\(i,j\) 很少,所以总的转移是 \(\text{O}(n^2)\) 的。

C0217 【0411 C组】模拟测试

A 【0411 C组】游戏

不难发现最优方案是固定的,可以直接从低到高位模拟。

B 【0411 C组】序列操作

类似思路指路:HDU5828 Rikka with Sequence

容易发现一个数模上一个比它小的数时至少减小一半。记录区间最大值,如果最大值已经小于 k 就停止下传。

upd:2023.05.08 原来这就是势能分析。

C 【0411 C组】逆序对

不会证结论,不想写。想写可以去看 oeis。

D 【0411 C组】小王献歌

东方神秘的【数据删除】的 字符串技巧

原题指路:CF914F Substrings in a String

体会到了 if->else 分支优化的巨大力量(暴力 15pts \(\rightarrow\) 46pts)。

考虑优化暴力匹配的过程。对文本串 \(s\) 中每种字符开一个 bitset \(B_c\) 表示字符 c 的所有出现位置。对于求文本串 \(t\)\([l,r]\) 中每次出现的出现位置,定义 \(len\)\(t\) 的长度,我们有以下套路:定义 bitset \(X\)\(X\) 中第 \(i\) 位为 1 表示 \([i,i+len-1]\)\(t\)。遍历 \(t\) 的每一位,同时 \(X\leftarrow X\And B_{t_i}\),最后 \(X\) 中为 1 的位置即为所求。本题中我们统计的是 \(X\)\([l,r-len+1]\) 的答案。注意特判 \(len>r-l+1\) 的情况。

C137 【初2020级】0411 D组考试

原比赛指路:C34 【初2020级】0817 C组-测试3

靠,为什么隐藏了。

D 【0817 C组】二叉树

定义 \(g_{0/1,i,j}\) 表示在 i 的子树内 A/B 最大得分不超过 j 的方案数,\(h_i\) 表示在 i 的子树内任意选的方案数(考虑点权和每个人的选择),有转移式

\[g_{c,u,i}=2\times g_{c,l,i}\times g_{c,r,i} \]

\[g_{c\oplus 1,u,i}=g_{c\oplus 1,l,i}\times h_{r}+g_{c\oplus 1,r,i}\times h_{l} \]

\[h_{u}=2\times h_{l}\times h_{r} \]

边界条件是叶子上 \(g_{0/1,u,i}=i\times k,h_{u}=k^2\)。其中 c 表示当前由谁操控,l,r 表示左儿子/右儿子。

定义 \(f_{0/1,i,j}\) 表示钦定一条从 1 到 i 的重链,A/B 得分刚好为 j 的方案数。有转移式

\[f_{c,l,i}=f_{c,u,i}\times g_{c,r,i},f_{c,r,i}=f_{c,u,i}\times g_{c,l,i} \]

\[f_{c\oplus 1,l,i}=f_{c\oplus 1,u,i},f_{c\oplus 1,r,i}=f_{c\oplus 1,u,i} \]

叶子处统计答案即可。时间复杂度 \(\text{O}(nk)\)

C0220 【0414 C组】模拟测试

没挂分,很高兴。

A 【0414 C组】ZYB建围墙

手玩可以发现最优摆放,是一个正六边形从正下方开始外边贴一圈。

B 【0414 C组】ZYB和售货机

原题目转化成连 \(i\rightarrow f_i\),权为 \(D_{f_i}-c_i\) 的边:对于边 \((u,v,w)\),表示在 \(A_u\neq 0\) 时可以用 \(w\) 的单价卖一个 \(v\),求最大收益。

对于不在环上的点,可以直接以最大价格卖出;对于在环上的点,只需要保留两种最优选择:不在环上的/在环上的。假如一个环上有点的最优选择是在环上的(或者环的大小为 1),则整个环上的点都可以以最优选择卖出;反之,需要牺牲一个点以环上价格卖一个。贪心即可。正确性可以自己手玩一下两种情况(策略就是先都卖到只剩一个,保证最优一选最多)。

C 【0414 C组】ZYB玩字符串

暴力怎么做?在 s 中枚举 p,check 就是递归地每次删除 s 中的 p。

考虑用区间 DP 优化暴力 check 的过程。定义 \(f_{i,j}\) 为 1 表示 \(s_{i,i+1\ldots j}\) 在递归地每次删除 s 中的 p 后剩下的是一个 p 的前缀,反之不是。如果 s 同时满足 \(f_{1,n}=1,\left|s\right|\bmod\left|p\right|=0\) 则可行。转移分两种情况:[i,j] 存在一个完美匹配(即刚好能用题目中的方法表示出来)的后缀,设该后缀长度为 \(k\times len\),那么可以从 \(f_{i,j-k\times len},f_{j-k\times len+1,j}\) 转移过来(其中 len 表示枚举的 p 的长度);[i,j] 不存在一个完美匹配的后缀,那么 \(s_j\) 如果满足 \(s_j=p_{j\bmod len}\),可以从 \(f_{i,j-1}\) 转移过来。这样,check 的时间复杂度就是 \(\text{O}(\frac{n^3}{len})\) 的了。

时间还是比较爆炸,怎么优化?可以随便加一点剪枝:\(\left|s\right|\bmod len=0\) 啊,p 中每个字母出现次数必须是 s 中的因数啊,从短到长枚举 p 啊,p 不重复算啊 \(\ldots\) 等等。

C0223 【0418 C组】模拟测试

T1 好妙。

A 【0418 C组】序列

考场上卡容斥了。

算最终答案是 \(x=i\) 的倍数的方案数很简单,每次选 \(i\) 的倍数就行。怎么容斥?可以倒序枚举 \(x=i\),这样当 \(x=2i,3i\ldots\) 时的答案我们就已经算出来了,可以直接减掉。

B 【0418 C组】 图

启发式合并乱搞就行。

C 【0418 C组】金条争夺

不会。

D 【0418 C组】推销员

发现按 \(s_i\) 从小到大排序后 \(x=k\) 的答案就是 \(\min\limits_{i=1}^n(2s_i+\text{val}(i,k))\),其中 \(\text{val}(x,y)\) 表示前 \(x\) 个房子中最小的 \(y\)\(a_i\) 之和。set 维护一下就做到了 \(\text{O}(n^2)\)

发现对于 \(p<q\)\(\min\limits_{i=1}^n(2s_i+\text{val}(i,p))\)\(\min\limits_{i=1}^n(2s_i+\text{val}(i,q))\) 的最优决策点是不降的,可以使用分治优化计算(因为如果 \(q\) 的最优决策点小于 \(p\),则 \(p\) 挪到 \(q\) 的位置显然不劣)。

C0224 【0420 C组】模拟测试

考得很爆炸。

呃呃,原题大战。

A 【0420 C组】姨

不取模的

题目中的贡献可以看做一个长为 2n 位的 01 串的子集数,所以可以枚举有 r 行 c 列被染黑,计算包含这个子集的方案数,而不用考虑子集的累加贡献是否与原题中的相同。答案就是 \(\sum\limits_{i=0}^n\sum\limits_{j=0}^n\binom{n}{i}\binom{n}{j}\binom{m-(in+jn-ij)}{k-(in+jn-ij)}\),最后除方案数 \(\binom{m}{k}\)。注意到 \(\binom{n}{i},\binom{n}{j}\)\(\binom{m-(in+jn-ij)}{k-(in+jn-ij)},\binom{m}{k}\) 可以分别预处理。

B 【0420 C组】矿石

对相邻两个安全采矿点的容斥是 trivial 的,思考一种每次把前缀和当前点的答案合并的过程。假设对于点 i,i+1,不变(即同时包含 i,i+1)的区间数设为 A,减少(即包含 i 但不包含 i+1)的区间数设为 B,增加(即不包含 i 但包含 i+1)的区间数设为 C,容斥得到的新答案就是原答案加上 \(2^{A+C}-1\) 再减去 \(2^{A}-1\),维护即可。至于为什么只用跟 i 容斥是因为 i+1 与 i 的交叉部分一定严格包含 i+1 与其他点的交叉部分,算了 i 以后就相当于都算过了。

C 【0420 C组】再见括号序列

md,这不纯脑瘫题???考场上怎么连 70 都没写???我是纯智障吗???

为什么自然溢出没被卡啊()

40pts:\(O(n^3)\) 的区间 dp。

70pts:\(O(n^2)\) 枚举区间丢进栈判断。

100pts:70pts 的优化。记录每个起点为 1 的前缀对应的栈的状态,容易发现区间 [l,r] 合法当且仅当前缀 [1,l-1] 和 [1,r] 对应的栈状态相同,hash 即可。单模被卡,可以双模/自然溢出。

C0229 【0426 C组】模拟测试

究极好题。

A 【0425 C组】匹配

发现答案是 n 个 r 之和减去 n/2 个最小的 l+r。

B 【0425 C组】狼人

首先暴力的树上背包你是会的,然后加上一点剪枝就可以过。

复杂度玄学,至今无人会证。

感觉不好,不想写,也没搞懂自己怎么过的。

C 【0425 C组】旅行

倒着 dp,转移 \(f_i=\max\limits_{j=i+1}^{i+t_j}f_j-\lfloor\frac{j-i}{k}\rfloor d+h_i\)。维护 k 棵大小为 n/k 的 BIT,第 x 棵表示 \(i\bmod=x\) 时随着 \(\lfloor\frac{j-i}{k}\rfloor d\) 的变化(就是前 x-1 个是 \(f_j-md\),后面是 \(f_j-(m+1)d\) 的形式),每块的最大值。这样你一次查询中间那些每块大小为 k 的区间就可以 \(\log n\) 了(具体大小你可以加加减减一下,反正大小关系是不变的)。这样只剩下查询区间的前后两块不完整的区间了。

对于查询区间的头部,他就是 i 在的这一块的一个后缀,扫的时候记一下就行;对于尾部,我们开 n/k 棵大小为 k 的 BIT 表示每块的前缀最大值。

有一种可能就是令 \(R_i=i+t_i\)\(R_i\) 所在块编号为 o,i 在 o 中对应位置 i' 在 \(R_i\) 前面。这样看似会出现错误(从 o 这块的开头到 i'-1 这部分你应该少减一个 d),但是你会发现这部分我们可以单独查询一下少减一个 d 的结果,且这个结果一定会比查询 o 开头到 \(R_i\) 中的这部分的错误结果更优(少减一个 d),所以错误的那部分答案不会影响真实答案。

每次 dp 完一块后再一起更新答案即可。

放一下 code:

#include<bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
typedef long long ll;
#define T (1<<15)
char buf[T],*p1=buf,*p2=buf;
#define nc() (p1==p2&&(p2=buf+fread(p1=buf,1,T,stdin),p1==p2)?-1:*p1++)
ll read(){
	ll x=0;char c;bool s=0;
	do c=nc();while(c!=45&&(c<48||c>57));
	if(c==45)c=nc(),s=1;
	do x=10*x+(c&15),c=nc();while(c>=48&&c<=57);
	return s?-x:x;
}
#undef T
int n,m,k,d,h[2000005],t[2000005],f[2000005];
struct BIT1{
	vector<int>c;
	void build(){
		c.clear();c.resize(m+5);
		for(int i=0;i<=m+1;i++)c[i]=-inf;
	}
	void add(int x,int y){
		for(;x<=m;x+=x&-x)c[x]=max(c[x],y);
	}
	int ask(int x){
		int res=-inf;
		for(;x;x-=x&-x)res=max(res,c[x]);
		return res; 
	}
};
vector<BIT1>Tr1;//all block
struct BIT2{
	vector<int>c;
	void build(){
		c.clear();c.resize(k+5);
		for(int i=0;i<=k+1;i++)c[i]=-inf;
	}
	void add(int x,int y){
		for(;x<=k;x+=x&-x)c[x]=max(c[x],y);
	}
	int ask(int x){
		int res=-inf;
		for(;x;x-=x&-x)res=max(res,c[x]);
		return res; 
	}
};
vector<BIT2>Tr2;//every block
int id(int ind){
	return (ind-1)%k;
}
int bel[2000005],lt[2000005],rt[2000005];
void solve(int l,int r,int o){
	int ma=-inf;BIT2 pre;pre.build();
	for(int i=r;i>=l;i--){
		int res=-inf,L=i+1,R=i+t[i];
		if(i==n){
			f[i]=h[i];
			pre.add(i-lt[o]+1,f[i]);ma=max(ma,f[i]);
			continue;
		}
		if(R<=r){
			res=max(res,pre.ask(R-lt[o]+1));
			f[i]=res+h[i];
			pre.add(i-lt[o]+1,f[i]);ma=max(ma,f[i]);
			continue;
		}
		res=max(res,ma);
		int lim=bel[R]-1;
		if(lim>o)res=max(res,Tr1[id(i)].ask(lim)+(o+1)*d);
		if(id(R)>=id(i)){
			res=max(res,Tr2[lim+1].ask(id(R)+1)-(lim-o+1)*d);
			res=max(res,Tr2[lim+1].ask(id(i))-(lim-o)*d);
		}
		else{
			res=max(res,Tr2[lim+1].ask(R-lt[lim+1]+1)-(lim-o)*d);	
		}
		f[i]=res+h[i];
		pre.add(i-lt[o]+1,f[i]);ma=max(ma,f[i]);
	}
}
void update(int l,int r,int o){
	BIT2 tmp;tmp.build();
	for(int i=l;i<=r;i++){
		tmp.add(i-lt[o]+1,f[i]-(o+1)*d);
	}
	for(int i=l;i<=r;i++){
		Tr1[id(i)].add(o,tmp.ask(k));
		tmp.add(i-lt[o]+1,f[i]-o*d);
		Tr2[o].add(i-lt[o]+1,f[i]);
	}
}
signed main(){
	n=read(),k=read(),d=read(),m=(n+k-1)/k;
	for(int i=1;i<=n;i++)h[i]=read();
	for(int i=1;i<n;i++)t[i]=read();
	for(int i=1;i<=n;i++)f[i]=-inf;
	Tr1.resize(k+5);
	for(int i=0;i<=k+1;i++)Tr1[i].build();
	Tr2.resize(m+5);
	for(int i=0;i<=m+1;i++)Tr2[i].build();
	for(int i=1;i<=n;i++)bel[i]=(i-1)/k+1;
	for(int i=1;i<=m;i++)lt[i]=(i-1)*k+1,rt[i]=min(n,i*k);
	for(int o=m;o>=1;o--){
		solve(lt[o],rt[o],o);
		update(lt[o],rt[o],o);
	}
	int ans=0;
	for(int i=1;i<=n;i++)ans^=(f[i]+i);
	printf("%lld",ans);
	return 0;
}

D 【0425 C组】密码

【数据删除】的构造,没什么营养,没写。

C0232 【0428 C组】模拟测试

A 【0428 C组】前k大

枚举 \(f(n)=x\),则 \(a_n=n+c\times f(n)=p2^x+cx\),其中 p 是一个奇数。这就是一个等差数列的形式,二分答案即可。

B 【0425 C组】石头剪刀布

想不到的线性 dp。

我们记当前要尽力保留的为 W,会产生威胁的为 D,会被利用的叫 S。

定义 \(f_{i,0/1}\) 表示考虑前 \(i\) 个人,末尾是否存在没有被消灭的 D,能保留的最多的 W 的数量。

  • 如果 \(s_i\) 为 W:
    • \(f_{i,0}=f_{i-1,0}+1\)
    • \(f_{i,1}=f_{i-1,1}\)(由于末尾存在没有消灭的 D,所以当前这个 W 没有贡献);
  • 如果 \(s_i\) 为 D:定义 \(ph\) 表示 \(i\) 之前的最后一个 S 出现的位置,\(pq\) 表示 \(i\) 之前最后一个 ? 出现的位置
    • \(f_{i,0}=\max(f_{ph,0},f_{pq-1,0},f_{pq-1,1})\)(这个 D 要消灭掉;-1 是因为 ? 可能会有贡献,影响答案);
    • \(f_{i,1}=\max(f_{i-1,0},f_{i-1,1})\)
  • 如果 \(s_i\) 为 S:
    • \(f_{i,0}=\max(f_{i-1,0},f_{i-1,1})\)
    • \(f_{i,1}\) 没有值(因为在当前就把 D 消灭掉一定比不消灭更优);
  • 如果 \(s_i\) 为 ?:
    • \(f_{i,0}= \max(f_{i-1,0}+1,f_{i-1,1})\)(同 S,但 ? 可以产生贡献);
    • \(f_{i,1}\) 没有值;

\(O(n)\) 的 dp。喜欢的话可以压空间。

C 【0428 C组】吉司机线段树

首先原序列可以写成一个长为 2n 的操作序列,这个你是会的。

删除可以倒着看做加入。我们记 \(i\) 最后一次 1 操作的时间点为 \(lst_i\)\(mx_i\) 表示从 \(i\) 这个时刻开始往后最大的一次 2 操作的值。我们求的就是 \(\sum\limits_{i=1}^n mx_{lst_i}\),需要支持 \(lst_i\) 的单点改和 \(mx_i\) 的区间改(可以线段树上二分找到插入一个 2 操作后会影响后缀最大值的区间)。线段树送走。

不要像这个 SB 一样忘了在二分的时候下传标记调一年。

D 【0428 C组】排列

连暴力都不会。

C0234 【0430 C组】模拟测试

感觉比上一场小清新了不少(不过好像是 4 道原题?)。

A 【0430 C组】ksum

隐藏条件:\(a_i\ge0\)

sb 题,有加强版 U295987 ksum (hard version)\(\left|a_i\right|\le10^9\))(数据不保证正确性,出锅请联系我)。

B 【0420 C组】label

感觉有点神奇。

定义 \(f_{i,j}\) 表示 \(i\) 这个点值为 \(j\) 的方案数,前缀和优化可以做到 \(O(nm)\)

发现 \(f_i\) 的取值是对称的(显然),且中间有很长一段是相同的,所以我们可以只保留左右至多 \((n-1)k\) 个的值(中间的记一个就够了)。

为什么够了?对于深度为 1 的树(就是叶子节点),显然答案都是 1;对于一棵深度为 2 的树,只有左右两边 k 个会有影响;对于一棵深度为 3 的树,因为转移时左右两边 k 个有变化,所以左右两边 2k 个会有影响 \(\ldots\) 以此类推,n 个点深度至多为 n,故至多 \((n-1)k\) 种变化。时间复杂度 \(O(n^2k)\)

C 【0430 C组】square

定义 \(f_{i,j}\) 表示以 \((i,j)\) 为左上角能扩展出的最大正方形的边长。这个可以 \(O(n^2\log n)\) 或者 \(O(n^2)\) 预处理。

考虑优化暴力二分答案的过程。假设当前二分的答案为 mid,相当于我们要看从 \((x_1,y_1)\)\((x_2-mid+1,y_2-mid+1)\) 中间 f 的最大值是否不小于 mid。二维 rmq 送走,时间复杂度 \(O(n^2\log^2n)\)。略微卡常,请预处理对数表。

D 【0430 C组】最短路

如果你不会做,再看看题面。

因为保证有解,且保证解是两点之间的最短路,k 个点一定在一条路径上,求“直径”即可。

posted @ 2023-04-08 16:51  xx019  阅读(24)  评论(0)    收藏  举报