CWOI NOIP 真题训练专题

链接:link

希望能苟到这些题发挥用处的时候。

A - 排水系统

topsort。

B - 报数

埃筛。

C - 种花

模拟。

D - 涂色游戏

link

E - 字符串匹配

我会 hashing!考虑枚举 \(AB\)\(i\),hash 判断是否相同,于是 \(C\) 是剩下的,可以得到。发现 \(A,C\) 都只会是一段前缀/后缀,于是预处理任意前后缀的 \(F\),假设 \(AB=s[1\ldots p]\),相当于统计有多少个 \(t\) 满足 \(t\in[1,p)\)\(F(s[1\ldots t])\le F(C)\),开个桶即可。复杂度 \(\mathcal{O}(n\ln n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
using namespace std;
const int inf=1e18;
const ull BASE=1145141;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
ull pw[2000005],h[2000005];char s[2000005];
ull calc(int l,int r){
	return h[r]-h[l-1]*pw[r-l+1];
}
int sum[55],c[55],pre[2000005],suf[2000005];
void solve(){
	scanf("%s",s+1);int n=strlen(s+1),cnt,ans=0;pre[0]=suf[n+1]=0;
	for(int i=0;i<=26;i++)sum[i]=0;
	for(int i=0;i<26;i++)c[i]=0;
	cnt=0;for(int i=1;i<=n;i++)cnt-=c[s[i]-'a'],c[s[i]-'a']^=1,cnt+=c[s[i]-'a'],pre[i]=cnt;
	for(int i=0;i<26;i++)c[i]=0;
	cnt=0;for(int i=n;i>=1;i--)cnt-=c[s[i]-'a'],c[s[i]-'a']^=1,cnt+=c[s[i]-'a'],suf[i]=cnt;
	for(int i=1;i<=n;i++)h[i]=h[i-1]*BASE+s[i];
	for(int i=1;i<n;i++){
		ull val=calc(1,i);
		for(int j=1;j*i<n;j++){
			if(calc((j-1)*i+1,j*i)!=val)break;
			ans+=sum[suf[j*i+1]];
		}
		for(int j=pre[i];j<=26;j++)sum[j]++;
	}
	printf("%lld\n",ans);
}
signed main(){
	pw[0]=1;
	for(int i=1;i<=(1ll<<20);i++)pw[i]=pw[i-1]*BASE;
	int T=read();
	while(T--)solve();
	return 0;
}

F - 幂次

link

G - 数列

H - 圣诞树

link

I - 建造军营

怎么缩完边双之后还是不会(

先缩边双。设计这样一个 dp:\(f_{i,0/1}\) 表示在 \(i\) 的子树内,没有/有军营的方案数,强制任意两个军营之间的边必须被看守。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=1e9+7,i2=(mod+1)/2;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int qpow(int b,int p){
	int res=1;
	for(;p;p>>=1,b=b*b%mod)if(p&1ll)res=res*b%mod;
	return res;
}
struct edge{
	int v,id,nxt;
}e[2000005]; 
int tot=1,head[500005];
void add(int u,int v,int id){
	e[++tot]=(edge){v,id,head[u]},head[u]=tot;
}
int cur,dfn[500005],low[500005],B[1000005];
void tarjan(int u,int fa){
	dfn[u]=low[u]=++cur;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v,id=e[i].id;
		if(!dfn[v]){
			tarjan(v,i);low[u]=min(low[u],low[v]);
			if(low[v]>dfn[u])B[id]=1;
		}
		else if(i!=(fa^1))low[u]=min(low[u],dfn[v]);
	}
}
int num,bel[500005],cnt[500005],ec[500005];
void merge(int u,int col){
	if(bel[u])return;
	bel[u]=col,cnt[col]++;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;
		merge(v,col);
	}
}
int f[500005][2],s[500005],pw[1500005];
void dfs(int u,int fa){
	s[u]=ec[u],f[u][0]=pw[ec[u]],f[u][1]=(pw[cnt[u]]-1+mod)%mod*pw[ec[u]]%mod;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa)continue;
		dfs(v,u);s[u]+=s[v]+1;
		f[u][1]=((f[v][0]*2ll%mod+f[v][1])%mod*f[u][1]%mod+f[v][1]*f[u][0]%mod)%mod;
		f[u][0]=f[u][0]*f[v][0]%mod*2ll%mod;
	}
}
int n,m,ans,eu[1000005],ev[1000005];
signed main(){
	n=read(),m=read();
	pw[0]=1;for(int i=1;i<=n+m;i++)pw[i]=pw[i-1]*2ll%mod;
	for(int i=1;i<=m;i++)eu[i]=read(),ev[i]=read();
	for(int i=1;i<=m;i++)add(eu[i],ev[i],i),add(ev[i],eu[i],i);
	for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i,0);
	tot=0;for(int i=1;i<=n;i++)head[i]=0;
	for(int i=1;i<=m;i++)if(!B[i])add(eu[i],ev[i],i),add(ev[i],eu[i],i);
	for(int i=1;i<=n;i++)if(!bel[i])merge(i,++num);
	tot=0;for(int i=1;i<=n;i++)head[i]=0;
	for(int i=1;i<=m;i++){
		if(B[i])add(bel[eu[i]],bel[ev[i]],i),add(bel[ev[i]],bel[eu[i]],i);
		else ec[bel[eu[i]]]++;
	}
	dfs(1,0);ans=(ans+f[1][1])%mod;
	for(int i=2;i<=num;i++)ans=(ans+f[i][1]*pw[s[1]-1-s[i]]%mod)%mod;
	printf("%lld\n",ans);
	return 0;
}

J - 喵了个喵

K - 移球游戏

L - 方差

我会推式子!发现如果在 \(a\) 的差分数组 \(c_i=a_{i+1}-a_i\) 上观察修改 \(a_i\) 的影响,结果其实就是交换 \(c_i\)\(c_{i-1}\)。同时方差是有一个更好计算的形式的,即平方的平均数减平均数的平方,乘 \(n^2\) 后即 \(D=n\sum\limits_{i=1}^na_i^2-\left(\sum\limits_{i=1}^na_i\right)^2\)。因为方差是不关心具体值的,只关心相对大小,可以继续写成 \(D=n\sum\limits_{i=1}^n(a_i-a_1)^2-\left(\sum\limits_{i=1}^n(a_i-a_1)\right)^2\)。下面推推式子。

\[\begin{aligned} D&=n\sum\limits_{i=1}^n(a_i-a_1)^2-\left(\sum\limits_{i=1}^n(a_i-a_1)\right)^2\\ &=n\sum\limits_{i=1}^{n-1}\left(\sum\limits_{j=1}^ic_j\right)^2-\left(\sum\limits_{i=1}^{n-1}\sum\limits_{j=1}^ic_j\right)^2\\ &=n\sum\limits_{i=1}^{n-1}\sum\limits_{j=1}^i\sum\limits_{k=1}^ic_jc_k-\left(\sum\limits_{i=1}^{n-1}c_i(n-i)\right)^2\\ &=n\sum\limits_{j=1}^{n-1}\sum\limits_{k=1}^{n-1}c_jc_k(n-\max\{j,k\})-\sum\limits_{j=1}^{n-1}\sum\limits_{k=1}^{n-1}c_jc_k(n-j)(n-k)\\ &=\sum\limits_{j=1}^{n-1}\sum\limits_{k=1}^{n-1}c_jc_k(n(n-\max\{j,k\})-(n-j)(n-k))\\ &=\sum\limits_{j=1}^{n-1}\sum\limits_{k=1}^{n-1}c_jc_k(-n\max\{j,k\}+n(j+k)-jk)\\ &=\sum\limits_{j=1}^{n-1}\sum\limits_{k=1}^{n-1}c_jc_k(n\min\{j,k\}-jk)\\ &=\sum\limits_{j=1}^{n-1}c_j^2\cdot j(n-j)+2\sum\limits_{j=1}^{n-1}\sum\limits_{k=i+1}^{n-1}c_jc_k\cdot j(n-k)\\ \end{aligned} \]

根据这个式子,我们不难发现最终的差分序列应该是一个单谷的形式,因为越靠两边的系数越大。考虑 dp,定义 \(f_{i,j}\) 表示考虑前 \(i\) 小的 \(c\),目前 \(\sum a=j\) 的最小平方和。这样记是因为我们要用 \(D=n\sum\limits_{i=1}^n(a_i-a_1)^2-\left(\sum\limits_{i=1}^n(a_i-a_1)\right)^2\) 这个式子来算方差。转移即分成放在左边还是右边,记 \(s_i=\sum\limits_{j=1}^i c_j\),有转移式

\[f_{i-1,j}+2j\cdot c_i+i\cdot c_i^2\to f_{i,j+i\cdot c_i} \]

\[f_{i-1,j}+s_i^2\to f_{i,j+s_i} \]

滚掉第一维是 \(\mathcal{O}(nA)\) 的空间复杂度,可以通过。但是时间似乎是 \(\mathcal{O}(n^2A)\) 的,怎么办?我们发现 \(c_i=0\) 的位置是没有用的,不会产生不同位置间的转移,可以忽略。故时间复杂度是 \(\mathcal{O}(\min\{n,A\}nA)\) 的。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int a[10005],c[10005],s[10005],f[2][6000005];
signed main(){
	int n=read(),cnt=0;
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1;i<n;i++)c[i]=a[i+1]-a[i];
	sort(c+1,c+n);
	for(int i=1;i<n;i++)s[i]=s[i-1]+c[i],cnt+=(c[i]==0);
	for(int i=1;i<=a[n]*n;i++)f[cnt&1][i]=inf;
	for(int i=cnt+1;i<n;i++){
		for(int j=0;j<=a[n]*n;j++)f[i&1][j]=inf;
		for(int j=0;j<=a[n]*n;j++){
			if(f[(i&1)^1][j]==inf)continue;						
			f[i&1][j+i*c[i]]=min(f[i&1][j+i*c[i]],f[(i&1)^1][j]+j*2*c[i]+c[i]*c[i]*i);
			f[i&1][j+s[i]]=min(f[i&1][j+s[i]],f[(i&1)^1][j]+s[i]*s[i]);
		}
	}
	int ans=inf;
	for(int i=0;i<=a[n]*(n-1);i++){
		if(f[(n&1)^1ll][i]!=inf)ans=min(ans,n*f[(n&1)^1][i]-i*i);
	}
	printf("%lld",ans);
	return 0;
}

M - 微信步数

N - 密码锁

O - 比赛

posted @ 2023-09-20 11:20  xx019  阅读(42)  评论(1编辑  收藏  举报