Atcoder AGC008 题解

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 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 24的方块
  2. 一个"L"一个"J"一个"I"组成 2 ∗ 6 2*6 26的方块

分类讨论即可。

#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) (x,k)表示在 a x a_x ax是第 k k k k k k,那么就钦定 a x = k a_x=k ax=k,然后找到最前面的 k − 1 k-1 k1个空位填上 k k k。这么搞完一轮后,再按 x x x从小到大,把剩下的 k 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 p,建一个图,将点 i i i向点 p i p_i pi连边。因为这是个排列,所以每个点出度入度都为 1 1 1,所以一定由若干环构成。

环

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

有四种情况。

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

情况1

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

情况2

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

情况3

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

情况4

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

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

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

基环树

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

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

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

l 2 &gt; l 1 l_2&gt;l_1 l2>l1,有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) f(x,d)表示距离 x x x小于等于 d d d的节点的集合。

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

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

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

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

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

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

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

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

证明:

如果不包含的话,如下图, k 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) f(x,d2+d3)=f(y,d1+d3),从 y y y走到 x x x的距离是 d 1 + d 2 d_1+d_2 d1+d2,走到 x x x再走到 x x x的其他儿子,还能走的距离是 d 3 − d 2 d_3-d_2 d3d2。而对于状态 f ( z , d 3 ) f(z,d_3) f(z,d3),从 z z z走到 x x x后再能走的距离也是 d 3 − d 2 d_3-d_2 d3d2,而且也能走到 k k k,所以 f ( z , d 3 ) = f ( x , d 2 + d 3 ) f(z,d_3)=f(x,d_2+d_3) f(z,d3)=f(x,d2+d3) f ( x , d 2 + d 3 ) f(x,d_2+d_3) f(x,d2+d3)不是与它相同的集合表示中 d 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;
}
AtCoder Beginner Contest 134 是一场 AtCoder 的入门级比赛,以下是每道题的简要题解: A - Dodecagon 题目描述:已知一个正十二边形的边长,求它的面积。 解题思路:正十二边形的内角为 $150^\circ$,因此可以将正十二边形拆分为 12 个等腰三角形,通过三角形面积公式计算面积即可。 B - Golden Apple 题目描述:有 $N$ 个苹果和 $D$ 个盘子,每个盘子最多可以装下 $2D+1$ 个苹果,求最少需要多少个盘子才能装下所有的苹果。 解题思路:每个盘子最多可以装下 $2D+1$ 个苹果,因此可以将苹果平均分配到每个盘子中,可以得到最少需要 $\lceil \frac{N}{2D+1} \rceil$ 个盘子。 C - Exception Handling 题目描述:给定一个长度为 $N$ 的整数序列 $a$,求除了第 $i$ 个数以外的最大值。 解题思路:可以使用两个变量 $m_1$ 和 $m_2$ 分别记录最大值和次大值。遍历整个序列,当当前数不是第 $i$ 个数时,更新最大值和次大值。因此,最后的结果应该是 $m_1$ 或 $m_2$ 中较小的一个。 D - Preparing Boxes 题目描述:有 $N$ 个盒子和 $M$ 个物品,第 $i$ 个盒子可以放入 $a_i$ 个物品,每个物品只能放在一个盒子中。现在需要将所有的物品放入盒子中,每次操作可以将一个盒子内的物品全部取出并分配到其他盒子中,求最少需要多少次操作才能完成任务。 解题思路:首先可以计算出所有盒子中物品的总数 $S$,然后判断是否存在一个盒子的物品数量大于 $\lceil \frac{S}{2} \rceil$,如果存在,则无法完成任务。否则,可以用贪心的思想,每次从物品数量最多的盒子中取出一个物品,放入物品数量最少的盒子中。因为每次操作都会使得物品数量最多的盒子的物品数量减少,而物品数量最少的盒子的物品数量不变或增加,因此这种贪心策略可以保证最少需要的操作次数最小。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值