CWOI NOIP 真题训练专题
链接:link
希望能苟到这些题发挥用处的时候。
A - 排水系统
topsort。
B - 报数
埃筛。
C - 种花
模拟。
D - 涂色游戏
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 - 幂次
G - 数列
H - 圣诞树
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\)。下面推推式子。
根据这个式子,我们不难发现最终的差分序列应该是一个单谷的形式,因为越靠两边的系数越大。考虑 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\),有转移式
滚掉第一维是 \(\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;
}