AtCoder Grand Contest 008

AtCoder Grand Contest 008

A - Simple Calculator

翻译

有一个计算器,上面有一个显示按钮和两个其他的按钮。初始时,计算器上显示的数字是(x),现在想把这个数字给变成(y)。两个按钮的作用分别是让这个数加一和把这个数取反。问最少的按按钮的次数。

题解

神仙特判题,想清楚再写。

#include<iostream>
using namespace std;
int x,y,ans=2147483647;
int main()
{
	cin>>x>>y;
	if(x==y){puts("0");return 0;}
	if(y>x)ans=min(y-x,abs(y+x)+1);
	else ans=min(abs(x+y)+1,2+abs(x-y));
	printf("%d
",ans);
	return 0;
}

B - Contiguous Repainting

翻译

(n)个格子,每个格子上面都有一个数字,初始时所有格子都是黑色。每次可以选择连续的格子,把他们都染成黑色或者白色。最大化最终状态下黑格子上面的数字和。

题解

我们抛去一个长度为(K)的区间,那么显然的,除了这个区间之外的任何一个格子我们都能够控制它的颜色,而这个区间只能整体为白或者整体为黑,那么讨论一下即可。

#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
#define MAX 101000
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
ll ans,sum,s[MAX],ss[MAX];
int n,k,a[MAX];
int main()
{
	n=read();k=read();
	for(int i=1;i<=n;++i)a[i]=read();
	for(int i=1;i<=n;++i)s[i]=s[i-1]+(a[i]>0?a[i]:0);
	for(int i=1;i<=n;++i)ss[i]=ss[i-1]+a[i];
	for(int i=1;i<=n-k+1;++i)
	{
		sum=s[i-1]+(s[n]-s[i+k-1]);
		if(ss[i+k-1]-ss[i-1]>0)sum+=ss[i+k-1]-ss[i-1];
		ans=max(ans,sum);
	}
	cout<<ans<<endl;
	return 0;
}

C - Tetromino Tiling

翻译

(7)种俄罗斯方块,你现在要用他们拼出一个(2*k)的矩形,最大化(k)

每种方块可以旋转但是不能对称的翻转。(输出的是(k/2))

题解

(C)(B)简单系列。

首先(2*2)的是无脑放。剩下的可以放进去的显然只有(1*4)和两个(L)型的。发现这三个的匹配方法有两个自己匹配和三个各一个拼在一起匹配,算一下即可。

#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
ll ans;
int a[8];
int main()
{
	for(int i=1;i<=7;++i)scanf("%d",&a[i]);
	ans+=a[2];ans+=2*(a[1]>>1);ans+=2*(a[4]>>1);ans+=2*(a[5]>>1);
	if((a[1]&1)&&(a[4]&1)&&(a[5]&1))ans+=3;
	else if((a[1]&1)&&(a[4]&1)&&(!(a[5]&1))&&a[5])ans+=1;
	else if((a[1]&1)&&(!(a[4]&1))&&(a[5]&1)&&a[4])ans+=1;
	else if((!(a[1]&1))&&(a[4]&1)&&(a[5]&1)&&a[1])ans+=1;
	cout<<ans<<endl;
	return 0;
}

D - K-th K

翻译

给定一个长度为(n)的序列(x),构造一个长度为(n*n)的序列(a),保证([1,n])中的每个数都恰好出现了(n)次,并且第(i)(i)出现的位置是(x[i])

题解

我说前面几题里面最傻逼的就是(D)题?

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAX 505
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
int n,pos=1,a[MAX*MAX],p[MAX],x[MAX];
bool vis[MAX];
bool cmp(int a,int b){return x[a]<x[b];}
int main()
{
	n=read();
	for(int i=1;i<=n;++i)x[i]=read(),a[x[i]]=i,p[i]=i;
	sort(&p[1],&p[n+1],cmp);
	for(int i=1;i<=n;++i)
	{
		int v=p[i];
		for(int j=1;j<v;++j)
		{
			while(a[pos])++pos;
			a[pos]=v;
		}
		if(pos>x[v]){puts("No");return 0;}
	}
	for(int i=1;i<=n;++i)
	{
		int v=p[i];
		for(int j=1;j<=n-v;++j)
		{
			while(a[pos])++pos;
			if(pos<x[v]){puts("No");return 0;}
			a[pos]=v;
		}
	}
	puts("Yes");
	for(int i=1;i<=n*n;++i)printf("%d ",a[i]);puts("");
	return 0;
}

E - Next or Nextnext

翻译

给定一个长度为(n)的序列(a)。求满足要么(P_i=a_i),要么(P_{P_i}=a_i)的排列(P)的个数。

题解

我们把所有的(i)(a_i)连边,那么对于一个合法的(P),两点之间要么跳一步,要么跳两步。

那么我们考虑这两种跳法可以构成几种不同的情况(对于一个环考虑)。

  • 所有点都跳了一步。那么显然就是原图。
  • 所有点都跳了两部。那么如果当前的环是一个奇环,那么显然它跳完之后还是一个奇环,只不过是点的顺序改变了一下,即得到了一个同构的环。如果当前的环是一个偶环,那么他就被拆分成了两个小环。
  • 一部分是奇环,一部分是偶环。那么这样子会形成一个环,然后在它的外面形成了一些"脚",即连进来的一些边。同时所有的"脚"都不会从同一个节点伸展出来,并且所有的脚都是一条链的形式。

(i)连向(a_i)构成的图称之为原图,(P)构建出来的图称为新图,显然原图是由若干个部分组成的。同理(P)构建出来的图也只能基于原图,通过上面三种情况(如果奇偶环分开考虑就是四种),归类之后只有两种情况,一种是环,另外一种是长出了"脚",即基环内向树,这些东西构成。

先考虑原图构成的环,对于每个环要么就是原图,要么对于奇环而言是同构的图,要么是通过偶环被拆分成了两个等大小的图。那么我们把所有等大小的环一起考虑,考虑所有的方案数。在合并两个等大小的环的时候注意一下有环大小种方法合并。

再考虑基环内向树的情况,显然两两之间不能合并,所以我们分开考虑每一个的答案。首先对于两条腿,我们最多只有两种方法将一条腿和环合并(这里画不了图,直接看(atcoder)上面的题解就有图)。现在考虑合并两条腿的情况,设(l1)为第二条腿的长度,(l2)是两条腿在挂在环上的那个点的之间的距离。如果(l1lt l2),那么有(2)种方法合并进来,如果(l1=l2),那么只有一种方法,否则(0)种方法。

代码里面有注释。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define MOD 1000000007
#define MAX 100100
#define ll long long
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
int n,ans=1;
int a[MAX],dg[MAX];
bool cir[MAX];
int col[MAX],foot[MAX],cnt[MAX];
int f[MAX];
int main()
{
	n=read();
	for(int i=1;i<=n;++i)a[i]=read(),++dg[a[i]];
	for(int i=1;i<=n;++i)
	{
		if(col[i])continue;
		int x=i;
		for(;!col[x];x=a[x])col[x]=i;
		if(col[x]!=i)continue;//circle with foot
		for(;!cir[x];x=a[x])cir[x]=true;//circle
	}
	for(int i=1;i<=n;++i)//illegal part 
		if((cir[i]&&dg[i]>2)||(!cir[i]&&dg[i]>1)){puts("0");return 0;}
	for(int i=1;i<=n;++i)
	{
		if(dg[i])continue;//start of a foot do not have egde in
		int x=i,len=0;
		while(!cir[x])x=a[x],++len;
		foot[x]=len;//record length
	}
	for(int i=1;i<=n;++i)//calculate answer of circle with feet
	{
		if(!cir[i])continue;//calculate circles
		int x=i,pts=0,st=0,l1=0,fr=0;
		//st:last root of foot,fr:first root of foot,l1:length of the first foot
		//pts:number of the node in the circle
		while(cir[x])
		{
			++pts;cir[x]=false;
			if(foot[x])//a foot in the circle
			{
				if(!fr)st=fr=pts,l1=foot[x];
				else
				{
					int ways=(foot[x]<(pts-st))+(foot[x]<=(pts-st));
					ans=ans*ways%MOD;st=pts;
				}
			}
			x=a[x];
		}
		if(fr)//first foot of the circle
		{
			int ways=(l1<(pts+fr-st))+(l1<=(pts+fr-st));
			ans=ans*ways%MOD;
		}
		else//just a circle
			cnt[pts]+=1;
	}
	for(int i=1;i<=n;++i)//dp,in order to calc each length of circles
	{
		if(!cnt[i])continue;
		f[0]=1;
		for(int j=1;j<=cnt[i];++j)
		{
			if(i>1&&(i&1))//odd circle
				f[j]=(f[j-1]<<1)%MOD;//two ways
			else//even circle
				f[j]=f[j-1];//only one way
			if(j>1)f[j]=(f[j]+1ll*f[j-2]*(j-1)%MOD*i%MOD)%MOD;
			//merge two circle in the same length
		}
		ans=1ll*ans*f[cnt[i]]%MOD;
	}
	printf("%d
",ans);
	return 0;
}

F - Black Radius

题面

给定一个(n)个节点的树。有些点可以染色,每次染色操作为选定一个可以染色的点,以及一个距离,把距离这个点不超过钦定值的所有点全部染黑。初始时所有点都是白色。恰好可以进行一次染色,求最终所有点染色情况的方案数。

题解

抄题解.jpg。

定义(f(x,d))表示距离(x)不超过(d)的点集。显然答案就是询问本质不同的(f(x,d))的个数。考虑(S=f(x,d1)=f(y,d_2)),那么对于(x)(y)路径上的任何一点(K),都有(S=f(K,d_1-dist(x,K)))

我们现在在假设所有点都可以染黑的情况下来讨论答案。首先(f(x,d))不考虑全集,最后再把全集加入答案即可。另外,对于(x)的任意一个相邻节点(v),不存在(f(x,d)=f(v,d-1))。第一个限制很容易理解。考虑如何理解第二个限制,根据上面那个有关于两者等价的式子,需要满足路径上任意两点都等于当前点集,那么,当不存在相邻点(v)满足(f(x,d)=f(v,d-1))的时候,证明(x)一定在这条路径的中点,因此也只会被考虑计算一次。

满足第一个限制的(d)的范围很好计算,只需要求出距离当前点的最远点即可。考虑第二个限制如何计算。想清楚什么时候会有(f(x,d)=f(v,d-1))。如果以(x)为根,考虑距离(v)不超过(d-1)的所有点,如果除(v)自己的子树之外,所有(x)的子树都被染黑了,那么此时等式成立。那么在第二个限制中,(d)的范围是什么呢?显然不能超过到达它自己子树的最远距离。

这样只考虑了(d)的上界,而在一个点可以被染黑的时候,显然(d)的下界是(0)。现在考虑(d)不能被染黑时它的下界。我们先钦定(x)作为当前的树根。如果(f(x,d))合法的话,必定满足一个可以被染黑的节点它的整棵子树都被染黑了,那么这里的下界就是这个可以被染黑的子树的最大深度。

然而我也不太懂(抄题解)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define MAX 200100
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
const int inf=1e9;
struct Line{int v,next;}e[MAX<<1];
int h[MAX],cnt=1,dg[MAX];
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;++dg[u];}
int n,c[MAX],fa[MAX],sz[MAX];ll ans;
int S[MAX],top;
char s[MAX];
int d1[MAX];//子树中距离最远的一个点
int d2[MAX];//同上,可以从父亲转移
int d3[MAX];//子树中有经过至少一个黑点的最远点
int d5[MAX];//上界
int d6[MAX];//下界
void dfs1(int u,int ff)
{
	fa[u]=ff;sz[u]=c[u];
	if((u!=1&&dg[u]==1)||(u==1&&!dg[u]))//叶子节点
	{
		d1[u]=0;d3[u]=c[u]?0:inf;
		return;
	}
	int mx=0,mx2=inf;
	for(int i=h[u];i;i=e[i].next)
	{
		int v=e[i].v;if(v==ff)continue;
		dfs1(v,u);sz[u]+=sz[v];
		mx=max(mx,d1[v]);
		if(d3[v]<inf)mx2=min(mx2,d1[v]);
	}
	d1[u]=mx+1;d3[u]=mx2+1;
	if(c[u])d3[u]=min(d3[u],d1[u]);
}
void dfs2(int u,int ff)
{
	d2[u]=max(d2[u],(ff!=0)+d2[ff]);//从父亲转移过来
	if(ff)d5[u]=max(d5[u],d2[u]-1);//维护上界
	top=0;for(int i=h[u];i;i=e[i].next)if(e[i].v!=ff)S[++top]=e[i].v;
	for(int i=1,mx=-1;i<=top;++i)//前缀其他儿子转移一下
	{
		int v=S[i];
		d2[v]=max(d2[v],mx+2);
		mx=max(mx,d1[v]);
	}
	for(int i=top,mx=-1;i>=1;--i)//后缀其他儿子转移一下
	{
		int v=S[i];
		d2[v]=max(d2[v],mx+2);
		mx=max(mx,d1[v]);
	}
	for(int i=h[u];i;i=e[i].next)
		if(e[i].v!=ff)dfs2(e[i].v,u);
}
int main()
{
	n=read();
	for(int i=1;i<n;++i)
	{
		int u=read(),v=read();
		Add(u,v);Add(v,u);
	}
	scanf("%s",s+1);int tot=0;
	for(int i=1;i<=n;++i)tot+=(c[i]=s[i]-48);
	if(!tot){puts("0");return 0;}
	memset(d3,63,sizeof(d3));
	dfs1(1,0);dfs2(1,0);
	for(int i=1;i<=n;++i)
	{
		if(c[i])d6[i]=0;//可以涂色的点
		else d6[i]=min(d3[i],(sz[i]==sz[1])?inf:d2[i]);
		//不能涂色的点考虑下界
		int u=max(d1[i],d2[i])-1;//u为上界,初始为最短点距离减一
		for(int j=h[i];j;j=e[j].next)
		{
			int v=e[j].v;
			if(v==fa[i])u=min(u,d1[i]+1);//如果从父亲转移过来,
			else u=min(u,d5[v]+1);
		}
		if(u>=d6[i])ans+=u-d6[i]+1;
	}
	cout<<ans+1<<endl;
	return 0;
}
原文地址:https://www.cnblogs.com/cjyyb/p/9699261.html