[省选集训2022] 模拟赛7
A
题目描述
给定 \(n\) 个黑白球,排成一个序列。现在要把黑白两种颜色的球消除到只剩一个球,操作步骤是:选取一段长度为奇数的前缀,从后往前取出后三个球,然后根据规则将其变成一个球,循环这个过程直到只剩一个球,然后把它放在序列的最前端。
其中规则由一个长度为 \(8\) 的字符串给出,表示这三个球的颜色从后往前看成 \(01\) 串的状态压缩对应球的颜色,也就是从后往前代表从高位到低位。(这个颜色的名字是薇尔莉特,发现新色号 o(*≧▽≦)ツ)
\(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
#include
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<>j&1)//k1=x --> f(x)
f[i][s]|=(1<>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
#include
#include
#include
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]]>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]] 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;iur)
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
#include
#include
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 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");
}