Atcoder AGC009 题解

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

A - Multiple Array

从后往前考虑当前数至少要按几次按钮。

注意 a i = 0 a_i=0 的情况。

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

B - Tournament

i i a i a_i 相连,则构成一棵树结构。每个人必须直接打败的人就是他的儿子(啊咧?),而且要一场一场地打败。

f ( x ) f(x) 表示以 x x 为根的子树中的每个人,想要取得这棵子树内比赛的胜利,需要打多少场比赛。则对于当前 x x 的每个儿子, x x 要一场一场地打他们,倒数第 i i 个打的人,想要继续胜利就还要打 i i 场比赛。所以我们把 x x 的儿子按照 f ( y ) f(y) 排序,贪心地决定 x x 打他们的顺序即可。

#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,tot,h[N],ne[N],to[N],st[N],f[N];
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
bool cmp(int x,int y) {return x>y;}
void dfs(int x) {
    int top=0;
    for(RI i=h[x];i;i=ne[i]) dfs(to[i]);
    for(RI i=h[x];i;i=ne[i]) st[++top]=f[to[i]];
    sort(st+1,st+1+top,cmp);
    for(RI i=1;i<=top;++i) f[x]=max(f[x],st[i]+i);
}
int main()
{
    int x;n=read();
    for(RI i=2;i<=n;++i) x=read(),add(x,i);
    dfs(1),printf("%d\n",f[1]);
    return 0;
}

C - Division into Two

容易想到一种DP,就是 f ( i , 0 / 1 ) f(i,0/1) 表示 s i s_i 被划分到哪一个集合,并且 s i + 1 s_{i+1} 被划分到另一个集合的方案数。转移就是 f ( i , 0 / 1 ) = f ( j , 1 / 0 ) f(i,0/1)= \sum f(j,1/0) ,对 [ j + 1 , i ] [j+1,i] 这一段之间的数两两差值大于等于 A / B A/B s i + 1 s j s_{i+1}-s_j 大于等于 B / A B/A

那么这个寻找合法 j j 的过程我们可以优化一下,首先 s i + 1 s j s_{i+1}-s_j 这个条件,我们可以在所有 s s 中二分一下,找到合法 j j 的最大值。而另一个条件,我们可以用RMQ来处理 s s 的差分数组在每个区间内的最小值,然后就也可以二分合法 j j 的最小值。前缀和优化转移即可做到 O ( n log n ) O(n \log n)

#include<bits/stdc++.h>
using namespace std;
#define RI register int
typedef long long LL;
LL read() {
	LL q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10LL+(LL)(ch-'0'),ch=getchar();
	return q;
}
const int mod=1e9+7,N=100005;
const LL inf=2e18;
int n,Log[N],bin[17],f[N][2],sum[N][2];
LL X[2],a[N],mi[17][N];
int qm(int x) {return x>=mod?x-mod:x;}
void prework() {
	bin[0]=1;for(RI i=1;i<=16;++i) bin[i]=bin[i-1]<<1;
	Log[0]=-1;for(RI i=1;i<=n;++i) Log[i]=Log[i>>1]+1;
	for(RI j=1;j<=16;++j)
		for(RI i=1;i+bin[j]-1<=n;++i)
			mi[j][i]=min(mi[j-1][i],mi[j-1][i+bin[j-1]]);
}
LL getmi(int l,int r) {
	if(l>r) return inf;
	int t=Log[r-l+1];
	return min(mi[t][l],mi[t][r-bin[t]+1]);
}
int findl(int x,int o) {
	int l=0,r=x-1,mid,re=0;
	while(l<=r) {
		mid=(l+r)>>1;
		if(getmi(mid+2,x)>=X[o]) re=mid,r=mid-1;
		else l=mid+1;
	}
	return re;
}
int main()
{
	n=read(),X[0]=read(),X[1]=read();
	for(RI i=1;i<=n;++i) a[i]=read(),mi[0][i]=a[i]-a[i-1];
	prework();
	f[0][0]=f[0][1]=sum[0][0]=sum[0][1]=1;
	a[n+1]=inf;
	for(RI i=1;i<=n;++i) {
		int l=findl(i,0);
		int r=upper_bound(a+1,a+i,a[i+1]-X[1])-a-1;
		if(l<=r) f[i][0]=qm(sum[r][1]-(l?sum[l-1][1]:0)+mod);
		l=findl(i,1);
		r=upper_bound(a+1,a+i,a[i+1]-X[0])-a-1;
		if(l<=r) f[i][1]=qm(sum[r][0]-(l?sum[l-1][0]:0)+mod);
		sum[i][0]=qm(sum[i-1][0]+f[i][0]);
		sum[i][1]=qm(sum[i-1][1]+f[i][1]);
	}
	printf("%d\n",qm(f[n][0]+f[n][1]));
	return 0;
}

D - Uninity

我们逆着思维,来找最后一个将若干Uninity k-1的树连接起来的节点,然后将其删掉,在剩下那些Uninity k-1的树中重复这个操作,这就有点像一个任意选择重心点分治(题外话:随机选取重心的点分治就是XZY-点分治了)。假如我们建出分治树,题目要求的就是分治树可能的最小深度。

首先如果我们就按照点分治的方法来建立分治树,树高就是log级别的,所以这就是上界。

现在我们将分治树连根拔起,倒过来,也就是分治树上的叶子的深度为 0 0 ,根的深度为 d e p dep 。然后我们把每个节点在分治树上的深度填回原树上,记作 v v 。则两个 v v 值相同的节点之间,一定有一个 v v 值大于它们的节点。逆过来考虑,只要满足这一点,通过每次将连通块内 v v 值最大节点 x x 选出来当重心,如果发现某个剩下连通块的最大 v v 不是等于 v ( x ) 1 v(x)-1 ,则将该连通块 v v 值不断全部加 1 1 直到满足为止,一定可以建出合法点分树。

所以我们可以来dfs一次原树,记录每棵子树中对于某个 v v 值,到根的路径上还没有出现大于它的 v v 值的有多少个,然后贪心地决定当前点的 v v 值即可。

#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,tot,ans;
int h[N],ne[N<<1],to[N<<1],f[N][22];
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
void dfs(int x,int las) {
	for(RI i=h[x];i;i=ne[i]) {
		if(to[i]==las) continue;
		dfs(to[i],x);
		for(RI j=0;j<=20;++j) f[x][j]+=f[to[i]][j];
	}
	int p=0;
	for(RI i=20;i>=0;--i) if(f[x][i]>=2) {p=i+1;break;}
	//如果有两棵子树中有这个v值,则当前点的v值必须大于它
	while(f[x][p]) ++p;//如果f[x][p]=1,则x到那个v值为p的点之间就没有v值大于它们的点
	++f[x][p];for(RI i=0;i<p;++i) f[x][i]=0;
	ans=max(ans,p);
}
int main()
{
	int x,y;
	n=read();
	for(RI i=1;i<n;++i) x=read(),y=read(),add(x,y),add(y,x);
	dfs(1,0),printf("%d\n",ans);
	return 0;
}

E - Eternal Average

我们把这个问题写成 k k 叉树结构,一共有 n + m n+m 片叶子,其中 n n 片写着 0 0 m m 片写着 1 1 。对于一个非叶子节点,它的值是它儿子们的值的平均数。则根节点的数就是黑板上剩下的那个数。

扫描二维码关注公众号,回复: 3654009 查看本文章

假设那 n n 0 0 的深度分别是 x i x_i m m 1 1 的深度分别是 y i y_i ,则根节点的数就是 i = 1 m ( 1 k ) y i \sum_{i=1}^m (\frac{1}{k})^{y_i} 。并且我们知道如果所有叶节点的数都是 1 1 则根节点也是 1 1 ,所以 i = 1 m ( 1 k ) y i + i = 1 n ( 1 k ) x i \sum_{i=1}^m (\frac{1}{k})^{y_i}+\sum_{i=1}^n (\frac{1}{k})^{x_i} ,而若满足这个条件,一定也能构造出合法的 k k 叉树(考虑 k k 进制小数的进位,则同一深度的叶子节点一定要有 k k 个才能进以位,依此构造即可)

现在的问题转化为,有多少个 z z 满足 z z 可以写成 m m ( 1 k ) y (\frac{1}{k})^y 相加的形式,而 1 z 1-z 又可以写成 n n ( 1 k ) x (\frac{1}{k})^x 相加的形式。(litble的代码里求解的是有多少个满足条件的 1 z 1-z ,这当然也没问题)

z z 写成 k k 进制小数 0. c 1 c 2 c 3 . . . 0.c_1c_2c_3... ,不考虑进位,则 c = m \sum c=m 。考虑还原进位,则可以将 c i c_i 减去 1 1 而将 c i + 1 c_{i+1} 增加 k k ,则 c m ( m o d k 1 ) \sum c \equiv m \pmod{k-1} 。假设小数有 l e n len 位,则 1 z 1-z 的位数和应该是 ( l e n 1 ) ( k 1 ) + k c = l e n ( k 1 ) c + 1 (len-1)(k-1)+k-\sum c=len(k-1)-\sum c+1

f ( i , j ) f(i,j) 表示小数点后 i i 位,每一位的和是 j j 的方案数进行DP即可。因为小数的末尾不能是0,所以当第 i i 位的末尾是否是0也应该加进状态里面。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
const int mod=1e9+7,N=2005;
int n,m,K,ans,f[N<<1][N][2],s[N];
int qm(int x) {return x>=mod?x-mod:x;}
int main()
{
	scanf("%d%d%d",&n,&m,&K);
	f[0][0][0]=1;
	for(RI i=1;i<=max(n,m)*2;++i) {
		s[0]=qm(f[i-1][0][0]+f[i-1][0][1]);
		for(RI j=1;j<=n;++j)
			s[j]=qm(s[j-1]+qm(f[i-1][j][0]+f[i-1][j][1]));
		for(RI j=0;j<=n;++j) {
			f[i][j][0]=qm(s[j]-s[j-1]+mod);
			if(j) f[i][j][1]=qm(s[j-1]-(j-K>=0?s[j-K]:0)+mod);
		}
		for(RI j=0;j<=n;++j)
			if(j%(K-1)==n%(K-1)&&(i*(K-1)-j+1)%(K-1)==m%(K-1)&&i*(K-1)-j+1<=m)
				ans=qm(ans+f[i][j][1]);
	}
	printf("%d\n",ans);
	return 0;
}

猜你喜欢

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