• [省选集训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");
    }
    
  • 相关阅读:
    在Ubuntu18.04.2LTS上安装搜狗输入法
    生活点滴:java基础知识细化
    乘风破浪:LeetCode真题_041_First Missing Positive
    乘风破浪:LeetCode真题_040_Combination Sum II
    乘风破浪:LeetCode真题_039_Combination Sum
    乘风破浪:LeetCode真题_038_Count and Say
    乘风破浪:LeetCode真题_037_Sudoku Solver
    乘风破浪:LeetCode真题_036_Valid Sudoku
    乘风破浪:LeetCode真题_035_Search Insert Position
    乘风破浪:LeetCode真题_034_Find First and Last Position of Element in Sorted Array
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15895541.html
Copyright © 2020-2023  润新知