• 【CodeForces】CodeForcesRound594 Div1 解题报告


    点此进入比赛

    (A):Ivan the Fool and the Probability Theory(点此看题面

    大致题意: 给一个(n imes m)的矩阵(01)染色,使得不存在某个同色连通块大小超过(2)

    这道题看似很神仙,实际上仔细想一想、推一推性质,还是比较简单的。

    先考虑第一行,这是一个(1 imes m)的矩阵,可以设(f_{i,0/1})为第(i)个格子所填与上个格子不同/相同的方案数,则:(f_{i,0}=f_{i-1,0}+f_{i-1,1},f_{i,1}=f_{i-1,0})

    由于第一个位置可填(0)可填(1),所以初始化(f_{1,0}=2,f_{1,1}=0)

    接下来,我们考虑从第一行向下扩展,分两种情况讨论:

    • 如果第一行不存在连续两个格子所填相同,则每一行填的数只能与上一行完全相反或是完全相同,且不能有连续超过(2)行完全相同。此时第一行只有(0101...01)(1010...10)两种情况,而若我们把(0101...01)看作(0)(1010...10)看作(1),那么就相当于对(n imes 1)的矩阵(01)染色,所以方案数为(f_{n,0}+f_{n,1})
    • 如果第一行存在连续的两个格子所填相同,则每一行填的数只能与上一行完全相反。此时第一行有(f_{m,0}+f_{m,1}-2)种填法(除去第一行不存在连续两个格子所填相同的两种填法),每种第一行填法对应唯一一种全局填法,所以方案数为(f_{m,0}+f_{m,1}-2)

    综上所述,总方案数为(f_{n,0}+f_{n,1}+f_{m,0}+f_{m,1}-2)

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 100000
    #define X 1000000007
    using namespace std;
    int n,m,f[N+5][2];
    I int Qpow(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
    int main()
    {
    	RI i;scanf("%d%d",&n,&m);
    	for(f[1][0]=2,i=2;i<=max(n,m);++i) f[i][0]=(f[i-1][0]+f[i-1][1])%X,f[i][1]=f[i-1][0];
    	return printf("%d",(1LL*f[n][0]+f[n][1]+f[m][0]+f[m][1]-2+X)%X),0;
    }
    

    (B):The World Is Just a Programming Task (Hard Version)(点此看题面

    大致题意: 定义一个括号序列(S)的美丽值为有多少个(p)使能得(S_{p,n}S_{1,p-1})为合法括号序列。现在你可以交换一个括号序列中两个括号,求所能得到的最大的美丽值及交换的位置。

    细节巨多的题目,写了一个下午......

    首先,如果(S)的左右括号数量不同,直接输出(0)(1 1)走人。

    然后,我们考虑,对于一个括号序列,它的美丽值的意义。然后不难推导出,如果我们把(看作(1))看作(-1),求出前缀和(s)数组后,美丽值就是(s)中的最小值个数

    假设我们交换括号(x,y(x<y))(s)中原本的最小值为(Mn)

    我的做法是,先对于(x)是左括号还是右括号进行讨论,然后再分别继续处理。

    • 对于(x)是左括号的情况。此时,就相当于(s_{xsim y-1})减去(2)
      • 如果(s_{xsim y-1})中的最小值是(Mn),则操作后(s)中的最小值是(Mn-2)。显然答案不会更优。
      • 如果(s_{xsim y-1})中的最小值是(Mn+1),则操作后(s)中的最小值是(Mn-1)。答案就是(s_{xsim y-1})(Mn+1)的个数。
      • 如果(s_{xsim y-1})中的最小值是(Mn+2),则操作后(s)中的最小值依然是(Mn-2)。答案就是(s_{xsim y-1})(Mn+2)的个数加上整个(s)(Mn)的个数。
    • 对于(x)是右括号的情况。此时,就相当于(s_{xsim y-1})加上(2),也就是(s_{1sim x-1},s_{ysim n})减去(2),随后的讨论与上面类似。

    通过上面的讨论,不难发现,在最小值不变的情况下,变化区间肯定是范围越大越好,因此我们用两个双指针来维护出最小值为(Mn+1,Mn+2)的两个答案区间即可。

    具体实现可能有些细节要注意,可详见代码。

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con cosnt
    #define CI Con int&
    #define I inline
    #define W while
    #define N 300000
    using namespace std;
    int n,a[N+5],p[N+5];char s[N+5];
    int main()
    {
    	RI i,Mn=n,res=0,ans=0,tx=1,ty=1;for(scanf("%d%s",&n,s+1),i=1;i<=n;++i)//读入+转化
    		a[i]=s[i]=='('?1:-1,p[i]=p[i-1]+a[i],Mn>p[i]?(Mn=p[i],res=1):Mn==p[i]&&++res;
    	if(p[n]) return puts("0
     1 1"),0;ans=res;RI t=0,g=0;//如果左右括号数量不同,直接输出
    	RI nxt1=n,nxt2=n;for(i=n-1;i;--i)//对于左边是左括号
    		p[i]==Mn?(t=0,nxt1=i):(t+=p[i]==Mn+1),//维护最小值是Mn+1的答案区间
    		(p[i]==Mn||p[i]==Mn+1)?(g=0,nxt2=i):(g+=p[i]==Mn+2),//维护最小值是Mn+2的答案区间
    		~a[i]&&(ans<t&&(ans=t,tx=i,ty=nxt1),ans<res+g&&(ans=res+g,tx=i,ty=nxt2));//更新答案
    	RI v=0,w=0,k=0;for(t=g=0,i=1;i<=n;++i) p[i]==Mn+1&&++t,p[i]==Mn+2&&++g;//初始化
    	RI lim=t;for(nxt1=nxt2=i=1;i<=n;++i)//对于左边是右括号
    	{
    		W(v^res) p[nxt1]==Mn&&++v,p[nxt1]==Mn+1&&--t,nxt1=nxt1%n+1;//维护最小值是Mn+1的答案区间
    		W(w^lim||k^res) p[nxt2]==Mn+1&&++w,p[nxt2]==Mn&&++k,p[nxt2]==Mn+2&&--g,nxt2=nxt2%n+1;//维护最小值是Mn+2的答案区间
    		!~a[i]&&ans<t&&(ans=t,tx=i,ty=nxt1),p[i]==Mn&&--v,p[i]==Mn+1&&++t,//更新最小值是Mn+1的答案,然后更新区间
    		!~a[i]&&ans<res+g&&(ans=res+g,tx=i,ty=nxt2),p[i]==Mn+1&&--w,p[i]==Mn&&--k,p[i]==Mn+2&&++g;//更新最小值是Mn+2的答案,然后更新区间
    	}
    	return printf("%d
    %d %d",ans,tx,ty),0;//输出答案
    }
    

    (C):Queue in the Train(点此看题面

    大致题意:(n)个人要灌水,每个人灌水用时皆为一个定值。每个人会在第(p_i)刻开始想要灌水,如果大于等于(p_i)的某一时刻他前方没有人在排队,他就会去排队。求每个人灌完水的时间。

    模拟题,一开始理解错几个细节就挂飞了......

    其实这道题说简单也很简单,个人认为我的做法可能有点复杂......

    首先,我们把人以开始灌水时间为第一关键字、编号为第二关键字,排序,并用一个指针(i)来表示排序后的前(i)个人当前能够灌水。

    然后,我们开一个小根堆(优先队列),把当前能够灌水的人都扔到堆里。

    扔的同时,我们要判断这个人是否能去排队了,这只需要判断他之前是否有人去排队了,因此我写了一个树状数组。

    如果能排队,我们就把它扔到一个队列里,果然是排队。

    还有一些细节自己注意一下吧,下面放出代码以供参考。

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 100000
    #define LL long long
    using namespace std;
    int n,m,a[N+5];LL ans[N+5];queue<int> q;
    struct data
    {
    	int p,v;I data(CI x=0,CI y=0):p(x),v(y){}
    	I bool operator < (Con data& o) Con {return v^o.v?v<o.v:p<o.p;}
    }s[N+5];priority_queue<int,vector<int>,greater<int> > p;
    class FastIO
    {
    	private:
    		#define FS 100000
    		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
    		#define pc(c) (C==E&&(clear(),0),*C++=c)
    		#define tn (x<<3)+(x<<1)
    		#define D isdigit(c=tc())
    		int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
    	public:
    		I FastIO() {A=B=FI,C=FO,E=FO+FS;}
    		Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
    		Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
    		Tp I void write(Con Ty& x,Con char& y) {write(x),pc(y);}
    		I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
    }F;
    class TreeArray//树状数组
    {
    	private:
    		int v[N+5];
    	public:
    		I void Add(RI x,CI y) {W(x<=n) v[x]+=y,x+=x&-x;}//后缀修改
    		I int Qry(RI x,RI t=0) {W(x) t+=v[x],x-=x&-x;return t;}//单点查询
    }T;
    int main()
    {
    	RI i,f;LL t=0;for(F.read(n),F.read(m),i=1;i<=n;++i) F.read(a[i]),s[i]=data(i,a[i]);
    	sort(s+1,s+n+1),i=1;W(i<=n||!p.empty()||!q.empty())
    	{
    		!(f=q.empty())&&(ans[q.front()]=(t+=m)),i<=n&&p.empty()&&q.empty()&&(t=s[i].v);//计算当前时间
    		W(!p.empty()&&!T.Qry(p.top())) q.push(p.top()),T.Add(p.top()+1,1),p.pop();//处理原有能够灌水的人
    		W(i<=n&&s[i].v<=t) p.push(s[i++].p),!T.Qry(p.top())&&(q.push(p.top()),T.Add(p.top()+1,1),p.pop(),0);//处理新增能够灌水的人,同时判断是否能去排队
    		!f&&(T.Add(q.front()+1,-1),q.pop(),0);//当前正在灌水的人已经灌完
    	}
    	for(i=1;i<=n;++i) F.write(ans[i]," 
    "[i==n]);return F.clear(),0;//输出答案
    }
    

    (D):Catowice City(点此看题面

    大致题意:(n)个人,每人有一只猫。每个人都认识若干猫(一定认识自己的猫),现在要选出人和猫共(n)个,使得至少有一个人和一只猫,且每个人都不认识任何一只猫。

    首先,我们可以发现,因为一个人一定认识自己的猫,所以一个数不可能被选两次。而题目要求选出(n)个数,就必然是把(1sim n)分成两部分。

    则,我们考虑如果选择了编号为(i)的人,那么他认识的所有的猫的主人都必须被选择,而他认识的猫的主人认识的猫的主人同样也都必须被选择,以此类推。

    如果我们把编号为(i)的人认识编号为(j)的猫看作一条有向边,那么选择一个人,就相当于选择了从这个点出发能够到达的所有点。

    因此,我们用(Tarjan)将图缩点,显然,如果原图只有一个强连通分量,就会输出(No),否则必然输出(Yes)

    考虑如果我们选择一个强连通分量中的点作为人,如果这个强连通分量出度为(0),是肯定合法的。而根据(Tarjan)的原理可知,(Tarjan)过程中得到的第一个强连通分量,出度是必然为(0)的。

    因此我们只要把编号为(1)的强连通分量中的点作为人,剩余点作为猫,即可满足题目要求了。

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 1000000
    #define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
    #define Gmin(x,y) (x>(y)&&(x=(y)))
    using namespace std;
    int n,m,ee,d,cnt,T,lnk[N+5],dfn[N+5],low[N+5],col[N+5],vis[N+5],S[N+5],Sz[N+5];
    struct edge {int to,nxt;}e[N+5];
    class FastIO
    {
    	private:
    		#define FS 100000
    		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
    		#define pc(c) (C==E&&(clear(),0),*C++=c)
    		#define tn (x<<3)+(x<<1)
    		#define D isdigit(c=tc())
    		int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
    	public:
    		I FastIO() {A=B=FI,C=FO,E=FO+FS;}
    		Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
    		Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
    		Tp I void write(Con Ty& x,Con char& y) {write(x),pc(y);}
    		I void writes(Con string& x) {for(RI i=0,y=x.length();i^y;++i) pc(x[i]);}
    		I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
    }F;
    I void Tarjan(CI x,CI lst)//Tarjan缩点
    {
    	dfn[x]=low[x]=++d,vis[S[++T]=x]=1;for(RI i=lnk[x];i;i=e[i].nxt) dfn[e[i].to]?
    		vis[e[i].to]&&Gmin(low[x],dfn[e[i].to]):(Tarjan(e[i].to,x),Gmin(low[x],low[e[i].to]));
    	if(dfn[x]^low[x]) return;Sz[col[x]=++cnt]=1,vis[x]=0;
    	W(S[T]^x) ++Sz[col[S[T]]=cnt],vis[S[T--]]=0;--T;
    }
    int main()
    {
    	RI Tt,i,j,x,y;F.read(Tt);W(Tt--)
    	{
    		for(F.read(n),F.read(m),ee=d=cnt=0,i=1;i<=n;++i) lnk[i]=dfn[i]=vis[i]=0;//清空
    		for(i=1;i<=m;++i) F.read(x),F.read(y),x^y&&add(x,y);//连边
    		for(i=1;i<=n;++i) !dfn[i]&&(Tarjan(i,0),0);//Tarjan缩点
    		if(cnt==1) {F.writes("No
    ");continue;}F.writes("Yes
    ");//判断是否有解
    		F.write(Sz[1],' '),F.write(n-Sz[1],'
    ');//分别输出人与猫的个数
    		for(i=1;i<=n;++i) col[i]==1&&(F.write(i,' '),0);F.writes("
    ");//输出强连通分量内的点,作为人
    		for(i=1;i<=n;++i) col[i]^1&&(F.write(i,' '),0);F.writes("
    ");//输出剩余的点,作为猫
    	}return F.clear(),0;
    }
    

    (E):Turtle(点此看题面

    大致题意: 让你把(2n)个元素放到一个(2 imes n)的矩阵中,使得从左上角走到右下角(只能往下或往右)所经元素总和的最大值最小。

    因为只能往下或往右走,所以我们可以找到一个关键点(p),使得在(p)点之前在第一行走,(p)点之后在第二行走,(p)点从第一行到第二行。

    设所经元素总和为(f(p)=sum_{i=1}^pa_{1,i}+sum_{i=p}^na_{2,i})

    根据贪心,显而易见,第一排的数应当递增摆放,第二排的数应当递减摆放。

    考虑将(p)(1)移动到(n),则每次移动,元素总和发生的变化其实都是加上(a_{1,p+1}-a_{2,p})。由于第一排递增,第二排递减,则(a_{1,p+1}-a_{2,p})也是递增的。

    假设当(p=x)时满足使(f(p))最大的同时(x)最大,则我们可以得到结论:(x=1)(x=n)

    (x≠1)(x≠n)时,(ecause f(x)=f(x-1)+a_{1,x}-a_{2,x-1},f(x)ge f(x-1), herefore a_{1,x}-a_{2,x-1}ge0.)

    (ecause a_{1,x+1}-a_{2,x}ge a_{1,x}-a_{2,x-1}, herefore a_{1,x+1}-a_{2,x}ge 0.)

    ( herefore f(x+1)=f(x)+a_{1,x+1}-a_{2,x}ge f(x).)

    这与(p=x)时满足使(f(p))最大的同时(x)最大矛盾,( herefore x=1)(x=n.)

    也就是说,最大值应该是(max(f(1),f(n))),即(max(a_{1,1}+sum_{i=1}^na_{2,i},sum_{i=1}^na_{1,i}+a_{2,n}))

    如果我们同时从两个式子中拿出(a_{1,1})(a_{2,n}),就得到:(a_{1,1}+a_{2,n}+max(sum_{i=1}^{n-1}a_{2,i},sum_{i=2}^na_{1,i}))

    由于(a_{1,1})(a_{2,n})无论如何都会被取到,显然它们两个应该分别填上最小值和次小值。

    那么,题目也就变成了,将剩余的(2n-2)个数分成两个大小为(n-1)的集合,使得两个集合内数总和的较大值最小。

    所以,我们可以先通过背包来求出这个答案。

    (f_{i,j,k})表示是否能在前(i)个数中选择(j)个数使得它们和为(k),第一维根据背包问题的常用技巧,可以在数组中去掉,变成(f_{j,k})。然后由于只需知道是否可行,因此我们可以用(bitset)优化。

    最后我们枚举(k),然后找到一个(f_{n-1,k}=1)(max(k,s-k))最小的(k)(s)为这(2n-2)个数的和)。

    然后,我们考虑题目要求输出一个合法方案。

    则我们用(Meet in middle),搜索过程中记录(t,v,s)分别表示选择数的个数、选择数的和以及选择数状压后的状态。

    这样一来,这道题就算做完了。

    说实话,其实最后的实现是很简单的,难点主要是前面的部分。

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 25
    #define V 50000
    #define LL long long
    #define max(x,y) ((x)>(y)?(x):(y))
    #define min(x,y) ((x)<(y)?(x):(y))
    #define Gmin(x,y) (x>(y)&&(x=(y)))
    using namespace std;
    int n,a[2*N+5];
    class DpSolver//背包
    {
    	private:
    		bitset<2*N*V+5> f[2*N+5];
    	public:
    		I int GetAns()
    		{
    			RI i,j,s=0,ans=s;for(i=3;i<=2*n;++i) s+=a[i];//记录数的和
    			for(f[0].set(0),i=3;i<=2*n;++i) for(j=min(n-1,i-2);j;--j) f[j]|=f[j-1]<<a[i];//背包
    			for(i=0;i<=s;++i) f[n-1].test(i)&&max(ans,s-ans)>max(i,s-i)&&(ans=i);//统计答案
    			return min(ans,s-ans);//返回
    		}
    }DP;
    class DfsSolver//Meet in middle
    {
    	private:
    		#define pb push_back
    		LL w[N+5][N*V+5];
    		I void dfs1(CI p,CI x,CI y,CI t,CI v,Con LL& s)//前一半dfs
    		{
    			if(x>y) return (void)(!~w[t][v]&&(w[t][v]=s));//记录答案
    			dfs1(p,x+1,y,t,v,s),v+a[x]<=p&&(dfs1(p,x+1,y,t+1,v+a[x],s|(1LL<<x)),0);return;
    		}
    		I void dfs2(CI p,CI x,CI y,CI t,CI v,Con LL& s)//后一半dfs
    		{
    			if(x>y)
    			{
    				if(!~w[n-1-t][p-v]) return;RI i;LL g=w[n-1-t][p-v]|s;
    				printf("%d ",a[1]);for(i=3;i<=2*n;++i) g>>i&1&&printf("%d ",a[i]);putchar('
    ');//第一行,升序输出
    				for(i=2*n;i>=3;--i) !(g>>i&1)&&printf("%d ",a[i]);printf("%d
    ",a[2]);exit(0);//第二行,降序输出
    			}
    			dfs2(p,x+1,y,t,v,s),v+a[x]<=p&&(dfs2(p,x+1,y,t+1,v+a[x],s|(1LL<<x)),0);
    		}
    	public:
    		I void Solve(CI x) {memset(w,-1,sizeof(w)),dfs1(x,3,n+1,0,0,0),dfs2(x,n+2,2*n,0,0,0);}
    }DFS;
    int main()
    {
    	RI i,j;for(scanf("%d",&n),i=1;i<=2*n;++i) scanf("%d",a+i);
    	return sort(a+1,a+2*n+1),DFS.Solve(DP.GetAns()),0;
    }
    

    (F):Swiper, no swiping!(点此看题面

    大致题意: 让你在一张图中删去若干点(不能不删,也不能删完),使得剩余的每个点度数模(3)的余数不变。

    大码量多细节分类讨论题,调了两天......(话说这场比赛实在太多细节了,心态爆炸啊)

    首先,这里我们按照一个点度数模(3)的余数将其分为(0)类点、(1)类点、(2)类点。(注意,下面的每一个情况都建立于之前情况不成立的基础上)

    • 如果存在(0)类点:对于(n=1)的情况输出(No),否则保留这个点。
    • 如果存在一个由(2)类点组成的环:对于整张图是一个环的情况输出(No),否则保留这个环。(注意,这个环必须是简单环,不然度数会出锅)
    • 如果存在两个(1)类点:对于整张图是一条链的情况输出(No),否则保留一条以两个(1)类点为端点、中间全为(2)类点的链。(同样要注意这些(2)类点之间不能有边,可以用(BFS)
    • 此时,由于没有(0)类点、没有超过(1)(1)类点、没有(2)类点组成的环,所以可以保证这张图必然由一个(1)类点和一片(2)类点森林组成。可以证明,必然存在至少两棵树皆与这个(1)类点有至少两条边相连。则在两棵树上各找出一条以和(1)号点有边相连的点为端点的链,这个以(1)号点为唯一交点的两个相交的环,显然是符合要求的。但如果图中只有这样一对相交的环,也应该输出(No)。(再次强调,要注意这些(2)类点之间不能有边相连,我已经被这坑惨了)

    具体实现可以看代码。

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 500000
    #define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
    using namespace std;
    int n,m,ee,x[N+5],y[N+5],s[N+5],q[N+5],lst[N+5],lnk[N+5],deg[N+5],vis[N+5],fa[N+5],tag[N+5];
    struct edge {int to,nxt;}e[2*N+5];
    struct data {int v;I bool operator < (Con data& o) Con {return fa[v]<fa[o.v];}}p[N+5];
    class FastIO
    {
    	private:
    		#define FS 100000
    		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
    		#define pc(c) (C==E&&(clear(),0),*C++=c)
    		#define tn (x<<3)+(x<<1)
    		#define D isdigit(c=tc())
    		int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
    	public:
    		I FastIO() {A=B=FI,C=FO,E=FO+FS;}
    		Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
    		Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
    		Tp I void write(Con Ty& x,Con char& y) {write(x),pc(y);}
    		I void writes(Con string& x) {for(RI i=0,y=x.length();i^y;++i) pc(x[i]);}
    		I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
    }F;
    I int getfa(CI x) {return fa[x]^x?fa[x]=getfa(fa[x]):x;}
    I void FindLine(CI x)//有两个度数为1的点,找出一条链
    {
    	RI i,k,H=1,T=0;vis[q[++T]=x]=1;W(H<=T)
    	{
    		if(deg[k=q[H++]]%3==1&&k^x) break;
    		for(i=lnk[k];i;i=e[i].nxt) !vis[e[i].to]&&(lst[q[++T]=e[i].to]=k,vis[e[i].to]=1);
    	}W(s[k]=1,k^x) k=lst[k];
    }
    I void FindOnTree(CI x,CI st)//在以x为根的树上找出一条合法路径 
    {
    	RI i,k,H=1,T=0;vis[q[++T]=x]=1;W(H<=T)
    	{
    		if(tag[k=q[H++]]&&k^x) break;
    		for(i=lnk[k];i;i=e[i].nxt) !vis[e[i].to]&&(lst[q[++T]=e[i].to]=k,vis[e[i].to]=1);
    	}W(s[k]=1,k^x) k=lst[k];
    }
    I bool FillCircle(CI x,CI st,CI f=1)//找出以st为环上一点的简单环,f表示点数
    {
    	if(f>2) for(RI i=lnk[x];i;i=e[i].nxt) if(e[i].to==st) return s[x]=1;//先判断是否有环 
    	vis[x]=2;for(RI i=lnk[x];i;i=e[i].nxt) if(vis[e[i].to]==1)
    		if(FillCircle(e[i].to,st,f+1)) return s[x]=1;return vis[x]=1,false;
    }
    I bool FindCircle(CI x,CI lst)//找度数模3余2的点之间的环
    {
    	vis[x]=1;for(RI i=lnk[x];i;i=e[i].nxt) if(e[i].to^lst&&vis[e[i].to]) return FillCircle(x,x);//将环补充完整 
    	for(RI i=lnk[x];i;i=e[i].nxt) if(e[i].to^lst&&FindCircle(e[i].to,x)) return true;return false;
    }
    int main()
    {
    	freopen("a.in","r",stdin),freopen("a.out","w",stdout);
    	RI Tt,i,j,t1,t2;F.read(Tt);W(Tt--)
    	{
    		for(F.read(n),F.read(m),ee=0,i=1;i<=n;++i) s[i]=lnk[i]=deg[i]=vis[i]=tag[i]=0,fa[i]=i;//清空
    		for(i=1;i<=m;++i) F.read(x[i]),F.read(y[i]),++deg[x[i]],++deg[y[i]];//读入边,计算点数 
    		if(n==1) {F.writes("No
    ");continue;}//只有一个点,无解 
    		for(i=1;i<=n;++i) if(!(deg[i]%3)) {s[i]=1;goto End;}//存在度数模3余0的点,保留该点 
    		for(t2=0,i=1;i<=n;++i) deg[i]==2&&++t2;if(t2==n) {F.writes("No
    ");continue;}//整张图是一个环,无解 
    		for(i=1;i<=m;++i) deg[x[i]]%3==2&&deg[y[i]]%3==2&&
    			(fa[getfa(x[i])]=getfa(y[i]),add(x[i],y[i]),add(y[i],x[i]));//给度数模3余2的点连边
    		for(i=1;i<=n;++i) if(deg[i]%3==2&&!vis[i]&&FindCircle(i,0)) goto End;//找度数模3余2的点之间的环 
    		for(i=1;i<=n;++i) vis[i]=0; 
    		for(t1=t2=0,i=1;i<=n;++i) deg[i]==1&&++t1,deg[i]==2&&++t2;
    		if(t1==2&&t2==n-2) {F.writes("No
    ");continue;}//整张图是一条链,无解
    		for(t1=0,i=1;i<=n;++i) deg[i]%3==1&&++t1;if(t1>1)//有两个度数模3余1的点,找出一条链
    		{
    			for(i=1;i<=m;++i) ((deg[x[i]]%3)^2||(deg[y[i]]%3)^2)&&(add(x[i],y[i]),add(y[i],x[i]));//补上之前没有连的边 
    			for(i=1;i<=n;++i) if(deg[i]%3==1) {FindLine(i);goto End;}//搜索出一条路径 
    		} 
    		for(t1=t2=0,i=1;i<=n;++i) deg[i]%3==1&&(t1=i),deg[i]==2&&++t2;
    		if(deg[t1]==4&&t2==n-1) {F.writes("No
    ");continue;}//整张图是两个相连的环,无解
    		for(s[t1]=1,t2=0,i=1;i<=m;++i) x[i]==t1&&(tag[p[++t2].v=y[i]]=1),y[i]==t1&&(tag[p[++t2].v=x[i]]=1);//记下与度数模3余1的点相连的点 
    		for(i=1;i<=n;++i) deg[i]%3==2&&(fa[i]=getfa(i)); 
    		for(t1=0,sort(p+1,p+t2+1),i=1;i<t2;++i) if(fa[p[i].v]==fa[p[i+1].v])
    			if(FindOnTree(p[i].v,p[i].v),!t1) {t1=1;W(i<t2&&fa[p[i].v]==fa[p[i+1].v]) ++i;}else goto End;//从两棵树中找一条合法路径 
    		End:for(t1=0,i=1;i<=n;++i) !s[i]&&++t1;F.writes("Yes
    "),F.write(t1,'
    ');//统计删去的点数 
    		for(i=1;i<=n;++i) !s[i]&&(F.write(i,' '),0);F.writes("
    ");//输出删去的点 
    	}return F.clear(),0;
    }
    
  • 相关阅读:
    OO先导课——第二次上课
    OO先导课——第一次上课
    OO先导课——JAVA初见懵的知识合集
    OO先导课——作业(1)
    在驱动和应用程序间共享内存
    【求助】NdisSend,自定义数据包发送失败?
    HTTP协议详解(真的很经典)
    原始数据包的分析
    IP数据包的校验和算法
    基于IMD的包过滤防火墙原理与实现
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/CodeForcesRound594Div1.html
Copyright © 2020-2023  润新知