• 6641. 【GDOI20205.20模拟】Sequence


    题目

    构造出一个正整数序列({a_1,a_2,...,a_m}),满足:
    (sum_{i=1}^m=n)
    (a_{i}geq a_{i+1}*frac{p}{q})
    其中(n)(p,q)是给定的,(m)自己定。
    求最大的(sum_{i=1}^n a_ix^k)
    (n,p,qleq 1e9)
    (kleq 1e6)


    正解

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

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

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

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

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

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


    代码

    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
    ",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
    ",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
    ",ans);
    	}
    	return 0;
    }
    

    总结

    比赛时要敢于贪心啊……

  • 相关阅读:
    如何在SpringMVC中使用REST风格的url
    c#实现的udt
    数据库查询服务化-缓存和分页
    c#常用数据库封装再次升级
    c#数据库连接池Hikari重构升级
    c# 常用数据库封装
    聊聊数据存储查询
    c#分析SQL语句
    c# 分析SQL语句中的表操作
    c#最近博文结尾
  • 原文地址:https://www.cnblogs.com/jz-597/p/12951101.html
Copyright © 2020-2023  润新知