Atcoder AGC007 题解

A - Shik and Stone

只要判断一下’#'的数量即可,如果只向右和下走的话,走过的格子数量一定是 n + m − 1 n+m-1 n+m1个。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int n,m,js;char ch[10];
int main()
{
	scanf("%d%d",&n,&m);
	for(RI i=1;i<=n;++i) {
		scanf("%s",ch+1);
		for(RI j=1;j<=m;++j) js+=(ch[j]=='#');
	}
	if(js==n+m-1) puts("Possible");
	else puts("Impossible");
	return 0;
}

B - Construct Sequences

先将a和b构造成两个等差数列,公差 d d d比较大(至少大于 n n n吧),假设其中最大项是 s s s(显然 s = 1 + ( n − 1 ) d s=1+(n-1)d s=1+(n1)d)。

a i + b i = ( 1 + ( i − 1 ) d ) + ( 1 + ( n − i + 1 ) d ) = 2 + ( n − 1 ) d a_i+b_i=(1+(i-1)d)+(1+(n-i+1)d)=2+(n-1)d ai+bi=(1+(i1)d)+(1+(ni+1)d)=2+(n1)d

我们只要让 a p i a_{p_i} api增加 i − 1 i-1 i1,就可以让 a p i + b p i = s + i a_{p_i}+b_{p_i}=s+i api+bpi=s+i了。

而由于公差大于 n n n,所以这么增加还是能让序列a满足条件。

#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 N=100005;
int n,a[N],b[N];
int main()
{
	int x;
	n=read();
	for(RI i=1;i<=n;++i) a[i]=b[n-i+1]=1+(i-1)*(n+1);
	for(RI i=1;i<=n;++i) x=read(),a[x]+=i-1;
	for(RI i=1;i<=n;++i) printf("%d ",a[i]);
	puts("");
	for(RI i=1;i<=n;++i) printf("%d ",b[i]);
	puts("");
	return 0;
}

C - Pushing Balls

我们来到操场的沙坑上,赶走在练习跳远的人,然后挖四个坑,然后找体育组借三个实心球放在四个坑直接,那么这些物品两两的初始距离是:

d d d d + x d+x d+x d + 2 x d+2x d+2x d + 3 x d+3x d+3x d + 4 x d+4x d+4x d + 5 x d+5x d+5x

考虑一下不同的推球方法,当一个球进入一个坑后,就默不作声地用沙子将这个球埋起来,假装什么都没有发生过,将这些球重新编号。你会发现物品两两间距离有这么几种可能(表格头的编号是重编过的):

情况坑1与球1球1与坑2坑2与球2球2与坑3
球1入坑1 d + 2 x d+2x d+2x d + 3 x d+3x d+3x d + 4 x d+4x d+4x d + 5 x d+5x d+5x
球1入坑2 3 d + 3 x 3d+3x 3d+3x d + 3 x d+3x d+3x d + 4 x d+4x d+4x d + 5 x d+5x d+5x
球2入坑2 d d d 3 d + 6 x 3d+6x 3d+6x d + 4 x d+4x d+4x d + 5 x d+5x d+5x
球2入坑3 d d d d + x d+x d+x 3 d + 9 x 3d+9x 3d+9x d + 5 x d+5x d+5x
球3入坑3 d d d d + x d+x d+x d + 2 x d+2x d+2x 3 d + 12 x 3d+12x 3d+12x
球3入坑4 d d d d + x d+x d+x d + 2 x d+2x d+2x d + 3 x d+3x d+3x
期望 8 d + 5 x 6 \frac{8d+5x}{6} 68d+5x 8 d + 15 x 6 \frac{8d+15x}{6} 68d+15x 8 d + 25 x 6 \frac{8d+25x}{6} 68d+25x 8 d + 35 x 6 \frac{8d+35x}{6} 68d+35x

哇,好神奇啊,期望的物品间距离居然还是个等差数列~

你会对这个发现感到异常惊奇,但还是不要忘了把实心球挖出来还回去。

现在来观察,等差数列的第一项就是我们表格的坑1与球1这一列,你会发现不管初始用几个球,这一列只有前两行是 d + 2 x d+2x d+2x 3 d + 3 x 3d+3x 3d+3x,其他行都是 d d d。所以 d ′ = d + 2 d + 5 x 2 n d&#x27;=d+\frac{2d+5x}{2n} d=d+2n2d+5x。至于公差,我们观察一下球2与坑2这一列,发现这一列的期望永远会是 d + x + 2 d + 9 x 2 n d+x+\frac{2d+9x}{2n} d+x+2n2d+9x,所以公差 x ′ = x + 4 x 2 n x&#x27;=x+\frac{4x}{2n} x=x+2n4x

这样我们做 n n n次对 d d d x x x(其实还有 n n n别忘了)的变换,即可得到答案。

oh,对,至于每种局面下的推球期望距离,应该是 d + 2 n − 1 2 x d+\frac{2n-1}{2x} d+2x2n1,具体为什么自己推(zhǎo)一(guī)推(lǜ)吧。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
typedef double db;
int n;db d,x,ans;
int main()
{
	scanf("%d%lf%lf",&n,&d,&x);
	db nn=n+n;
	for(RI i=n;i>=1;--i) {
		ans+=d+(nn-1)/2*x;
		db dd=d+(2*d+5*x)/nn;
		db xx=x+4*x/nn;
		x=xx,d=dd,nn-=2;
	}
	printf("%.10lf\n",ans);
	return 0;
}

D - Shik and Game

显然我们每次给一个区间的熊喂完糖,然后走到区间的第一只熊那里,依次捡金币过去即可。那么DP(只考虑捡金币的时间,正常走到出口的时间另加即可)

f i = m i n ( f j + m a x ( T , 2 ( d i − d j + 1 ) ) f_i=min(f_j+max(T,2(d_i-d_{j+1})) fi=min(fj+max(T,2(didj+1))

就维护一下满足 2 ( d i − d j + 1 ) 2(d_i-d_{j+1}) 2(didj+1) j j j中最小的 f j − 2 d j + 1 f_j-2d_{j+1} fj2dj+1即可,因为 f i f_i fi单调不降,所以从第一不满足 2 ( d i − d k + 1 ) 2(d_i-d_{k+1}) 2(didk+1) k k k处做 f k + T f_k+T fk+T这个转移即可。

#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=100005;
int n;LL f[N],d[N],mi,E,T;
int main()
{
	n=read(),E=read(),T=read();
	for(RI i=1;i<=n;++i) d[i]=read();
	mi=1e15;
	for(RI i=1,j=0;i<=n;++i) {
		while(j<i&&(d[i]-d[j+1])*2>T) mi=min(mi,f[j]-2*d[j+1]),++j;
		f[i]=mi+2*d[i];
		if(j<i&&f[j]+T<f[i]) f[i]=f[j]+T;
	}
	printf("%lld\n",f[n]+E);
	return 0;
}

E - Shik and Travel

首先二分答案,那么每次叶子到叶子之间的距离就不能超过我们二分出的答案。

由于每条边必须且只能走两次,所以必须一棵子树里的叶子都被走完,才能走别的叶子。我们发现不断合并即可。

假设要合并一个节点 x x x的左子树和右子树,到左儿子和右儿子的边权分别是 v 0 v_0 v0 v 1 v_1 v1

每棵子树上维护一堆数对 ( a , b ) (a,b) (a,b),表示从这个子树的根走到第一片叶子的费用是 a a a,最后一片叶子走到根的距离是 b b b。假设左子树有数对 ( a , b ) (a,b) (a,b),右子树有数对 ( c , d ) (c,d) (c,d),且 b + c + v 0 + v 1 ≤ a n s b+c+v_0+v_1 \leq ans b+c+v0+v1ans,则可以合出数对 ( a + v 0 , d + v 1 ) (a+v_0,d+v_1) (a+v0,d+v1)。假如这个 ( a , b ) (a,b) (a,b)已经被钦定了,我们只要找出满足条件的右子树中 d d d最小的一对来合并即可。

于是我们将数对数量较少的那个,分别钦定为 ( a , b ) (a,b) (a,b) ( c , d ) (c,d) (c,d),做两次合并,每次合出的数对数量不会超过两倍数量较少的那个子树的数对数量,类似于启发式合并,复杂度是 O ( n log ⁡ n ) O(n \log n) O(nlogn)的。如果到根节点还有数对,说明这个 a n s ans ans要大于等于最终答案,否则它小于最终答案。

总复杂度 O ( n log ⁡ n log ⁡ a n s ) O(n \log n \log ans) O(nlognlogans)

#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=132000;
int n;
int s[N][2];LL v[N][2];
typedef pair<LL,LL> PR;
vector<PR> q[N];
bool cmpx(PR a,PR b) {return a.first<b.first;}
bool cmpy(PR a,PR b) {return a.second<b.second;}

void dfs(int x,int lim) {
	if(!s[x][0]) {q[x].push_back((PR){0,0});return;}
	dfs(s[x][0],lim),dfs(s[x][1],lim);
	int s0=s[x][0],s1=s[x][1],v0=v[x][0],v1=v[x][1];
	int sz0=q[s0].size(),sz1=q[s1].size();
	if(sz0>sz1) swap(s0,s1),swap(v0,v1),swap(sz0,sz1);
	LL mi=1e9;
	sort(q[s0].begin(),q[s0].end(),cmpy);
	sort(q[s1].begin(),q[s1].end(),cmpx);
	for(RI i=sz0-1,j=0;i>=0;--i) {
		while(j<sz1&&q[s1][j].first+q[s0][i].second+v1+v0<=lim)
			mi=min(mi,q[s1][j].second),++j;
		if(j) q[x].push_back((PR){q[s0][i].first+v0,mi+v1});
	}
	sort(q[s0].begin(),q[s0].end(),cmpx);
	sort(q[s1].begin(),q[s1].end(),cmpy);
	mi=1e9;
	for(RI i=sz0-1,j=0;i>=0;--i) {
		while(j<sz1&&q[s1][j].second+q[s0][i].first+v1+v0<=lim)
			mi=min(mi,q[s1][j].first),++j;
		if(j) q[x].push_back((PR){mi+v1,q[s0][i].second+v0});
	}
	q[s0].clear(),q[s1].clear();
}
int main()
{
	int x,y;
	n=read();
	for(RI i=2;i<=n;++i) {
		x=read(),y=read();
		if(s[x][0]) s[x][1]=i,v[x][1]=y;
		else s[x][0]=i,v[x][0]=y;
	}
	LL l=0,r=1e9,res=1e9;
	while(l<=r) {
		LL mid=(l+r)>>1;q[1].clear(),dfs(1,mid);
		if(q[1].empty()) l=mid+1;
		else res=mid,r=mid-1;
	}
	printf("%lld\n",res);
	return 0;
}

F - Shik and Copying String

又一道玄学大神题。

首先我们会发现,此题就是从某个S字母出发,每次向下方或者右下方或者右方走,直到走到T的与该字母相同的位置,所有的路径不能相交,问中间至少要填充几行。

我们可以维护T中每一个字母必须要由S中哪个字母抵达,连续的相同字母则由S中同一个字母抵达即可。

灵魂画手litble

首先判断ans=0的情况。然后我们先留一行,用于左右走动。

连续一段相同的字母就用一个相同的起点即可。

会产生若干起点区间,折线紧紧地相贴。假如这个区间有 k k k条折线,对于最后面的一个起点 p p p,每个拐点的往前数 k − 1 k-1 k1个格子都会被折线控制。如此如此,我们玄学地用队列维护一下区间,区间里有 k k k条折线,则至少要 k k k行。(加上左右走动的行,是 k + 1 k+1 k+1行)

呃,要我的理解有哪里错了,欢迎把我打一顿然后告诉我哪错了QAQ

#include<bits/stdc++.h>
using namespace std;
#define RI register int
const int N=1000005;
int n,ans,he,ta,q[N];
char S[N],T[N];
int main()
{
	scanf("%d",&n);
	scanf("%s",S+1),scanf("%s",T+1);
	if(!strcmp(S+1,T+1)) {puts("0");return 0;}
	he=1;
	for(RI i=n,j=n;i>=1;--i) {
		if(T[i]==T[i-1]) continue;
		while(j&&(S[j]!=T[i]||j>i)) --j;
		if(!j) {puts("-1");return 0;}
		while(he<=ta&&q[he]-(ta-he)>i) ++he;
		q[++ta]=j;if(i!=j) ans=max(ans,ta-he+1);
	}
	printf("%d\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、付费专栏及课程。

余额充值