【CodeForces】CodeForcesRound594 Div1 解题报告
\(A\):Ivan the Fool and the Probability Theory(点此看题面)
大致题意: 给一个\(n\times m\)的矩阵\(01\)染色,使得不存在某个同色连通块大小超过\(2\)。
这道题看似很神仙,实际上仔细想一想、推一推性质,还是比较简单的。
先考虑第一行,这是一个\(1\times 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\times 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_{x\sim y-1}\)减去\(2\)。
- 如果\(s_{x\sim y-1}\)中的最小值是\(Mn\),则操作后\(s\)中的最小值是\(Mn-2\)。显然答案不会更优。
- 如果\(s_{x\sim y-1}\)中的最小值是\(Mn+1\),则操作后\(s\)中的最小值是\(Mn-1\)。答案就是\(s_{x\sim y-1}\)中\(Mn+1\)的个数。
- 如果\(s_{x\sim y-1}\)中的最小值是\(Mn+2\),则操作后\(s\)中的最小值依然是\(Mn-2\)。答案就是\(s_{x\sim y-1}\)中\(Mn+2\)的个数加上整个\(s\)中\(Mn\)的个数。
- 对于\(x\)是右括号的情况。此时,就相当于\(s_{x\sim y-1}\)加上\(2\),也就是\(s_{1\sim x-1},s_{y\sim 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\n 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\n%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]," \n"[i==n]);return F.clear(),0;//输出答案
}
\(D\):Catowice City(点此看题面)
大致题意: 有\(n\)个人,每人有一只猫。每个人都认识若干猫(一定认识自己的猫),现在要选出人和猫共\(n\)个,使得至少有一个人和一只猫,且每个人都不认识任何一只猫。
首先,我们可以发现,因为一个人一定认识自己的猫,所以一个数不可能被选两次。而题目要求选出\(n\)个数,就必然是把\(1\sim 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\n");continue;}F.writes("Yes\n");//判断是否有解
F.write(Sz[1],' '),F.write(n-Sz[1],'\n');//分别输出人与猫的个数
for(i=1;i<=n;++i) col[i]==1&&(F.write(i,' '),0);F.writes("\n");//输出强连通分量内的点,作为人
for(i=1;i<=n;++i) col[i]^1&&(F.write(i,' '),0);F.writes("\n");//输出剩余的点,作为猫
}return F.clear(),0;
}
\(E\):Turtle(点此看题面)
大致题意: 让你把\(2n\)个元素放到一个\(2\times 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\)时,\(\because f(x)=f(x-1)+a_{1,x}-a_{2,x-1},f(x)\ge f(x-1),\therefore a_{1,x}-a_{2,x-1}\ge0.\)
又\(\because a_{1,x+1}-a_{2,x}\ge a_{1,x}-a_{2,x-1},\therefore a_{1,x+1}-a_{2,x}\ge 0.\)
\(\therefore f(x+1)=f(x)+a_{1,x+1}-a_{2,x}\ge f(x).\)
这与\(p=x\)时满足使\(f(p)\)最大的同时\(x\)最大矛盾,\(\therefore 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('\n');//第一行,升序输出
for(i=2*n;i>=3;--i) !(g>>i&1)&&printf("%d ",a[i]);printf("%d\n",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\n");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\n");continue;}//整张图是一个环,无解
for(i=1;i<=m;++i) deg[x[i]]%3==2&°[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\n");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\n");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\n"),F.write(t1,'\n');//统计删去的点数
for(i=1;i<=n;++i) !s[i]&&(F.write(i,' '),0);F.writes("\n");//输出删去的点
}return F.clear(),0;
}
【CodeForces】CodeForcesRound594 Div1 解题报告的更多相关文章
- 【CodeForces】CodeForcesRound576 Div1 解题报告
点此进入比赛 \(A\):MP3(点此看题面) 大致题意: 让你选择一个值域区间\([L,R]\),使得序列中满足\(L\le a_i\le R\)的数的种类数不超过\(2^{\lfloor\frac ...
- codeforces 31C Schedule 解题报告
题目链接:http://codeforces.com/problemset/problem/31/C 题目意思:给出 n 个 lessons 你,每个lesson 有对应的 起始和结束时间.问通过删除 ...
- codeforces 499B.Lecture 解题报告
题目链接:http://codeforces.com/problemset/problem/499/B 题目意思:给出两种语言下 m 个单词表(word1, word2)的一一对应,以及 profes ...
- codeforces 495C. Treasure 解题报告
题目链接:http://codeforces.com/problemset/problem/495/C 题目意思:给出一串只有三种字符( ')','(' 和 '#')组成的字符串,每个位置的这个字符 ...
- codeforces 490B.Queue 解题报告
题目链接:http://codeforces.com/problemset/problem/490/B 题目意思:给出每个人 i 站在他前面的人的编号 ai 和后面的人的编号 bi.注意,排在第一个位 ...
- CodeForces 166E -Tetrahedron解题报告
这是本人写的第一次博客,学了半年的基础C语言,初学算法,若有错误还请指正. 题目链接:http://codeforces.com/contest/166/problem/E E. Tetrahedro ...
- codeforces 489A.SwapSort 解题报告
题目链接:http://codeforces.com/problemset/problem/489/A 题目意思:给出一个 n 个无序的序列,问能通过两两交换,需要多少次使得整个序列最终呈现非递减形式 ...
- codeforces 485A.Factory 解题报告
题目链接:http://codeforces.com/problemset/problem/485/A 题目意思:给出 a 和 m,a 表示第一日的details,要求该日结束时要多生产 a mod ...
- codeforces 483A. Counterexample 解题报告
题目链接:http://codeforces.com/problemset/problem/483/A 题目意思:给出一个区间 [l, r],要从中找出a, b, c,需要满足 a, b 互质,b, ...
随机推荐
- JDK新特性关于流操作部分
// array 工具类 可以用来快捷的将数组转化为list List<String> strings = Arrays.asList("zhongguo", &quo ...
- vue如何循环渲染element-ui中table内容
对于大多数前端开发者来说,vuejs+element-ui是开发后台管理系统过程中必不可少的技术框架.而后台管理系统中,最常见的形式就是表格和表单,以便用来增删改查. element-ui中table ...
- [转]UiPath State Machines
本文转自:https://docs.uipath.com/studio/docs/state-machines A state machine is a type of automation that ...
- 入职小白随笔之Android四大组件——广播详解(broadcast)
Broadcast 广播机制简介 Android中的广播主要可以分为两种类型:标准广播和有序广播. 标准广播:是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播 ...
- Spring Boot修改JSP不用重启的办法
在application.properties文件中添加一行代码解决. Spring Boot 2.0以上添加如下一行: server.servlet.jsp.init-parameters.deve ...
- OAuthon2.0机制详解
最近在忙企业微信和钉钉的第三方应用开发,需要获取一些信息,第一个就是这个OAuthon2.0,先详细了解下概念和流程 一.应用场景 我们要想用第三方播放器播放你的云盘账号里面的一些秘密视频资源,为了要 ...
- ssdb make 失败 autoconf required
ERROR! autoconf required! install autoconf first Makefile:4: build_config.mk: No such file or direct ...
- mysql研究跟进
count(1)对比 count(*) count(N),N指的是列的序列号,innodb引擎下一般为主键列:count(*),mysql优化器也会将统计列自动优化.所以日常使用区别不大 阿里规范里的 ...
- Tensorflow常用算数操作
TensorFlow 将图形定义转换成分布式执行的操作, 以充分利用可用的计算资源(如 CPU 或 GPU.一般你不需要显式指定使用 CPU 还是 GPU, TensorFlow 能自动检测.如果检测 ...
- redis为什么是单线程而且速度快?
redis支持的5种数据类型: 1.String(字符串) 2.List(数组或列表) 3.Set(集合) 4.Hash(哈希或字典) 5.ZSet(有序集合) 数据库的工作模式按存储方式可分为: 硬 ...