Atcoder AGC009 题解

本文深入探讨了多项算法竞赛中的核心技巧,包括动态规划优化、树结构分治、重心分治、DP状态压缩等高级算法策略。通过实例解析,详细阐述了如何在复杂问题中运用这些技巧,实现高效解题。

A - Multiple Array

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

注意ai=0a_i=0ai=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

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

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

#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)f(i,0/1)表示sis_isi被划分到哪一个集合,并且si+1s_{i+1}si+1被划分到另一个集合的方案数。转移就是f(i,0/1)=∑f(j,1/0)f(i,0/1)= \sum f(j,1/0)f(i,0/1)=f(j,1/0),对[j+1,i][j+1,i][j+1,i]这一段之间的数两两差值大于等于A/BA/BA/Bsi+1−sjs_{i+1}-s_jsi+1sj大于等于B/AB/AB/A

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

#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级别的,所以这就是上界。

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

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

#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

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

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

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

zzz写成kkk进制小数0.c1c2c3...0.c_1c_2c_3...0.c1c2c3...,不考虑进位,则∑c=m\sum c=mc=m。考虑还原进位,则可以将cic_ici减去111而将ci+1c_{i+1}ci+1增加kkk,则∑c≡m(modk−1)\sum c \equiv m \pmod{k-1}cm(modk1)。假设小数有lenlenlen位,则1−z1-z1z的位数和应该是(len−1)(k−1)+k−∑c=len(k−1)−∑c+1(len-1)(k-1)+k-\sum c=len(k-1)-\sum c+1(len1)(k1)+kc=len(k1)c+1

f(i,j)f(i,j)f(i,j)表示小数点后iii位,每一位的和是jjj的方案数进行DP即可。因为小数的末尾不能是0,所以当第iii位的末尾是否是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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值