Atcoder AGC008 题解

版权声明:这篇文章的作者是个蒟蒻,没有转载价值,如果要转说一下好了 https://blog.csdn.net/litble/article/details/83118814

A - Simple Calculator

分情况讨论一下,要仔细一点。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int x,y;
int main()
{
	scanf("%d%d",&x,&y);
	if(x==y) puts("0");
	else if(y>x) printf("%d\n",min(y-x,abs(y+x)+1));
	else printf("%d\n",min(abs(x+y)+1,2+abs(x-y)));
	return 0;
}

B - Contiguous Repainting

除了最后一次染色的那个长度为 k k 的区间以外的所有格子,都可以任意确定颜色(方法就是先贴着整个序列两侧用区间涂色,然后慢慢地把涂色区间向中间挪,这样就可以一格一格地确定颜色)。

那么只要用前缀和处理一下,枚举这个最后涂色的区间即可。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0,w=1;char ch=' ';
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') w=-1,ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q*w;
}
const int N=100005;
typedef long long LL;
int n,K;LL a[N],s1[N],s2[N],ans;
int main()
{
	n=read(),K=read();
	for(RI i=1;i<=n;++i) {
		LL x=read();
		s1[i]=s1[i-1]+max(0LL,x),s2[i]=s2[i-1]+x;
	}
	for(RI i=K;i<=n;++i) {
		ans=max(ans,s2[i]-s2[i-K]+s1[i-K]+s1[n]-s1[i]);
		ans=max(ans,s1[i-K]+s1[n]-s1[i]);
	}
	printf("%lld\n",ans);
	return 0;
}

C - Tetromino Tiling

"o"型的方块直接放就行了,剩下的只有:

  1. 两个"L"或者两个"J"或两个"I"组成 2 4 2*4 的方块
  2. 一个"L"一个"J"一个"I"组成 2 6 2*6 的方块

分类讨论即可。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
typedef long long LL;
LL ans,a,b,c,d,e,f,g;
int main()
{
	scanf("%lld%lld%lld%lld%lld%lld%lld",&a,&b,&c,&d,&e,&f,&g);
	ans+=b;
	if(a&&d&&e&&((a&1)+(d&1)+(e&1)>=2)) --a,--d,--e,ans+=3;
	ans+=2*(a/2+d/2+e/2);
	printf("%lld\n",ans);
	return 0;
}

D - K-th K

这个…你就按照被钦定的位置从小到大排序,假设数对 ( x , k ) (x,k) 表示在 a x a_x 是第 k k k k ,那么就钦定 a x = k a_x=k ,然后找到最前面的 k 1 k-1 个空位填上 k k 。这么搞完一轮后,再按 x x 从小到大,把剩下的 k k 往剩下的空位里填,同时判断合不合法即可。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int a[250005],x[505],n;
struct node{int v,x;}t[505];
bool cmp(node k1,node k2) {return k1.x<k2.x;}
int main()
{
	int x;scanf("%d",&n);
	for(RI i=1;i<=n;++i) scanf("%d",&x),a[x]=i,t[i]=(node){i,x};
	sort(t+1,t+1+n,cmp);
	int j=1;
	for(RI i=1;i<=n;++i) {
		for(RI k=1;k<=t[i].v-1;++k) {
			while(j<n*n&&a[j]) ++j;
			if(a[j]||j>t[i].x) {puts("No");return 0;}
			a[j]=t[i].v;
		}
	}
	for(RI i=1;i<=n;++i) {
		for(RI k=t[i].v+1;k<=n;++k) {
			while(j<n*n&&a[j]) ++j;
			if(a[j]||j<t[i].x) {puts("No");return 0;}
			a[j]=t[i].v;
		}
	}
	puts("Yes");
	for(RI i=1;i<=n*n;++i) printf("%d ",a[i]);
	return 0;
}

E - Next or Nextnext

水完了前面四道水题,就获得了一道神题。

litble不生产题解,litble只是官方题解的翻译工,以下图都是从官方题解搬过来的。

我们考虑最终的那个排列 p p ,建一个图,将点 i i 向点 p i p_i 连边。因为这是个排列,所以每个点出度入度都为 1 1 ,所以一定由若干环构成。

环

考虑其中的一个环,我们擦掉它的所有边,然后将 i i a i a_i 连边,可以知道 a i a_i 是它前面一个节点或者前面的前面一个节点。

有四种情况。

  1. 所有 i i a i a_i 都是它的前面一个节点,则环保持不变。

情况1

  1. 所有 i i a i a_i 都是它前面的前面的节点,且环为奇环,则环变成同构的另一个环

情况2

  1. 所有 i i a i a_i 都是它前面的前面的点,且原环为偶环,则这个环会被拆成两个相同大小的环。

情况3

  1. 有的是前面一个节点,有的又是前面的前面,则变成了一棵由一个环和若干指向环的链构成的基环内向树。

情况4

行吧,现在我们手头上只有由 a a 构成的那张图,没有由 p p 构成的那张图,所以我们就要反过来考虑了。

首先我们找到所有的环,先记录每个大小的环有多少个,那么每种大小可以单独考虑,DP一下,决策就是这种大小的第 k k 个环是和前面的环合并呢,还是单独组成 p p 图中的环。最后用乘法原理乘起来这些东西。

对于一棵基环内向树,我们考虑相邻两个“脚”(即挂在环上的链),将“脚”往环里面塞,并且要求还是一条边与它指着的节点中间最多只能插一个节点。大致如下图:

基环树

这个脚可以塞到树里的位置,就是到下一个脚之间的边,假设这些边有 l 2 l_2 条,这个脚的长度为 l 1 l_1 ,那么:

l 2 &lt; l 1 l_2&lt;l_1 ,有0种方案。

l 2 = l 1 l_2=l_1 ,有1种方案。

l 2 &gt; l 1 l_2&gt;l_1 ,有2种方案。

也可以用乘法原理搞,就做完了。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
const int mod=1e9+7,N=100005;
int n,ans;
int a[N],du[N],cir[N],vis[N],footL[N],sum[N],f[N];
int qm(int x) {return x>=mod?x-mod:x;}
void workcir(int x) {
	int now=0,fr=0,ed=0,frL=0;
	//fr:第一个有脚的位置,ed:上一个找到的有脚的位置
	//frL:第一个脚的长度,now:当前节点是从x开始走环走到的第几个点
	while(cir[x]) {
		++now,cir[x]=0;
		if(footL[x]) {
			if(!fr) ed=fr=now,frL=footL[x];
			else {//塞脚
				int kl=(footL[x]<now-ed)+(footL[x]<=now-ed);
				ans=1LL*ans*kl%mod,ed=now;
			}
		}
		x=a[x];
	}
	if(!fr) ++sum[now];//是简单环
	else {//考虑第一个脚
		int kl=(frL<now-ed+fr)+(frL<=now-ed+fr);
		ans=1LL*ans*kl%mod;
	}
}
void work() {
	for(RI i=1;i<=n;++i) {
		if(du[i]) continue;
		int x=i,len=0;while(!cir[x]) x=a[x],++len;
		footL[x]=len;//算挂在每个点上的脚长
	}
	ans=1;
	for(RI i=1;i<=n;++i) if(cir[i]) workcir(i);
	for(RI i=1;i<=n;++i) {//对每一种长度的简单环做DP
		if(!sum[i]) continue;
		f[0]=1;
		for(RI j=1;j<=sum[i];++j) {
			if(i>1&&(i&1)) f[j]=qm(f[j-1]+f[j-1]);//情况1,2
			else f[j]=f[j-1];//情况1
			if(j>1) f[j]=qm(f[j]+1LL*f[j-2]*(j-1)%mod*i%mod);//情况3
		}
		ans=1LL*ans*f[sum[i]]%mod;
	}
}
int main()
{
	n=read();
	for(RI i=1;i<=n;++i) a[i]=read(),++du[a[i]];
	for(RI i=1;i<=n;++i) {
		if(vis[i]) continue;
		int x=i;while(!vis[x]) vis[x]=i,x=a[x];
		if(vis[x]!=i) continue;//说明i在一个脚上
		while(!cir[x]) cir[x]=1,x=a[x];//给环打上是环标记
	}
	for(RI i=1;i<=n;++i)//判无解
		if((cir[i]&&du[i]>2)||(!cir[i]&&du[i]>1)) {puts("0");return 0;}
	work();
	printf("%d\n",ans);
	return 0;
}

F - Black Radius

先假设树上所有点都是关键点。然后设 f ( x , d ) f(x,d) 表示距离 x x 小于等于 d d 的节点的集合。

我们想一个精妙的不重不漏计数方法,假设有若干 f ( x , d ) f(x,d) 同构,我们希望只在 d d 最小的那个位置计算贡献,则我们计算满足以下条件的数对个数:

  1. f ( x , d ) f(x,d) 不覆盖整棵树(计数完成后让答案+1即可统计整棵树被染色的情况)
  2. 对于与 x x 相邻的点 y y ,都不存在 f ( x , d ) = f ( y , d 1 ) f(x,d)=f(y,d-1) 对于与 x x 相邻的点 y y ,都不存在 f ( x , d ) = f ( y , d 1 ) f(x,d)=f(y,d-1)

所以我们发现,每个点上可以取的 d d 存在一个上界。

设离 x x 最远的点离 x x 的距离为 m x ( x ) mx(x) ,显然条件1等价于 d &lt; m x ( x ) d&lt;mx(x)

而条件2,考虑若存在一个这样的 y y ,把 x x 看做树根。由于 y y 能够染周围 d 1 d-1 的点,所以在 y y 子树里的点,若存在于 f ( x , d ) f(x,d) 中必然存在在 f ( y , d 1 ) f(y,d-1) 中,反之不存在于。而若在 y y 子树以外的地方有一个点 z z x x 的距离大于 d 2 d-2 ,则 f ( y , d 1 ) f(y,d-1) 中没有它,而 f ( x , d ) f(x,d) 中有它。

因此若设 s e ( x ) se(x) 表示删掉 y y 子树后剩余节点中找一个点,使得 x x 离它的距离最远,则条件2满足等价于 d 2 &lt; s e ( x ) d-2&lt;se(x) 。显然在 y y 子树里存在到 x x 距离最远的点时, s e ( x ) se(x) 最小,最能产生约束。

现在看有的点不是关键点的情况。

那么对于不是关键点的点 x x ,若要 f ( x , d ) f(x,d) 是与它相同的集合表示中, d d 最小的那个,且也是一个关键点 y y 的某种染色集合,则这个集合必须包含以 x x 为根,它的儿子们的子树中,包含 y y 的那个子树中的所有节点。(有点拗口啊QAQ)

证明:

如果不包含的话,如下图, k k 以下的部分没包含,则 f ( x , d 2 + d 3 ) = f ( y , d 1 + d 3 ) f(x,d_2+d_3)=f(y,d_1+d_3) ,从 y y 走到 x x 的距离是 d 1 + d 2 d_1+d_2 ,走到 x x 再走到 x x 的其他儿子,还能走的距离是 d 3 d 2 d_3-d_2 。而对于状态 f ( z , d 3 ) f(z,d_3) ,从 z z 走到 x x 后再能走的距离也是 d 3 d 2 d_3-d_2 ,而且也能走到 k k ,所以 f ( z , d 3 ) = f ( x , d 2 + d 3 ) f(z,d_3)=f(x,d_2+d_3) f ( x , d 2 + d 3 ) f(x,d_2+d_3) 不是与它相同的集合表示中 d d 最小的那个。
灵魂画手litble

于是我们就做换根DP即可。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
typedef long long LL;
const int N=200005,inf=0x3f3f3f3f;
int h[N],ne[N<<1],to[N<<1],mx[N],se[N],d[N],sz[N];
int n,tot;LL ans;char S[N];

void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
void dfs1(int x,int las) {
	if(S[x]=='1') d[x]=0,sz[x]=1;
	else d[x]=inf;
	for(RI i=h[x];i;i=ne[i]) {
		if(to[i]==las) continue;
		int y=to[i];dfs1(y,x),sz[x]+=sz[y];
		if(mx[y]+1>mx[x]) se[x]=mx[x],mx[x]=mx[y]+1;
		else if(mx[y]+1>se[x]) se[x]=mx[y]+1;
		if(sz[y]) d[x]=min(d[x],mx[y]+1);
	}
}
void dfs2(int x,int las) {
	int R=min(se[x]+1,mx[x]-1);
	if(d[x]<=R) ans+=(LL)(R-d[x]+1);
	for(RI i=h[x];i;i=ne[i]) {
		if(to[i]==las) continue;
		int y=to[i],kl=(mx[y]+1==mx[x]?se[x]+1:mx[x]+1);
		if(kl>mx[y]) se[y]=mx[y],mx[y]=kl;
		else if(kl>se[y]) se[y]=kl;
		if(sz[1]-sz[y]) d[y]=min(d[y],kl);
		dfs2(y,x);
	}
}
int main()
{
	int x,y;
	n=read();
	for(RI i=1;i<n;++i) x=read(),y=read(),add(x,y),add(y,x);
	scanf("%s",S+1);
	dfs1(1,0),dfs2(1,0);
	printf("%lld\n",ans+1);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/litble/article/details/83118814
今日推荐