zoukankan      html  css  js  c++  java
  • 【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;
    }
    
  • 相关阅读:
    无法重用Linq2Entity Query
    The Joel Test
    MSBuilder directly instead of default VSComplie with keyborad shotcut 原创
    客户端缓存(Client Cache)
    关于代码重构和UT的一些想法,求砖头
    ExtJS2.0实用简明教程 应用ExtJS
    Perl information,doc,module document and FAQ.
    使用 ConTest 进行多线程单元测试 为什么并行测试很困难以及如何使用 ConTest 辅助测试
    史上最简单的Hibernate入门简介
    汽车常识全面介绍 传动系统
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/CodeForcesRound594Div1.html
Copyright © 2011-2022 走看看