6641. 【GDOI20205.20模拟】Sequence

题目

构造出一个正整数序列 { a 1 , a 2 , . . . , a m } \{a_1,a_2,...,a_m\} {a1,a2,...,am},满足:
∑ i = 1 m = n \sum_{i=1}^m=n i=1m=n
a i ≥ a i + 1 ∗ p q a_{i}\geq a_{i+1}*\frac{p}{q} aiai+1qp
其中 n n n p , q p,q p,q是给定的, m m m自己定。
求最大的 ∑ i = 1 n a i x k \sum_{i=1}^n a_ix^k i=1naixk
n , p , q ≤ 1 e 9 n,p,q\leq 1e9 n,p,q1e9
k ≤ 1 e 6 k\leq 1e6 k1e6


正解

没有思考历程,因为比赛的时候几乎没有想过这题,也不存在一点思路。
这题是乱搞题。

d = p q d=\frac{p}{q} d=qp,按照 d d d的大小分类讨论:
d ≤ 1 d\leq1 d1时,最优的构造方法是 a i = 1 a_i=1 ai=1
这个给出结论之后就可以很好地感受出来。如果问我为什么是这样,我只能回答无可奉告。
然后就变成了求自然数幂和。
直接套拉格朗日插值法公式,可以做到 O ( k ) O(k) O(k) O ( k lg ⁡ k ) O(k\lg k) O(klgk)(快速幂).。
d > 1 d>1 d>1时,
显然 a i > a i + 1 a_i>a_{i+1} ai>ai+1,所以 m ≤ 2 n m\leq \sqrt{2n} m2n
考虑一个厉害的贪心策略:找到尽量靠后的,并且可以加一的 a i a_i ai。判断是否可以加一,就是在 a i a_i ai加一之后,根据题目的性质往前调整所有 j < i j<i j<i a j a_j aj的值,如果增量不超过 n n n,就可以成立。如果可以成立,就给它加一,前面的也跟着调整, n n n的值减小。一直循环着做下去直到 n n n清零。

可以如此感受:不考虑题目的限制,给后面的数字加一,比起给前面的数字加一是更优的;然后,给尽量后的数字加一,有利于扩展到更优的情况。设想你给 a i a_i ai加一,由于题目限制 a i − 1 a_{i-1} ai1 x x x,这个肯定比直接给 a i − 1 a_{i-1} ai1 x x x优。对于更多的数量关系,也可以类似地考虑。

很显然,如果 a i a_i ai可以加一,则满足 j < i j<i j<i的所有 a j a_j aj都可以加一。
于是这个位置可以二分出来。
每次都加一太慢,于是二分一下最多可以加多少。

什么?担心 a i a_i ai加若干次之后,存在 j > i j>i j>i满足 a j a_j aj可以加一?
别想了,如果这样 a j a_j aj之前早就加一了。

至于时间复杂度分析,呃呃呃看题解吧……
在这里插入图片描述


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cassert>
#define ll long long
#define mo 1000000007
#define M 1000010
ll qpow(ll x,ll y=mo-2){
	ll r=1;
	for (;y;y>>=1,x=x*x%mo)
		if (y&1)
			r=r*x%mo;
	return r;
}
int n,m,k,p,q;
double d;
int a[M];
bool judge(int x,int y){
	if (y>n)
		return 0;
	ll ai=a[x]+y,need=y;
	for (int i=x;i>1;--i){
		ll ai_1=ceil(ai*d);
		need+=ai_1-a[i-1];
		if (need>n)
			return 0;
		ai=ai_1;
	}
	return 1;
}
void add(int x,int y){
	a[x]+=y;
	n-=y;
	for (int i=x;i>1;--i){
		ll ai_1=ceil(a[i]*d);
		n-=ai_1-a[i-1];
		a[i-1]=ai_1;
	}
}
ll fac[M];
ll s[M];
int pri[M],np;
bool inp[M];
ll pro(ll l,ll r){
	ll p=1;
	for (ll i=l;i<=r;++i)
		p=p*(i%mo)%mo;
	return (p+mo)%mo;
}
int main(){
	freopen("sequence.in","r",stdin);
	freopen("sequence.out","w",stdout);
	fac[0]=1;
	for (int i=1;i<=1000001;++i)
		fac[i]=fac[i-1]*i%mo;
	int T;
	scanf("%d",&T);
	while (T--){
		scanf("%d%d%d%d",&n,&k,&p,&q);
		if (p<=q){
			s[0]=0,s[1]=1;
			np=0;
			memset(inp,0,sizeof(bool)*(k+1));
			for (int i=2;i<=k+1;++i){
				if (!inp[i]){
					pri[++np]=i;
					s[i]=qpow(i,k);
				}
				for (int j=1;j<=np && i*pri[j]<=k+1;++j){
					inp[i*pri[j]]=1;
					s[i*pri[j]]=s[i]*s[pri[j]]%mo;
					if (i%pri[j]==0)
						break;
				}
			}
			for (int i=1;i<=k+1;++i)
				(s[i]+=s[i-1])%=mo;
			if (n<=k+1){
				printf("%lld\n",s[n]);
				continue;
			}
			ll sn=0;
			for (int i=0;i<=k+1;++i)
				sn+=qpow((-(i-k-1)&1?mo-1:1)*fac[-(i-k-1)]%mo*fac[i]%mo*(n-i)%mo)%mo*s[i]%mo;
			sn=sn%mo*pro(n-k-1,n)%mo;
			printf("%lld\n",sn);
			continue;
		}
		d=(double)p/q;
		m=sqrt(2*n);
		memset(a,0,sizeof(int)*(m+1));
		while (n){
			int l=1,r=m,x=1,y=1;
			while (l<=r){
				int mid=l+r>>1;
				if (judge(mid,1))
					l=(x=mid)+1;
				else
					r=mid-1;
			}
			l=1,r=n;
			while (l<=r){
				int mid=l+r>>1;
				if (judge(x,mid))
					l=(y=mid)+1;
				else
					r=mid-1;
			}
			add(x,y);
		}
		ll ans=0;
		for (int i=1;i<=m;++i)
			ans+=a[i]*qpow(i,k)%mo;
		ans%=mo;
		printf("%lld\n",ans);
	}
	return 0;
}

总结

比赛时要敢于贪心啊……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值