【考试总结】2022-02-14
开心消消乐
问题主要出在了怎么判断没有问号的序列是否合法,更关键的问题是场上读错题了,一个前缀是从后往前逐三个删,得到的 \(0/1\) 也往 末尾 放
没有被操作的数字形成了个大小为偶数的栈,我们两个一组向其中加数字,初始状态栈为空,加入 \(s_{1},s_{2}\);最后加入 \(s_{n}\) 判断是不是存在一个栈使得再操作整个栈能得到 \(1\)
向栈中加数分两种情况,设当前加入 \((a,b)\):
-
加入 \(a\),将整个栈操作变成一个 \(0/1\),加入 \(b\)
-
加入 \(a,b\),不对栈进行额外操作
注意到这里的栈有 \(4\) 中表达方式:加入 \(0/1\) 后操作,结果为 \(0/1\)。那么据此设 \(f_{i,a,b}\) 表示用前 \(i\) 个数经过若干(可以为 \(0\))次操作,得到的栈加入 \(0\) 后操作结果为 \(a\),加入 \(1\) 后操作结果为 \(b\) 是否可行
转移直接根据上面两种情况加入 \(s_{i+1},s_{i+2}\) 并模拟得到新的 \(f_{i+2,a',b'}\) 即可,复杂度是 \(\Theta(n)\)
剩下的工作是一个平凡的 \(\rm DP\) 套 \(\rm DP\),就是将状态扩展到 \(f_{i,a,b,c,d}\),其中 \(a,b,c,d\) 分别表示当前填入的串是否满足加入 \(0/1\) 能得到 \(0/1\),可行则 \(a/b/c/d\) 为 \(1\)
转移可以枚举 \((a/b,c/d)\) 中的全 \(1\) 对和 \(s_{i+1},s_{i+2}\) 合并得到新的 \({a',b',c',d'}\),这里注意 \(a,b/c,d\) 是可以同时为 \(1\),因为这是表示通过所有可能操作是不是能得到该种栈
可以先 \(2^6\) 预处理每种 \((a,b,c,d),(s_{i+1},s_{i+2})\) 能转移给什么样的 \((a',b',c',d')\)
复杂度是 \(\Theta(2^{2^{\Sigma}}n)\),本题中 \(\Sigma=|\{0,1\}|=2\)
Code Display
const int N=1e5+10;
int n,dp[N][2][2][2][2];
char opt[10],s[N];
tuple trans[2][2][2][2][2][2];
inline int calc(int a,int b,int c){return opt[a+b*2+c*4]-'0';}
inline pair get1(int a,int b,int x,int y){
int s0=calc(x?b:a,y,0);
int s1=calc(x?b:a,y,1);
return make_pair(s0,s1);
}
inline pair get2(int a,int b,int x,int y){
int s0=calc(x,y,0)?b:a;
int s1=calc(x,y,1)?b:a;
return make_pair(s0,s1);
}
inline void upd(int &a,int &b,int &c,int &d,pair now){
if(now.fir) b=1; else a=1;
if(now.sec) d=1; else c=1;
return ;
}
signed main(){
freopen("game.in","r",stdin); freopen("game.out","w",stdout);
int T=read();
while(T--){
scanf("%s%s",opt,s+1);
rep(a,0,1) rep(b,0,1) if(a||b) rep(c,0,1) rep(d,0,1) if(c||d){
rep(c1,0,1) rep(c2,0,1){
int na=0,nb=0,nc=0,nd=0;
if(a&&c){
upd(na,nb,nc,nd,get1(0,0,c1,c2));
upd(na,nb,nc,nd,get2(0,0,c1,c2));
}
if(a&&d){
upd(na,nb,nc,nd,get1(0,1,c1,c2));
upd(na,nb,nc,nd,get2(0,1,c1,c2));
}
if(b&&c){
upd(na,nb,nc,nd,get1(1,0,c1,c2));
upd(na,nb,nc,nd,get2(1,0,c1,c2));
}
if(b&&d){
upd(na,nb,nc,nd,get1(1,1,c1,c2));
upd(na,nb,nc,nd,get2(1,1,c1,c2));
}
trans[a][b][c][d][c1][c2]=tie(na,nb,nc,nd);
}
}
n=strlen(s+1);
rep(i,1,n) rep(a,0,1) rep(b,0,1) rep(c,0,1) rep(d,0,1) dp[i][a][b][c][d]=0;
dp[0][1][0][0][1]=1;
for(int i=1;i
树上的棋局
题目中每个点 \(\rm SG\) 值就是该点到其自己子树里面所有点的最长路径长度,全局 \(\rm SG\) 值就是每个石子的 \(\rm SG\) 值异或起来
直接思考得到的瓶颈就是每个点每个儿子子树内点作为根时最长路径可能不一样
这个瓶颈可以通过找“直径中点”来突破!
我们令直径中点为全局根(如果直径长度为偶数那么随便找一个中点均可),此时若每个点在其子树内点为此时的根,其对应的长度就是它在树上的深度加一半直径,否则就是子树内最长路径
偶数长度直径对应的树根稍特殊,如果当前根在直径另一个中点的子树里面那么最长链是 \(\frac{len}2-1\),否则是 \(\frac{len}2\)
对以直径中点为根的线段树进行树链剖分,子树加/链加都是 “区间异或自己” 的模型,使用两个线段树分别维护向下的链长和向上的链长的异或即可
这里有个比较简洁的处理方式就是线段树上每个区间维护 \(val_{0,1}\) 区间异或自己的时候两者交换,上传的时候两者更新为小区间对应的异或和
剩下的细节比较基础,不再赘述
时间复杂度 \(\Theta(n\log ^2n)\)
Code Display
vector G[N];
int top[N],dfn[N],siz[N],tim,dep[N],son[N],fa[N],ord[N],n,Q;
int edp=0,rt,Mx[N],sec[N],up[N];
inline void get_edp(int x,int fat){
dep[x]=dep[fa[x]=fat]+1; if(dep[x]>dep[edp]) edp=x;
for(auto t:G[x]) if(t!=fat) get_edp(t,x);
return ;
}
inline void dfs1(int x,int fat){
dep[x]=dep[fa[x]=fat]+(siz[x]=1);
for(auto t:G[x]) if(t!=fat){
dfs1(t,x); siz[x]+=siz[t];
if(siz[son[x]]=Mx[x]) sec[x]=Mx[x],Mx[x]=Mx[t]+1;
else ckmax(sec[x],Mx[t]+1);
}
for(auto t:G[x]) if(t!=fat){
if(Mx[t]+1==Mx[x]) ckmax(up[t],sec[x]+1);
else ckmax(up[t],Mx[x]+1);
}
return ;
}
inline void dfs2(int x,int topf){
ord[dfn[x]=++tim]=x; top[x]=topf; if(son[x]) dfs2(son[x],topf);
for(auto t:G[x]) if(!dfn[t]) dfs2(t,t);
return ;
}
int len[N];
struct SEG{
int val[N<<2][2],rev[N<<2];
inline void push_up(int p){
val[p][0]=val[ls][0]^val[rs][0];
val[p][1]=val[ls][1]^val[rs][1];
return ;
}
inline void push_rev(int p){
rev[p]^=1;
swap(val[p][0],val[p][1]);
return ;
}
inline void push_down(int p){
if(rev[p]){
push_rev(ls); push_rev(rs);
rev[p]=0;
}return ;
}
inline void build(int p,int l,int r){
if(l==r) return val[p][1]=len[ord[l]],void();
int mid=(l+r)>>1; build(ls,l,mid); build(rs,mid+1,r);
return push_up(p);
}
inline void flip(int p,int l,int r,int st,int ed){
if(st<=l&&r<=ed) return push_rev(p);
int mid=(l+r)>>1; push_down(p);
if(st<=mid) flip(ls,l,mid,st,ed);
if(ed>mid) flip(rs,mid+1,r,st,ed);
return push_up(p);
}
inline int Query(int p,int l,int r,int st,int ed){
if(st<=l&&r<=ed) return val[p][1];
int mid=(l+r)>>1,res=0; push_down(p);
if(st<=mid) res^=Query(ls,l,mid,st,ed);
if(ed>mid) res^=Query(rs,mid+1,r,st,ed);
return res;
}
}T1,T2;
inline void re_dp(int x,int fat){
for(auto t:G[x]) if(t!=fat){
ckmax(up[t],up[x]+1);
re_dp(t,x);
} return ;
}
inline int get_son(int x,int y){
while(top[x]!=top[y]){
if(fa[top[x]]==y) return top[x];
x=fa[top[x]];
} return son[y];
}
int root=1;
inline void flip(int l,int r){
T1.flip(1,1,n,l,r);
T2.flip(1,1,n,l,r);
return ;
}
int Mark,dialen;
inline int Query(){
int tmp=root,ans=T1.val[1][1];
while(tmp){
ans^=T1.Query(1,1,n,dfn[top[tmp]],dfn[tmp])^T2.Query(1,1,n,dfn[top[tmp]],dfn[tmp]);
tmp=fa[top[tmp]];
}
int l=1,r=n,p=1;
while(l!=r){
T1.push_down(p);
p<<=1; r=(l+r)>>1;
}
if(!T1.rev[p]){
if((dialen&1)||rt==root) ans^=dialen>>1;
else{
int sideson=get_son(root,rt);
if(sideson==Mark) ans^=dialen/2-1;
else ans^=dialen/2;
}
}
return ans;
}
signed main(){
freopen("tree.in","r",stdin); freopen("tree.out","w",stdout);
n=read(); Q=read();
for(int i=1,u,v;i nodes;
while(edp){
nodes.push_back(edp);
edp=fa[edp];
}
rt=nodes[nodes.size()>>1];
dialen=nodes.size();
Mark=nodes.size()&1?-1:nodes[nodes.size()/2-1];
memset(sec,-1,sizeof(sec));
dfs1(rt,0); dfs2(rt,rt);
rep(i,1,n) len[i]=Mx[i]; T1.build(1,1,n);
re_dp(rt,0);
rep(i,1,n) len[i]=up[i]; T2.build(1,1,n);
while(Q--){
if(read()-1){
int u=read();
if(u==root){
flip(1,n);
}else{
if(dfn[u]>dfn[root]+siz[root]-1||dfn[u]+siz[u]-1=dfn[u]+siz[u])){
flip(dfn[u],dfn[u]+siz[u]-1);
}else{
int sideson=get_son(root,u);
flip(1,n);
flip(dfn[sideson],dfn[sideson]+siz[sideson]-1);
}
}
}else{
int u=read(),v=read();
while(top[u]!=top[v]){
if(dep[top[u]]>dep[top[v]]) swap(u,v);
flip(dfn[top[v]],dfn[v]);
v=fa[top[v]];
}
if(dep[u]>dep[v]) swap(u,v);
flip(dfn[u],dfn[v]);
}
root=read();
print(Query());
}
return 0;
}
社会黄油飞
不难根据题目含义想到最大权闭合子图,选择一条边的子图是两个点,点权是 \(-\lim\)
观察题目中所给出的表达式:分母是集合大小 \(-1\) 而非集合大小本身
那么枚举一个额外的点表示强制这个点已经被选到集合中,将这个点和汇点的边从图中删去表示已经付出 \(-\lim\) 的代价
此时表达式为 \(\Sigma E-|S|\lim>0\) 是标准的最大权闭合子图表达式,暴力做即可通过本题
注意不能删掉和其在原图上连的边,只是选择某条和强制被选的点在原图中相连的边的子图变成了只有另一个端点
这时复杂度是不正确的,一个比较明显的冗余是每次重新增广全图,这是不优的,那么强制选 \(i\) 时将网络流图上和它相连的边流量都设置成 \(0\) 就行了
Code Display
const int N=1e4+10,inf=0x3f3f3f3f;
int dep[N],head[N],cnt=1,n,m,lim;
struct edge{int to,nxt,lim;}e[N<<3];
inline void adde(int u,int v,int w){
e[++cnt]={v,head[u],w}; head[u]=cnt;
return ;
}
inline void add(int u,int v,int w){return adde(u,v,w),adde(v,u,0);}
queue q;
int S,T,cur[N];
inline bool bfs(){
rep(i,1,T) dep[i]=0,cur[i]=head[i]; q.push(S); dep[S]=1;
while(q.size()){
int fr=q.front(); q.pop();
for(int i=head[fr];i;i=e[i].nxt) if(e[i].lim){
int t=e[i].to; if(dep[t]) continue;
dep[t]=dep[fr]+1; q.push(t);
}
}
return dep[T];
}
inline int dfs(int x,int in){
if(x==T) return in; int out=0;
for(int i=cur[x];i;cur[x]=i,i=e[i].nxt) if(e[i].lim){
int t=e[i].to; if(dep[t]!=dep[x]+1) continue;
int res=dfs(t,min(e[i].lim,in));
in-=res; out+=res; e[i].lim-=res; e[i^1].lim+=res;
if(!in) break;
} if(!out) dep[x]=0;
return out;
}
int u[N],v[N];
signed main(){
freopen("socialbutterfly.in","r",stdin); freopen("socialbutterfly.out","w",stdout);
n=read(); m=read(); lim=read();
S=n+m+1; T=S+1;
for(int i=1;i<=m;++i){
u[i]=read(),v[i]=read();
add(S,i+n,1);
add(i+n,u[i],inf); add(i+n,v[i],inf);
}
rep(i,1,n) add(i,T,lim);
int flow=0;
while(bfs()) flow+=dfs(S,inf);
rep(x,1,n){
for(int i=head[T];i;i=e[i].nxt){
if(e[i].to==x-1) e[i].lim=0,e[i^1].lim=lim;
if(e[i].to==x) e[i].lim=e[i^1].lim=0;
}
for(int i=head[x];i;i=e[i].nxt){
int t=e[i].to; if(t==T) continue;
if(e[i].lim){
e[i].lim=0; e[i^1].lim=inf;
for(int j=head[t];j;j=e[j].nxt){
if(e[j].to==S){
e[j].lim=0; e[j^1].lim=1;
break;
}
}
flow--;
}
}
while(bfs()) flow+=dfs(S,inf);
if(m>flow) puts("Yes"),exit(0);
} puts("No");
return 0;
}