Codeforces Round #594 (Div. 1)

Preface

这场CF真是细节多的爆炸,B,C,F都是大细节题,每道题都写了好久的说

CSP前的打的最后一场比赛了吧,瞬间凉意满满

希望CSP可以狗住冬令营啊(再狗不住真没了)


A. Ivan the Fool and the Probability Theory

原谅我脑子不如陈指导好想不出来正解,然后打了个暴力3min找到规律做掉了。。。

然后讲一下正确的做法(陈指导教我的):

考虑我们用DP做出\(1\times m\)的矩阵的答案记为\(f_m\),考虑用它填了第一行

然后我们考虑第一行的填法:

  1. 第一行填的是\(0/1\)相间的,此时方案数有\(2\)种,考虑第二行填的就要么与第一行相同,要么与第一行相反。而且相同的不能连续填两行。我们仔细想想发现这个问题是不是和原来的一样了?(第一行看作某种状态,相反看作另一种状态),那么显然此时的方案数就是\(f_n\)
  2. 第一行填的是非\(0/1\)相间的,此时方案数有\(f_m-2\)种,显然第二行的填法只有每个与第一行的相反。而且这样的情况要一直延续到第\(n\)行,那么此时的总方案数就是\(f_m-2\)

综上所述,最后的答案就是\(f_n+f_m-2\)

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,mod=1e9+7;
int n,m,f[N][2];
inline int sum(CI x,CI y)
{
	int t=x+y; return t>=mod?t-mod:t;
}
int main()
{
	scanf("%d%d",&n,&m); int lim=max(n,m); RI i;
	for (f[1][0]=f[1][1]=1,f[2][0]=f[2][1]=2,i=3;i<=lim;++i)
	f[i][0]=f[i-1][1],f[i][1]=sum(f[i-1][0],f[i-1][1]);
	return printf("%d",sum(sum(sum(f[n][0],f[n][1]),sum(f[m][0],f[m][1])),mod-2)),0;
}

B. The World Is Just a Programming Task (Hard Version)

真NM烦人(假装有很多细节)的题,中间想了好多假算法的说

考虑我们先判掉必然无解的情况,那么显然这时我们可以搞出一个合法的括号序列

从这个合法的序列开始,我们把它的括号匹配求出来,同时记一个\(ct_i\)表示第\(i\)对括号里面有多少对匹配的括号

我们现在可以进行的交换有哪几类:

  1. 什么都不干(原地交换),此时的答案就是\(ct_0\)
  2. 交换某对匹配的括号,而这对括号外面没有其它的括号。我们可以发现此时这个串大概是\(\cdots(ct_i)\cdots\)的样子,因此我们交换后就变成\((\cdots)ct_i\)的样子,显然答案是了\(ct_i+1\)
  3. 交换某对匹配的括号,而这对括号外面只有一对其它的括号。此时的形式就是\(\cdots(\cdots(ct_i)\cdots)\cdots\),那么交换后就变成\(\cdots(\cdots)ct_i(\cdots)\cdots\)的样子,那么这时候答案就是\(ct_i+ct_0+1\)
  4. 交换某对匹配的括号,而这对括号外面有多于一对其它的括号。简单分析后答案不会改变
  5. 交换不匹配的括号,就是个弟弟玩意,显然答案也不会增加

那么这题总算是做完了

#include<cstdio>
#include<queue>
#define RI register int
#define CI const int&
using namespace std;
const int N=600005;
int n,dlt,st,cl,cr,stk[N],top,tot,L[N],R[N],ct[N],out[N],a[N],b[N],pfx[N],ans,x,y; char s[N];
struct heap
{
	priority_queue <int,vector <int>,greater <int> > val,del;
	inline void insert(CI x)
	{
		val.push(x);
	}
	inline void remove(CI x)
	{
		del.push(x);
	}
	inline int top(void)
	{
		while (!val.empty()&&!del.empty()&&val.top()==del.top())
		val.pop(),del.pop(); return val.top();
	}
}hp;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j; for (scanf("%d%s",&n,s+1),i=n+1;i<=(n<<1);++i)
	a[i]=a[i-n]=s[i-n]=='('?1:-1,s[i-n]=='('?++cl:++cr;
	if (cl!=cr) return puts("0\n1 1"),0;
	for (i=1;i<=n;++i) hp.insert(pfx[i]=pfx[i-1]+a[i]);
	for (i=1;i<=n;++i)
	{
		if (hp.top()+dlt>=0) { st=i; break; }
		hp.remove(pfx[i]); dlt-=a[i]; hp.insert(0-dlt);
	}
	for (i=st;i<=st+n-1;++i) b[i-st+1]=a[i];
	for (i=1;i<=n;++i) if (b[i]==1) stk[++top]=++tot,L[tot]=i;
	else out[stk[top]]=stk[top-1],R[stk[top]]=i,++ct[stk[--top]];
	for (ans=ct[0],x=y=i=1;i<=tot;++i)
	{
		if (!out[i]) { if (ct[i]+1>ans) ans=ct[i]+1,x=L[i],y=R[i]; }
		else if (!out[out[i]]) { if (ct[i]+ct[0]+1>ans) ans=ct[i]+ct[0]+1,x=L[i],y=R[i]; }
	}
	return printf("%d\n%d %d",ans,(x+st-2)%n+1,(y+st-2)%n+1),0;
}

C. Queue in the Train

真是令人头晕脑涨的模拟题,我们直接按题意模拟操作

考虑我们用一些数据结构来维护答案:

  1. 给原数组按时间第一关键字,序号第二关键字排序,然后把可选每个数进行操作
  2. 开一个队列表示正在排队的人
  3. 有些人可能已经可以打水了,但是由于前面有人再它前面排队,因此它不会去打水。那么我们需要一个来维护这些人的编号
  4. 怎么判掉堆顶能不能去打水呢,我们再开一个set用来动态维护队列里正在排队的人的编号,每次用堆顶和set里的最小值比较即可

注意这些操作维护的顺序以及各种各样的细节

#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#define RI register int
#define CI const int&
#define int long long
using namespace std;
const int N=100005;
struct data
{
	int val,id;
	friend inline bool operator < (const data& A,const data& B)
	{
		return A.val<B.val||(A.val==B.val&&A.id<B.id);
	};
}a[N],t[N]; int n,p,ans[N],cnt; queue <int> q;
set <int> s; priority_queue <int, vector <int>, greater <int> > hp;
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j,pos; for (scanf("%I64d%I64d",&n,&p),i=1;i<=n;++i)
	scanf("%I64d",&a[i].val),a[i].id=i; sort(a+1,a+n+1);
	int cur=0; for (i=pos=1;i<=n;++i)
	{
		if (hp.empty()&&q.empty()&&pos<=n)
		q.push(a[pos].id),s.insert(a[pos].id),cur=a[pos].val,++pos;
		while (!hp.empty()) if (s.empty()||hp.top()<*s.begin())
		q.push(hp.top()),s.insert(hp.top()),hp.pop(); else break; cur+=p;
		while (pos<=n&&a[pos].val<=cur) t[++cnt]=a[pos++];
		for (j=1;j<=cnt;++j) if (s.empty()||t[j].id<*s.begin())
		q.push(t[j].id),s.insert(t[j].id); else hp.push(t[j].id);
		ans[q.front()]=cur; s.erase(q.front()); q.pop(); cnt=0;
	}
	for (i=1;i<=n;++i) printf("%I64d ",ans[i]); return 0;
}

D. Catowice City

比B,C都清新的思维题,还算是帮CSP复习了一波板子

考虑由于每对人和猫都要作出选择,那么如果我们有人\(i\)和猫\(j\)的边,那么说明选了人\(i\)就必须选人\(j\)(除去与自己猫的边)

那么我们可以把这种关系看成一条有向边,那么容易发现SCC里的点都是互相依赖的,选了一个其它的都要选

那么我们先Tarjan缩点,然后如果最后的图就是一个联通块,那么显然是无解的

否则我们考虑找出一个度数为\(0\)的联通块,把这个联通块里的点选作人,其它的点都选做猫即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=1000005;
struct edge
{
	int to,nxt;
}e[N]; int t,head[N],n,m,cnt,x,y,ans1[N],c1,ans2[N],c2;
int dfn[N],low[N],idx,col[N],stk[N],top,deg[N],scc; bool vis[N];
inline void clear(void)
{
	RI i; for (i=1;i<=n;++i) head[i]=dfn[i]=low[i]=deg[i]=0;
	cnt=scc=top=c1=c2=0;
}
inline void addedge(CI x,CI y)
{
	e[++cnt]=(edge){y,head[x]}; head[x]=cnt;
}
#define to e[i].to
inline void Tarjan(CI now)
{
	dfn[now]=low[now]=++idx; vis[stk[++top]=now]=1;
	for (RI i=head[now];i;i=e[i].nxt)
	if (!dfn[to]) Tarjan(to),low[now]=min(low[now],low[to]);
	else if (vis[to]) low[now]=min(low[now],dfn[to]);
	if (dfn[now]==low[now])
	{
		for (col[now]=++scc,vis[now]=0;stk[top]!=now;--top)
		col[stk[top]]=scc,vis[stk[top]]=0; --top;
	}
}
int main()
{
	for (scanf("%d",&t);t;--t)
	{
		RI i,j; for (scanf("%d%d",&n,&m),i=1;i<=m;++i)
		scanf("%d%d",&x,&y),x!=y&&(addedge(x,y),0);
		for (i=1;i<=n;++i) if (!dfn[i]) Tarjan(i);
		if (scc==1) { puts("No"); clear(); continue; }
		for (j=1;j<=n;++j) for (i=head[j];i;i=e[i].nxt)
		if (col[j]!=col[to]) ++deg[col[j]];
		int id; for (i=1;i<=scc;++i) if (!deg[i]) { id=i; break; }
		for (i=1;i<=n;++i) if (col[i]==id) ans1[++c1]=i; else ans2[++c2]=i;
		puts("Yes"); printf("%d %d\n",c1,c2);
		for (i=1;i<=c1;++i) printf("%d%c",ans1[i]," \n"[i==c1]);
		for (i=1;i<=c2;++i) printf("%d%c",ans2[i]," \n"[i==c2]); clear();
	}
	return 0;
}

E. Turtle

思维难度较大的一道题,写到一半瞄了一眼陈指导的题解才会做

首先我们容易发现第一行的数必须是不降排序的,而第二行的数也要是不升的,证明非常简单,这里不再赘述

我们接下来考虑最大的路径是怎么走的,我们发现我们可以根据路径向下走的位置给它分类,例如我们设\(f(x)=\sum_{i=1}^n a_{1,i}=\sum_{i=p}^n a_{2,i}\)

那么我们考虑一个结论,当\(f(x)\)\(\max\)\(x=1\)\(x=n\),证明如下:

\(x\not = 1\)\(x\not =n\)时,设\(f(x)\)\(\max\)

\(f(x)=f(x-1)+a_{1,x}-a_{2,x-1}\)\(f(x)\ge f(x-1)\)得出\(a_{1,x}-a_{2,x-1}\ge 0\)

同理对于\(f(x)\)\(f(x+1)\)也有同样的分析,那么\(-a_{1,x+1}+a_{2,x}\ge 0\)

将不等式两边相加,得到\((a_{1,x}-a_{1,x+1})+(a_{2,x}-a_{2,x-1})\ge 0\),这显然与\(a_1\)不降与\(a_2\)不升矛盾,因此原命题得证

因此我们只需要让\(f(1)\)\(f(n)\)尽量小即可,贪心地想把两个都要经过的起点终点选上最小值和次小值,考虑剩下的怎么填

由于总和确定,那么我们只要尽量使剩下数的分成两组,和最接近即可

考虑用DP算出答案,我们设\(f_{i,j,k}\)表示前\(i\)个数选了\(j\)个和为\(k\)能否达成,然后用bitset可以优化掉一维

那么知道答案怎么构造方案呢,发现数据中\(n\)非常小,因此我们可以用meet in middle构造一组解,状态的话状压一下就可以存了

#include<cstdio>
#include<bitset>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
typedef long long LL;
const int N=27,S=50005;
int n,a[N<<1],ans[2][N],ret,cur=1e9;
long long g[N][N*S]; bitset <N*S> f[N]; bool vis[N<<1];
inline bool cd(CI x,CI y)
{
	return x>y;
}
inline void DFS1(CI nw,CI lim,CI cs=0,CI sum=0,const LL& sts=0)
{
	if (nw>lim) return (void)(g[cs][sum]=sts);
	DFS1(nw+1,lim,cs,sum,sts); DFS1(nw+1,lim,cs+1,sum+a[nw],sts|(1LL<<nw));
}
inline void DFS2(CI nw,CI lim,CI cs=0,CI sum=0,const LL& sts=0)
{
	if (nw>lim)
	{
		if (cur-sum>=0&&~g[n-1-cs][cur-sum])
		{
			RI i,c=1; for (i=3;i<=(n<<1);++i)
			if ((g[n-1-cs][cur-sum]>>i)&1LL) ans[0][++c]=a[i],vis[i]=1;
			for (i=3;i<=(n<<1);++i)
			if ((sts>>i)&1LL) ans[0][++c]=a[i],vis[i]=1;
			for (c=0,i=3;i<=(n<<1);++i) if (!vis[i]) ans[1][++c]=a[i];
			sort(ans[0]+1,ans[0]+n+1); sort(ans[1]+1,ans[1]+n+1,cd);
			for (i=1;i<=n;++i) printf("%d%c",ans[0][i]," \n"[i==n]);
			for (i=1;i<=n;++i) printf("%d%c",ans[1][i]," \n"[i==n]);
			exit(0);
		}
		return;
	}
	DFS2(nw+1,lim,cs,sum,sts); DFS2(nw+1,lim,cs+1,sum+a[nw],sts|(1LL<<nw));
}
int main()
{
	RI i,j; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
	for (i=n+1;i<=(n<<1);++i) scanf("%d",&a[i]);
	sort(a+1,a+(n<<1)+1); ans[0][1]=a[1]; ans[1][n]=a[2];
	for (f[0][0]=1,i=3;i<=(n<<1);++i)
	for (ret+=a[i],j=min(n-1,i-2);j;--j) f[j]|=(f[j-1]<<a[i]);
	for (i=0;i<=ret;++i) if (f[n-1][i])
	if (max(i,ret-i)<cur) cur=max(i,ret-i);
	return memset(g,-1,sizeof(g)),DFS1(3,n+1),DFS2(n+2,n<<1),0;
}

F. Swiper, no swiping!

细节超级多的分类讨论题,写了一天最后偷来了错掉的数据才过掉

考虑把点按\(\mod 3\)的余数分为三类,记为\(Z,A,B\)

  • 如果有\(Z\)类的点,显然我们只用保留一个就可以了(SP:除了只有一个点的情况)
  • 如果\(A\)类的点之间有边相连,那么显然只用保留一对这样的点就可以了(SP:除了只有两个点相连的情况)
  • 如果B类点之间有环,那么我们可以搜出一个最短的环保留即可(SP:除了整张图就是一个大环的情况)
  • 如果存在两个即以上的\(A\)类点,那么我们显然可以找出一条连接两个\(A\)类点的边,满足这两点之间只有\(B\)类点(SP:除了整张图是一条链的情况)
  • 除去上面的情况那么现在我们的图一定只有一个\(A\)类点,然后剩下的\(B\)类点构成了森林。那么我们找出两个子树,从里面找出包含\(A\)的两个环即可(SP:除了整张图就是一个节点套上两个环的情况)

大体思路就是这样,关于一些原理的证明可以看官方题解:

F

然后就做完了,看了题解觉得很简单,写了代码就会很绝望……

#include<cstdio>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=500005;
struct edge
{
	int to,nxt;
}e[N<<1]; int t,n,m,x[N],y[N],head[N],cnt,fa[N],deg[N],tim[N],cur,ans;
bool ext[N],flag; vector <int> v[N]; int q[N],pre[N],dep[N],vis[N];
inline void addedge(CI x,CI y)
{
	e[++cnt]=(edge){y,head[x]}; head[x]=cnt;
	e[++cnt]=(edge){x,head[y]}; head[y]=cnt;
}
inline void paint(CI s,CI t)
{
	for (int nw=t;~nw;nw=pre[nw]) ext[nw]=1;
}
#define to e[i].to
inline void findcircle(CI now,CI tc)
{
	if (flag) return; RI i; vis[now]=1; tim[now]=tc; int pos,cur=-1;
	for (i=head[now];i;i=e[i].nxt)
	if (vis[to]==1&&to!=pre[now]&&deg[to]%3==2) 
	if (dep[to]>cur) cur=dep[to],pos=to;
	if (~cur)
	{
		for (int x=now;x!=pos;x=pre[x])	ext[x]=1;
		flag=1; ext[pos]=1; return;
	}
	for (i=head[now];i;i=e[i].nxt) if (!vis[to]&&deg[now]%3==2)
	pre[to]=now,dep[to]=dep[now]+1,findcircle(to,tc); vis[now]=2;
}
inline void BFS1(CI st)
{
	RI H=0,T=1,i; for (i=1;i<=n;++i) pre[i]=0;
	pre[q[T]=st]=-1; while (H<T)
	{
		int now=q[++H]; if (now!=st&&deg[now]%3==1) return paint(st,now);
		for (i=head[now];i;i=e[i].nxt)
		if (!pre[to]) pre[to]=now,q[++T]=to;
	}
}
inline void BFS2(CI st)
{
	RI H=0,T=1,i; for (i=1;i<=n;++i) pre[i]=0;
	pre[q[T]=st]=-1; while (H<T)
	{
		int now=q[++H]; if (now!=st&&vis[now]) return paint(st,now);
		for (i=head[now];i;i=e[i].nxt)
		if (!pre[to]) pre[to]=now,q[++T]=to;
	}
}
#undef to
inline void output(void)
{
	RI i; for (puts("Yes"),ans=0,i=1;i<=n;++i) if (!ext[i]) ++ans;
	for (printf("%d\n",ans),i=1;i<=n;++i)
	if (!ext[i]) printf("%d ",i); putchar('\n');
}
inline void clear(void)
{
	for (RI i=1;i<=n;++i) head[i]=deg[i]=ext[i]=vis[i]=dep[i]=0,v[i].clear(); cnt=cur=flag=0;
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i,j; for (scanf("%d%d",&n,&m),i=1;i<=m;++i)
		scanf("%d%d",&x[i],&y[i]),++deg[x[i]],++deg[y[i]];
		if (n==1) { puts("No"); continue; }
		for (i=1;i<=n;++i) if (deg[i]%3==0)
		{
			ext[i]=flag=1; break;
		}
		if (flag) { output(); clear(); continue; }
		if (n==2) { puts("No"); clear(); continue; } 
		for (i=1;i<=m;++i) if (deg[x[i]]%3==1&&deg[y[i]]%3==1)
		{
			ext[x[i]]=ext[y[i]]=flag=1; break;
		}
		if (flag) { output(); clear(); continue; }
		int ct1,ct2=0; for (i=1;i<=n;++i) if (deg[i]==2) ++ct2;
		if (ct2==n) { puts("No"); clear(); continue; }
		for (i=1;i<=m;++i) if (deg[x[i]]%3==2&&deg[y[i]]%3==2) addedge(x[i],y[i]);
		for (i=1;i<=n;++i) pre[i]=0; for (i=1;i<=n;++i)
		if (deg[i]%3==2&&!vis[i]) findcircle(i,++cur);
		if (flag) { output(); clear(); continue; }
		for (ct1=ct2=0,i=1;i<=n;++i) ct1+=(deg[i]==1),ct2+=(deg[i]==2);
		if (ct1==2&&ct2==n-2) { puts("No"); clear(); continue; }
		for (ct1=0,i=1;i<=n;++i) if (deg[i]%3==1) ++ct1;
		if (ct1>=2)
		{
			for (i=1;i<=m;++i) if (deg[x[i]]%3!=2||deg[y[i]]%3!=2) addedge(x[i],y[i]);
			for (i=1;i<=n;++i) if (deg[i]%3==1) { ct1=i; break; }
			BFS1(ct1); output(); clear(); continue;
		}
		for (i=1;i<=n;++i) if (deg[i]%3==1) { ct1=i; break; }
		for (ct2=0,i=1;i<=n;++i) ct2+=(deg[i]%3==2);
		if (deg[ct1]==4&&ct2==n-1) { puts("No"); clear(); continue; }
		for (i=1;i<=n;++i) vis[i]=0; 
		for (i=1;i<=m;++i)
		{
			if (x[i]==ct1) v[tim[y[i]]].push_back(y[i]),vis[y[i]]=1;
			if (y[i]==ct1) v[tim[x[i]]].push_back(x[i]),vis[x[i]]=1;
		}
		for (ct2=0,ext[ct1]=1,i=1;i<=cur;++i) if (v[i].size()>=2)
		{
			BFS2(v[i][0]); if (++ct2==2) break;
		}
		output(); clear();
	}
	return 0;
}

Postscript

不说什么了,各路神仙保我CSP RP++++RP

posted @ 2019-11-14 20:38  空気力学の詩  阅读(213)  评论(0编辑  收藏  举报