AtCoder Grand Contest 033

Preface

从D题开始就不会做了,真的是越来越菜


A - Darker and Darker

SB题,多源最短路,直接大力BFS即可

#include<cstdio>
#include<utility>
#include<iostream>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef pair <int,int> pi;
const int N=1005,dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};
inline char get_ch(void)
{
	char ch; while (ch=getchar(),ch!='#'&&ch!='.'); return ch;
}
int n,m,stp[N][N],ans; pi q[N*N];
int main()
{
	RI i,j,H=0,T=0; for (scanf("%d%d",&n,&m),i=1;i<=n;++i)
	for (j=1;j<=m;++j) if (get_ch()=='#') q[++T]=mp(i,j); else stp[i][j]=1e9;
	while (H<T)
	{
		int nx=q[++H].fi,ny=q[H].se; for (i=0;i<4;++i)
		{
			int tx=nx+dx[i],ty=ny+dy[i];
			if (tx>=1&&tx<=n&&ty>=1&&ty<=m&&stp[tx][ty]>stp[nx][ny]+1)
			stp[tx][ty]=stp[nx][ny]+1,q[++T]=mp(tx,ty);
		}
	}
	for (i=1;i<=n;++i) for (j=1;j<=m;++j) ans=max(ans,stp[i][j]);
	return printf("%d",ans),0;
}


B - LRUD Game

考虑倒着操作,显然我们可以维护前\(a\)行,后\(b\)行,前\(c\)列,后\(d\)列为先手必胜状态

考虑先手操作,显然是把局面往对自己有利的方向推进一步,后手就是反向处理

注意倒过来之后实际上是先第二个人再第一个人操作,刚开始没注意WA了好多发

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int h,w,n,sx,sy,a,b,c,d; char s1[N],s2[N];
int main()
{
	RI i; scanf("%d%d%d%d%d%s%s",&h,&w,&n,&sx,&sy,s1+1,s2+1);
	for (i=n;i;--i)
	{
		switch (s2[i])
		{
			case 'U': if (b) --b; break;
			case 'D': if (a) --a; break;
			case 'L': if (d) --d; break;
			case 'R': if (c) --c; break;
		}
		switch (s1[i])
		{
			case 'U': ++a; break;
			case 'D': ++b; break;
			case 'L': ++c; break;
			case 'R': ++d; break;
		}
		if (a+b>=h||c+d>=w) return puts("NO"),0;
	}
	return puts(a<sx&&sx<=h-b&&c<sy&&sy<=w-d?"YES":"NO"),0;
}


C - Removing Coins

刚开始读错题意了想歪了很久,后来才发现是向着这个方向走而不是移到这个点上

我们发现对于每次操作显然所有除了该点的叶子节点都会被删去,因此做到最后其实都是在树的直径上操作

我们考虑求出直径\(len\),每次操作相当于把直径减\(1\)(选择一个直径的端点)或减\(2\)(otherwise)

而显然当只剩下两个点的时候就无法操作了,因此我们的问题变成了给一个数\(len-2\)进行减\(1\)或减\(2\)操作,谁先把数减到\(0\)就获胜了

这是个经典的Bash Game模型,只需要判断\(len\)\(3\)的关系即可

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
struct edge
{
	int to,nxt;
}e[N<<1]; int n,head[N],cnt,x,y,len,dis[N],q[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;
}
#define to e[i].to
inline int BFS(CI st=1)
{
	RI H=0,T=1,i,id=st; for (i=1;i<=n;++i) dis[i]=0;
	dis[q[1]=st]=len=1; while (H<T)
	{
		int now=q[++H]; if (dis[now]>len) len=dis[now],id=now;
		for (i=head[now];i;i=e[i].nxt) if (!dis[to])
		dis[to]=dis[now]+1,q[++T]=to;
	}
	return id;
}
#undef to
int main()
{
	RI i; for (scanf("%d",&n),i=1;i<n;++i)
	scanf("%d%d",&x,&y),addedge(x,y);
	return BFS(BFS()),puts((len-2)%3?"First":"Second"),0;
}


D - Complexity

只会\(O(n^5)\)大暴力真是太屑了的说

考虑到当情况最坏即图是黑白相间时,答案是\(\log nm\)的,因此我们可以吧答案压到状态里

此时原来的状态变成了\(0/1\),我们套路地考虑去掉其中一维,把存储的值改成另一个边界

\(f_{t,i,j,k}\)表示答案为\(t\),上边界为\(i\),下边界为\(j\),左边界为\(k\)时右边界的最大值

同理设\(g_{t,i,j,k}\)表示答案为\(t\),左边界为\(i\),右边界为\(j\),上边界为\(k\)时下边界的最大值

转移的时候本来是枚举某行某列的,现在容易发现例如对于\(f_{t,i,j,k}\),当\(t,i,k\)固定时,\(f_{t,i,j,k}\)关于\(j\)单调不增,对\(g_{t,i,j,k}\)也有类似的性质

那么我们可以直接二分或者two pointers优化转移,总复杂度就是\(O(n^3\log^2 n)\)\(O(n^3\log n)\)的了

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=200;
int n,m,f[N][N][N],g[N][N][N]; char a[N][N];
int main()
{
	RI i,j,k,p,t; for (scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%s",a[i]+1);
	for (i=1;i<=n;++i) for (j=i;j<=n;++j)
	for (f[i][j][m+1]=k=m;k;--k) if (i==j||(a[i][k]==a[j][k]&&f[i][j-1][k]>=k))
	f[i][j][k]=a[i][k]==a[i][k+1]?f[i][j][k+1]:k; else f[i][j][k]=k-1;
	for (i=1;i<=m;++i) for (j=i;j<=m;++j)
	for (g[i][j][n+1]=k=n;k;--k) if (i==j||(a[k][i]==a[k][j]&&g[i][j-1][k]>=k))
	g[i][j][k]=a[k][i]==a[k+1][i]?g[i][j][k+1]:k; else g[i][j][k]=k-1;
	if (f[1][n][1]==m) return puts("0"),0; for (t=1;;++t)
	{
		for (i=1;i<=n;++i) for (j=i;j<=n;++j) for (k=1;k<=m;++k)
		f[i][j][k]=f[i][j][f[i][j][k]+1];
		for (i=1;i<=m;++i) for (j=i;j<=m;++j) for (k=1;k<=n;++k)
		g[i][j][k]=g[i][j][g[i][j][k]+1];
		for (i=1;i<=n;++i) for (j=1;j<=m;++j)
		{
			for (f[i][n+1][j]=j-1,k=i;k<=n;++k)
			for (p=f[i][k][j];p>f[i][k+1][j];--p) g[j][p][i]=max(g[j][p][i],k);
			for (g[j][m+1][i]=i-1,k=j;k<=m;++k)
			for (p=g[j][k][i];p>g[j][k+1][i];--p) f[i][p][j]=max(f[i][p][j],k);
		}
		if (f[1][n][1]==m) return printf("%d",t),0;
	}
	return 0;
}


E - Go around a Circle

考虑假定\(s_1\)\(R\),否则我们可以交换\(B\)\(R\),不难发现此时一定不存在连续的两个\(B\)

假设\(s\)中只有一种字符,那么此时的限制就变成了求圆上不存在连续两个\(B\)的方案数

我们可以直接DP,\(f_{i,0/1,0/1}\)表示做到第\(i\)个位置,第一个位置放的是什么,第\(i\)位放的是什么

接下来没有这种情况了,我们认为圆上的若干个非空的\(R\)\(B\)给隔开

假设\(s\)中每一段连续的字符长度为\(c_1,c_2,\dots ,c_k\),当一种染色合法显然需要满足:

  1. 圆上每一个连续的\(R\)长度都是奇数。因为当长度为偶数时,从第一个点走出去的奇偶性和从第二个点走出去的奇偶性不同,显然不合法
  2. 圆上每一个连续的\(R\)长度不超过\(c_1+[2|c_1]\)。因为当\(c_1\)为奇数时,要满足从第一个点走过去的长度不超过\(c_1\),当\(c_1\)为偶数时,要满足从第二个点走过去的长度不超过\(c_1\)
  3. \(k\)为奇数,可以直接将\(k\)减一。因为此时最后一段必然是\(R\),因此可以直接拿掉
  4. 对于\(i\in[2,k],2\not|\ i\),若\(2\not |\ c_i\),则圆上的每一个连续的\(R\)长度都不超过\(c_i\)。因为当\(c_i\)为奇数的情况必须要跨过整个段

于是问题等价为使每一段连续的\(R\)长度都是奇数且小于一个数,我们把每段连续的\(R\)\(B\)连在一起那么长度就是偶数,总长度和限制均减半即可直接用前缀和优化DP解决

开始时把\(m\)打成\(n\)WA了好久,真想一巴掌扇死自己

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,mod=1e9+7;
int n,m,c[N],k,lst,f[N],pfx[N],ans; char s[N];
inline int sum(CI x,CI y)
{
	int t=x+y; return t>=mod?t-mod:t;
}
namespace SP //one char solver
{
	int f[N][2][2];
	inline int solve(void)
	{
		f[1][0][0]=f[1][1][1]=1; for (RI i=2;i<=n;++i)
		f[i][0][0]=sum(f[i-1][0][0],f[i-1][0][1]),f[i][0][1]=f[i-1][0][0],
		f[i][1][0]=sum(f[i-1][1][0],f[i-1][1][1]),f[i][1][1]=f[i-1][1][0];
		return sum(f[n][0][0],sum(f[n][0][1],f[n][1][0]));
	}
};
int main()
{
	RI i; for (scanf("%d%d%s",&n,&m,s+1),lst=1,i=2;i<=m;++i)
	if (s[i]!=s[i-1]) c[++k]=i-lst,lst=i; c[++k]=n-lst+1;
	if (k==1) return printf("%d",SP::solve()),0; if (n&1) return puts("0"),0;
	if (k&1) --k; int lim=c[1]+!(c[1]&1); for (i=3;i<=k;i+=2) if (c[i]&1) lim=min(lim,c[i]);
	for (n>>=1,lim=lim+1>>1,f[0]=pfx[0]=i=1;i<=n;++i)
	f[i]=sum(pfx[i-1],mod-(i-1-lim>=0?pfx[i-1-lim]:0)),
	pfx[i]=sum(pfx[i-1],f[i]); for (i=1;i<=min(n,lim);++i)
	ans=sum(ans,2LL*f[n-i]*i%mod); return printf("%d",ans),0;
}


F - Adding Edges

根本不会做的说,陈指导还能胡一个\(O(n^2\log^2 n)\),我只会写写\(O(n^3)\)

考虑我们把\(G\)中的边进行压缩,把条件的无序改成有序:

\((a,b)\in G,(a,c)\in G\)且在\(T\)中存在一条路径顺次包含\(a,b,c\),那么将\((a,c)\)删除并加入\((a,b)\)不会影响答案

我们考虑对\(G\)压缩完后,答案如何统计,有一个显然的结论:

考虑如果一对点\((x,y)\),如果它们的树上路径可以由若干条题目中给出的边对应的树上路径拼接而成,那么它们之间的边就能够存在

\((x,y)\)有边当且仅当存在\(a_1,a_2,\cdots ,a_k\)满足:

  • \(a_1=x,a_k=y\)
  • \((a_i,a_{i+1})\in G,i\in[1,k)\)
  • \(T\)中存在一条路径顺次包含\(a_1,a_2,\cdots ,a_k\)

这样的路径显然可以通过枚举起点直接DFS统计,那么问题在于如何压缩\(G\)

我们考虑维护一个\(p_{x,y}\)表示在\(a\)\(b\)的路径上,从出发能到达的离\(b\)最近的点

考虑加入一条边的\((a,b)\)时,如果\(p_{a,b}\)存在,那么按照我们的处理方式,要添加的边为\((p_{a,b},b)\)

若不存在,我们可以直接对\(b\)的子树进行遍历,维护子树内的\(p\)即可

具体实现详见代码,复杂度\(O(n^2)\)\(n,m\)同阶)

#include<cstdio>
#include<vector>
#include<utility>
#define RI register int
#define CI const int&
#define pb push_back
#define mp make_pair
using namespace std;
typedef pair <int,int> pi;
const int N=2005;
struct edge
{
	int to,nxt;
}e[N<<1];
int n,m,x,y,head[N],cnt,p[N][N],fa[N][N],ans,q[N]; bool g[N][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;
}
#define to e[i].to
inline void DFS(CI now,int* fa)
{
	for (RI i=head[now];i;i=e[i].nxt) if (to!=fa[now]) fa[to]=now,DFS(to,fa);
}
inline void insert(CI x,CI y)
{
	if (p[x][y]==y||p[y][x]==x) return;
	if (p[x][y]) return insert(p[x][y],y);
	if (p[y][x]) return insert(p[y][x],x);
	RI H=0,T=1,i; vector <pi> tp;
	g[x][y]=1; p[x][y]=y; q[1]=y;
	while (H<T)
	{
		int now=q[++H]; for (i=head[now];i;i=e[i].nxt) if (to!=fa[x][now])
		{
			if (!p[x][to]) p[x][to]=y,q[++T]=to; else 
			if (g[x][to]) g[x][to]=g[to][x]=0,tp.pb(mp(y,to));
		}
	}
	H=0,T=1; g[y][x]=1; p[y][x]=x; q[1]=x;
	while (H<T)
	{
		int now=q[++H]; for (i=head[now];i;i=e[i].nxt) if (to!=fa[y][now])
		{
			if (!p[y][to]) p[y][to]=x,q[++T]=to; else 
			if (g[y][to]) g[y][to]=g[to][y]=0,tp.pb(mp(x,to));
		}
	}
	for (vector <pi>:: iterator it=tp.begin();it!=tp.end();++it)
	insert(it->first,it->second);
}
inline void travel(CI now,int lst,int* fa)
{
	if (p[lst][now]==now) ++ans,lst=now;
	for (RI i=head[now];i;i=e[i].nxt) if (to!=fa[now]) travel(to,lst,fa);
}
int main()
{
	RI i,j; for (scanf("%d%d",&n,&m),i=1;i<n;++i)
	scanf("%d%d",&x,&y),addedge(x,y);
	for (i=1;i<=n;++i) DFS(i,fa[i]); for (i=1;i<=m;++i)
	scanf("%d%d",&x,&y),insert(x,y);
	for (j=1;j<=n;++j) for (i=head[j];i;i=e[i].nxt) travel(to,j,fa[j]);
	return printf("%d",ans>>1),0;
}


Postscript

一个暑假能多打多少AGC呢,能打到25就不错了吧

posted @ 2020-07-31 10:22  空気力学の詩  阅读(167)  评论(0编辑  收藏  举报