[省选集训2022] 模拟赛7
A
题目描述
给定 \(n\) 个黑白球,排成一个序列。现在要把黑白两种颜色的球消除到只剩一个球,操作步骤是:选取一段长度为奇数的前缀,从后往前取出后三个球,然后根据规则将其变成一个球,循环这个过程直到只剩一个球,然后把它放在序列的最前端。
其中规则由一个长度为 \(8\) 的字符串给出,表示这三个球的颜色从后往前看成 \(01\) 串的状态压缩对应球的颜色,也就是从后往前代表从高位到低位。(这个颜色的名字是薇尔莉特,发现新色号 o(*≧▽≦)ツ)
问有多少种初始序列,使得存在方案可以通过消除获得 \(1\)
\(n\leq 10^5\)
解法
这道题不能像 Median Replace
一样直接贪心,因为规则是多样的。
考虑一个神奇的题意转化,既然操作是三元的我们把前两位看成函数,第三位看成自变量,那么整个就是 \(f(x)\) 的效果。那么考虑合并的意思就是 \(f(f(f(...\) 这样多重函数的嵌套,并且我们可以把它理解为一个新的函数 \(h(x)\)
由于函数只有 \(4\) 种:不变、交换、变 \(0\)、变 \(1\);所以我们可以对函数建出 \(\tt DFA\),\(4\times 4\) 的转移可以根据函数的意义写出。考虑判定性问题只需要记录可不可以凑出这个函数,也就是一个大小为 \(4\) 的状态。可以预先处理出规则对应着什么函数,然后一对一对地加入数来转移,就两种方法:直接合并函数,前三位带入求值之后和第四位组合成新函数。
那么需要计数怎么办呢?我们可以 \(dp\) 套 \(dp\),也就是我们记录一个 \(2^4\) 的状态来转移,表示哪些函数是可能被组合出来的。时间复杂度 \(O(n)\)
#include <cstdio>
#include <cstring>
const int M = 100005;
const int MOD = 998244353;
#define rep(i,a,b) for(int i=(a);i<(b);i++)
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int T,n,a[10],t[10][10],dp[10][20],f[20][10],ty[10];
char s[M];
void add(int &x,int y) {x=(x+y)%MOD;}
int mold(int x,int y)
{
if(x==0 && y==1) return 0;//do not change
if(x==1 && y==0) return 1;//swap them
if(x==0 && y==0) return 2;//all zero
return 3;//all one
}
int ask(int s,int x)//s::f(x)=?
{
if(s==0) return x;
if(s==1) return x^1;
if(s==2) return 0;
return 1;
}
void work()
{
memset(f,0,sizeof f);
memset(dp,0,sizeof dp);
for(int i=0;i<8;i++) scanf("%1d",&a[i]);
for(int i=0;i<4;i++) ty[i]=mold(a[i],a[i+4]);
scanf("%s",s+1),n=strlen(s+1);
rep(i,0,16) rep(k1,0,2) rep(k2,0,2)
{
int s=k1|(k2<<1);
rep(j,0,4) if(i>>j&1)//merge two function
f[i][s]|=(1<<t[j][ty[s]]);
rep(j,0,4) if(i>>j&1)//k1=x --> f(x)
f[i][s]|=(1<<ty[ask(j,k1)|(k2<<1)]);
}
dp[0][1]=1;int w=0,ans=0;
for(int i=1;i<n;i+=2)
{
w^=1;memset(dp[w],0,sizeof dp[w]);
rep(k1,0,2) if(s[i]!=(k1^1)+'0')
rep(k2,0,2) if(s[i+1]!=(k2^1)+'0')
rep(j,0,16)//transform on DFA
add(dp[w][f[j][k1|(k2<<1)]]
,dp[w^1][j]);
}
rep(i,0,2) if(s[n]!=(i^1)+'0')
rep(j,0,16)//calc the legal answer
{
int pd=0;
rep(k,0,4) if(j>>k&1)
pd|=ask(k,i);
add(ans,pd*dp[w][j]);
}
printf("%d\n",ans);
}
signed main()
{
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
T=read();//initialize
t[0][0]=0;t[0][1]=1;t[0][2]=2;t[0][3]=3;
t[1][0]=1;t[1][1]=0;t[1][2]=3;t[1][3]=2;
t[2][0]=t[2][1]=t[2][2]=t[2][3]=2;
t[3][0]=t[3][1]=t[3][2]=t[3][3]=3;
while(T--) work();
}
B
题目描述
\(n\) 个灯构成一棵有根树,定义一个灯的 \(a_i\) 表示 \(i\) 到子树中的最长链,设 \(b_i\) 表示这个灯是开\(/\)关。有 \(m\) 个操作,每次操作之后都需要输出开着灯 \(a_i\) 的异或值:
- 翻转路径 \((u,v)\) 灯开关的状态,然后更改根为 \(x\)
- 翻转子树内 \(u\) 灯开关的状态(注意此时的树根),然后更改根为 \(x\)
\(n\leq 2\cdot 10^5\)
解法
最长路径问题都可以向直径方向考虑,考虑以点 \(x\) 为根时 \(x\) 的最长链的端点,一定是直径的端点,并且最长链一定经过直径的中点。设 \(f_x\) 表示以 \(x\) 为根时 \(x\) 的最长链,\(g_x\) 表示次长链,那么以直径的中点 \(rt\) 建树,在 \(x\) 为根的时候只有 \(rt\) 到 \(x\) 可以取到 \(f_x\),其他点只能取到 \(g_x\)
我们可以维护每个点的开关状态(树剖板子),然后把链的值算上去即可,时间复杂度 \(O(n\log^2 n)\)
细节:直径中点不一定是点,可能是边。那么我们从边的两个端点开始染色即可,每个点都可以找到其对应的根。
#include <cstdio>
#include <vector>
#include <iostream>
#include <queue>
using namespace std;
const int M = 200005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
void write(int x)
{
if(x>=10) write(x/10);
putchar(x%10+'0');
}
int n,m,tot,Ind,f[M],dp[M][2],t[M<<2][4],fl[M<<2];
int fa[M],siz[M],dep[M],son[M],dfn[M],top[M],id[M];
int R[3],b[M],zx[M][20];
struct edge{int v,next;}e[M<<1];
int Abs(int x) {return x>0?x:-x;}
//initialize
void dfs1(int u,int p)
{
siz[u]=1;fa[u]=p;
dep[u]=dep[p]+1;zx[u][0]=p;
for(int i=1;i<20;i++)
zx[u][i]=zx[zx[u][i-1]][i-1];
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(v==p) continue;
dfs1(v,u);
siz[u]+=siz[v];
if(siz[son[u]]<siz[v]) son[u]=v;
}
}
void dfs2(int u,int tp)
{
dfn[u]=++Ind;top[u]=tp;id[Ind]=u;
if(son[u]) dfs2(son[u],tp);
for(int i=f[u];i;i=e[i].next)
if(e[i].v^fa[u] && e[i].v^son[u])
dfs2(e[i].v,e[i].v);
}
void dfs3(int u)
{
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(v==fa[u]) continue;
dfs3(v);
if(dp[u][0]<dp[v][0]+1)
dp[u][1]=dp[u][0],dp[u][0]=dp[v][0]+1;
else if(dp[u][1]<dp[v][0]+1)
dp[u][1]=dp[v][0]+1;
}
}
void dfs4(int u)
{
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(v==fa[u]) continue;
int t=(dp[u][0]==dp[v][0]+1)
?(dp[u][1]+1):(dp[u][0]+1);
if(dp[v][0]<t) dp[v][1]=dp[v][0],dp[v][0]=t;
else if(dp[v][1]<t) dp[v][1]=t;
dfs4(v);
}
}
//segment tree
void up(int i)
{
for(int j=0;j<4;j++)
t[i][j]=t[i<<1][j]^t[i<<1|1][j];
}
void flip(int i)
{
swap(t[i][0],t[i][1]);
swap(t[i][2],t[i][3]);fl[i]^=1;
}
void down(int i)
{
if(!fl[i]) return;
flip(i<<1);flip(i<<1|1);fl[i]=0;
}
void build(int i,int l,int r)
{
if(l==r)
{
t[i][0]=dp[id[l]][0];
t[i][2]=dp[id[l]][1];
return ;
}
int mid=(l+r)>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
up(i);
}
void work(int i,int l,int r,int L,int R)
{
if(L>r || l>R) return ;
if(L<=l && r<=R) {flip(i);return ;}
int mid=(l+r)>>1;down(i);
work(i<<1,l,mid,L,R);
work(i<<1|1,mid+1,r,L,R);
up(i);
}
int ask(int i,int l,int r,int L,int R)
{
if(L>r || l>R) return 0;
if(L<=l && r<=R) return t[i][0]^t[i][2];
int mid=(l+r)>>1;down(i);
return ask(i<<1,l,mid,L,R)^
ask(i<<1|1,mid+1,r,L,R);
}
//chain-division
void upd(int u,int v)
{
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
work(1,1,n,dfn[top[u]],dfn[u]);
u=fa[top[u]];
}
if(dep[u]<dep[v]) swap(u,v);
work(1,1,n,dfn[v],dfn[u]);
}
int get(int u,int v)
{
int res=0;
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
res^=ask(1,1,n,dfn[top[u]],dfn[u]);
u=fa[top[u]];
}
if(dep[u]<dep[v]) swap(u,v);
res^=ask(1,1,n,dfn[v],dfn[u]);
return res;
}
//bfs
void bfs()
{
queue<int> q;
b[R[1]]=R[1];b[R[2]]=R[2];
q.push(R[1]);q.push(R[2]);
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=f[u];i;i=e[i].next)
if(!b[e[i].v])
b[e[i].v]=b[u],q.push(e[i].v);
}
}
int jump(int x,int y)
{
for(int i=19;i>=0;i--)
if(y>>i&1) x=zx[x][i];
return x;
}
signed main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
n=read();m=read();
for(int i=1;i<n;i++)
{
int u=read(),v=read();
e[++tot]=edge{v,f[u]},f[u]=tot;
e[++tot]=edge{u,f[v]},f[v]=tot;
}
dfs1(1,0);dfs2(1,1);dfs3(1);dfs4(1);
build(1,1,n);int mx=0,rt=1;
for(int u=1;u<=n;u++)
mx=max(mx,dp[u][0]+dp[u][1]);
for(int u=1,c=0;u<=n;u++)
if(dp[u][0]+dp[u][1]==mx &&
Abs(dp[u][0]-dp[u][1])<=1) R[++c]=u;
bfs();
while(m--)
{
int op=read();
if(op==1)
upd(read(),read());
else
{
int u=read(),ur=dfn[u]+siz[u]-1;
if(u==rt) work(1,1,n,1,n);
else if(dfn[rt]<dfn[u] || dfn[rt]>ur)
work(1,1,n,dfn[u],ur);
else
{
int v=jump(rt,dep[rt]-dep[u]-1);
work(1,1,n,1,dfn[v]-1);
work(1,1,n,dfn[v]+siz[v],n);
}
}
rt=read();
write(t[1][2]^get(rt,b[rt])),puts("");
}
}
C
题目描述
给你一张 \(n\) 个点 \(m\) 条边的图,对于一个大小至少为 \(2\) 的点集 \(S\),定义 \(f(S)=\sum_{(u,v)\in E}\frac{[u,v\in S]}{|S|-1}\),请你判断是否存在 \(f(S)>lim\)
\(n\leq 300,m\leq 10^3,lim\leq 70\)
解法
在子任务的环境下模拟退火竟然跑了 \(55\) 分,我已经很满足了。
我们可以枚举一个点 \(x\) 强制选他,那么剩下的直接跑最大权闭合子图即可。
不想跑 \(n\) 遍网络流怎么办?在图变化的时候把待删去边的流量退掉即可。
#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
const int M = 10005;
const int inf = 0x3f3f3f3f;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,S,T,ans,tot,f[M],cur[M],d[M],F[M];
struct edge{int v,c,next;}e[M<<5];
void add(int u,int v,int c)
{
e[++tot]=edge{v,c,f[u]},f[u]=tot;
e[++tot]=edge{u,0,f[v]},f[v]=tot;
}
int bfs()
{
queue<int> q;q.push(S);d[S]=1;
for(int i=1;i<=T;i++) d[i]=0;
while(!q.empty())
{
int u=q.front();q.pop();
if(u==T) return 1;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(!d[v] && e[i].c>0)
{
d[v]=d[u]+1;
q.push(v);
}
}
}
return 0;
}
int dfs(int u,int ept)
{
if(u==T) return ept;
int tmp=0,flow=0;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(d[v]==d[u]+1 && e[i].c>0)
{
tmp=dfs(v,min(ept,e[i].c));
if(!tmp) continue;
ept-=tmp;flow+=tmp;
e[i].c-=tmp;e[i^1].c+=tmp;
if(!ept) break;
}
}
return flow;
}
signed main()
{
freopen("socialbutterfly.in","r",stdin);
freopen("socialbutterfly.out","w",stdout);
n=read();m=read();k=read();
S=0;T=n+m+1;tot=1;
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
add(S,i,1);
add(i,u+m,inf);
add(i,v+m,inf);
}
int tmp=tot;
for(int i=0;i<=T;i++) F[i]=f[i];
for(int b=1;b<=n;b++)
{
tot=tmp;ans=m;
for(int i=2;i<=tot;i+=2)
e[i].c+=e[i^1].c,e[i^1].c=0;
for(int i=0;i<=T;i++) f[i]=F[i];
for(int i=1;i<=n;i++)
if(i!=b) add(i+m,T,k);
while(bfs())
{
for(int i=0;i<=T;i++) cur[i]=f[i];
ans-=dfs(S,inf);
}
if(ans>0) {puts("Yes");return 0;}
}
puts("No");
}