Atcoder AGC007 题解

A - Shik and Stone

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

#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构造成两个等差数列,公差dd比较大(至少大于nn吧),假设其中最大项是ss(显然s=1+(n1)ds=1+(n-1)d)。

ai+bi=(1+(i1)d)+(1+(ni+1)d)=2+(n1)da_i+b_i=(1+(i-1)d)+(1+(n-i+1)d)=2+(n-1)d

我们只要让apia_{p_i}增加i1i-1,就可以让api+bpi=s+ia_{p_i}+b_{p_i}=s+i了。

而由于公差大于nn,所以这么增加还是能让序列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

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

ddd+xd+xd+2xd+2xd+3xd+3xd+4xd+4xd+5xd+5x

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

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

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

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

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

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

oh,对,至于每种局面下的推球期望距离,应该是d+2n12xd+\frac{2n-1}{2x},具体为什么自己推(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(只考虑捡金币的时间,正常走到出口的时间另加即可)

fi=min(fj+max(T,2(didj+1))f_i=min(f_j+max(T,2(d_i-d_{j+1}))

就维护一下满足2(didj+1)2(d_i-d_{j+1})jj中最小的fj2dj+1f_j-2d_{j+1}即可,因为fif_i单调不降,所以从第一不满足2(didk+1)2(d_i-d_{k+1})kk处做fk+Tf_k+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

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

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

假设要合并一个节点xx的左子树和右子树,到左儿子和右儿子的边权分别是v0v_0v1v_1

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

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

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

#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中同一个字母抵达即可。

Atcoder AGC007 题解

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

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

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