首页 > 技术文章 > AtCoder Grand Contest 014题解

yuanquming 2019-09-20 17:54 原文

传送门

\(A\)

首先大力猜测一下答案不会很大,所以次数大于\(10^6\)输出\(-1\)就行了

不过我并不会证上界,据说是因为如果\(a=b=c\)且都是偶数肯定\(-1\),否则设\(a\leq b\leq c\),则最大最小值的差为\(c-a\),一次操作之后变成了\({c-a\over 2}\),所以操作次数就是\(\log\)级别的了

typedef long long ll;
ll a,b,c,sum;int res;
int main(){
	scanf("%lld%lld%lld",&a,&b,&c);
	sum=a+b+c;
	while(true){
		if((a&1)|(b&1)|(c&1))return printf("%d\n",res),0;
		a=(sum-a)>>1,b=(sum-b)>>1,c=(sum-c)>>1;
		if(++res>1e6)return puts("-1"),0;
	}
	return 0;
}

\(B\)

既然题目要求每条边被覆盖偶数次,那么一定可以被拆分成若干个联通边集且这个边集里每条边都被覆盖\(2\)次,而这个边集肯定形如若干条\((a_1,a_2),(a_2,a_3),...,(a_n,a_1)\)的路径的并,再转化一下就是在新图中将\((u,v)\)连边,且新图可以被拆分成若干条欧拉回路。那么充要条件就是每个点度数都为偶数

//quming
#include<bits/stdc++.h>
#define R register
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
const int N=2e5+5;
int d[N],n,m;
int main(){
	scanf("%d%d",&n,&m);
	for(R int i=1,u,v;i<=m;++i)scanf("%d%d",&u,&v),++d[u],++d[v];
	fp(i,1,n)if(d[i]&1)return puts("NO"),0;
	puts("YES");
	return 0;
}

\(C\)

肯定是先随便走到一个点,然后消走消走的就可以直接往一个方向冲了,那么\(bfs\)找出所有一开始能到的点,然后看看从哪个点开始冲就是了

//quming
#include<bits/stdc++.h>
#define R register
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
const int N=1005;
const int dx[]={0,1,0,-1};
const int dy[]={1,0,-1,0};
char mp[N][N];int vis[N][N],dis[N][N],qx[N*N],qy[N*N],n,m,k,res,sx,sy;
inline int calc(R int x,R int y){
	cmin(res,(x-1+k-1)/k),cmin(res,(n-x+k-1)/k);
	cmin(res,(y-1+k-1)/k),cmin(res,(m-y+k-1)/k);
}
void bfs(){
	int h=1,t=0,x,y,tx,ty;qx[++t]=sx,qy[t]=sy,vis[sx][sy]=1,dis[sx][sy]=0;
	while(h<=t){
		x=qx[h],y=qy[h++];
		if(dis[x][y]==k)continue;
		fp(i,0,3){
			tx=x+dx[i],ty=y+dy[i];
			if(!vis[tx][ty]&&tx>=1&&tx<=n&&ty>=1&&ty<=m)
				++t,qx[t]=tx,qy[t]=ty,vis[tx][ty]=1,dis[tx][ty]=dis[x][y]+1;
		}
	}
	fp(i,1,t)calc(qx[i],qy[i]);
}
int main(){
	scanf("%d%d%d",&n,&m,&k),res=2333333;
	fp(i,1,n){
		scanf("%s",mp[i]+1);
		fp(j,1,m){
			if(mp[i][j]=='S')sx=i,sy=j;
			if(mp[i][j]=='#')vis[i][j]=1;
		}
	}
	bfs();
	printf("%d\n",res+1);
	return 0;
}

\(D\)

显然曲明最擅长的就是猜测出假的结论

首先给出结论:后手必胜当且仅当这棵树有完美匹配

这棵树有完美匹配的时候,因为先手染什么颜色后手只要染它的匹配,就能保证每个白色节点有至少一个与它相邻的黑色节点了

如果一个点连着两个以上的叶子必定是先手必胜了,所以接下来我们默认所有的点连着的叶子个数小于等于\(1\)

如果没有完美匹配,我们随便找一个点为根,然后对于一个叶子,先手把它的父亲染白,那么后手必须把这个叶子染黑。然后我们把这两个点从树中删去,继续操作。显然树不会被删完,否则就存在完美匹配了。此时我们随便染白一个还未染的点就赢了

于是充要性都有了

求完美匹配直接从叶子开始贪心就是

//quming
#include<bits/stdc++.h>
#define R register
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
const int N=2e5+5;
struct eg{int v,nx;}e[N<<1];int head[N],tot;
inline void add(R int u,R int v){e[++tot]={v,head[u]},head[u]=tot;}
int dep[N],vis[N],fa[N],n;
void dfs(int u,int fa){
	go(u)if(v!=fa)dfs(v,u);
	if(!vis[u]&&vis[fa]){puts("First");exit(0);}
	if(!vis[u])vis[u]=vis[fa]=1;
}
int main(){
//	freopen("testdata.in","r",stdin);
	scanf("%d",&n);
	for(R int i=1,u,v;i<n;++i)scanf("%d%d",&u,&v),add(u,v),add(v,u);
	vis[0]=1;dfs(1,0);
	puts("Second");
	return 0;
}

\(E\)

容易发现,一次操作合法,当且仅当删掉蓝边和删掉红边之后两边点集是一样的

那么我们考虑倒着加边,则如果两个连通块之间多于\(1\)条边,那么必定是一条红边一条蓝边,且这个操作可以进行,那么我们把这两个连通块缩成一个点就好了

关于维护连通块直接的边集,可以用\(multiset\),然后启发式合并就行了

//quming
#include<bits/stdc++.h>
#define R register
#define fi first
#define se second
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
typedef pair<int,int> pi;
const int N=2e5+5;
multiset<int>to[N];map<pi,int>mp;queue<pi>q;int n;
inline void ins(R int u,R int v){
	if(u==v)return;
	if(u>v)swap(u,v);
	++mp[pi(u,v)],to[u].insert(v),to[v].insert(u);
	if(mp[pi(u,v)]==2)q.push(pi(u,v));
}
inline void del(R int u,R int v){
	if(u>v)swap(u,v);
	if(mp.count(pi(u,v)))mp.erase(pi(u,v));
}
inline void merge(R int u,R int v){
	for(auto w:to[v])del(w,v),to[w].erase(to[w].find(v)),ins(w,u);
}
int main(){
	scanf("%d",&n);
	for(R int i=1,u,v;i<=n*2-2;++i)scanf("%d%d",&u,&v),ins(u,v);
	fp(i,1,n-1){
		while(233){
			if(q.empty())return puts("NO"),0;
			R pi t=q.front();q.pop();
			if(mp.count(t)){
				if(to[t.fi].size()<to[t.se].size())swap(t.fi,t.se);
				merge(t.fi,t.se);
				break;
			}
		}
	}
	puts("YES");
	return 0;
}

\(F\)

好神仙的题啊……

这篇题解比起其他题解的优势是翻译的比较全

首先我们发现,如果去掉\(1\)是不会改变每个元素是\(low\)还是\(high\)的,那么我们去掉\(1\),假设需要\(T\)次令\(2\)\(n\)有序,如果此时\(1\)已经在最前面了,那么答案就是\(T\),否则是\(T+1\)

如果\(T=0\),那么答案是\(T\)还是\(T+1\)很好判断。否则我们考虑进行了\(T-1\)次操作之后的序列,令\(f\)\(2\)\(n\)中排在最前面的元素,易知\(f>2\)(如果\(f=2\)的话,可以用反证法证明,要么此时\(2\)\(n\)已经有序,要么再进行一次操作之后仍不有序)。然后我们发现,当且仅当此时三个数的相对位置形如\((f,1,2)\)时,答案为\(T\),否则为\(T+1\)

然后我们要证明这三个数在环上的相对位置是不变的,也就是说如果初始时相对位置为\((1,2,f)\),那么变化过程中相对位置只会有\((1,2,f),(2,f,1),(f,1,2)\)三种情况

先来证明另一件事,就是\(f\)如果不是在\(2\)\(n\)中所有数的最前面,它必定是一个\(low\)元素。这一段中考虑的元素只有\(2\)\(n\)。考虑反证法,假设\(x\)是一个不在第一位的\(high\)元素,因为第一位必定是\(high\),所以一次操作之后必定存在一个\(y\)使得\(y<x\)\(y\)\(x\)前面,接下来的操作过程中,如果\(y,x\)类型相同它们必定同时移动,而它们分开当且仅当某个时刻\(y\)\(low\)\(x\)\(high\),不过因为此时\(x\)依然不是第一个元素,所以又陷入了之前那种情况。所以可以发现如果\(f\)是一个不在最前面的\(high\)元素它无论如何都不可能在\(T-1\)次操作后到达最前面,所以上面那个结论成立

然后开始分类讨论了

  • 如果\(1\)在第一个
    • 如果\(2\)在第二个,那么\(1,2\)\(high\)\(f\)\(low\),环不会变
    • 如果\(f\)是第二个,那么\(1,f\)\(high\)\(2\)\(low\),环不会变
    • 否则\(1\)\(high\)\(2,f\)\(low\),环不会变
  • 如果\(2\)是第一个,那么\(2\)\(high\)\(1,f\)\(low\),环不会变
  • 如果\(f\)是第一个,那么\(f\)\(high\)\(1,2\)\(low\),环不会变
  • 否则\(1,2,f\)都是\(low\),环不会变

然后我们来倒推,设\(T_i\)表示只考虑\(i\)\(n\)的元素时的最小次数,\(f_i\)表示此时对应的\(f\),如果\(T_i=0\)\(f_i\)未定义。初值为\(T_1=0\),答案为\(T_1\)

我们设\(q_i\)表示\(i\)的位置,即\(p_{q_i}=i\),然后递推过程如下

  • 如果\(T_{i+1}=0\)
    • 如果\(q_i>q_{i+1}\),则\(T_i=1,f_i=i+1\)
    • 否则\(T_i=0\)
  • 否则
    • 如果三个元素的环按照\(q_{f_{i+1}},q_i,q_{i+1}\)的顺序,则\(T_i=T_{i+1},f_i=f_{i+1}\)
    • 否则\(T_i=T_{i+1}+1,f_i=i+1\)

时间复杂度\(O(n)\)

//quming
#include<bits/stdc++.h>
#define R register
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
const int N=2e5+5;
int p[N],q[N],f[N],t[N],n;
int main(){
	scanf("%d",&n);
	fp(i,1,n)scanf("%d",&p[i]),q[p[i]]=i;
	t[n]=0;
	fd(i,n-1,1)
		if(!t[i+1])q[i]>q[i+1]?(t[i]=1,f[i]=i+1):t[i]=0;
		else{
			if((q[f[i+1]]<q[i])+(q[i]<q[i+1])+(q[i+1]<q[f[i+1]])==2)
				t[i]=t[i+1],f[i]=f[i+1];
			else t[i]=t[i+1]+1,f[i]=i+1;
		}
	printf("%d\n",t[1]);
	return 0;
}

推荐阅读