小清新签到题(DP)
Posted yyys-
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了小清新签到题(DP)相关的知识,希望对你有一定的参考价值。
然鹅我并不觉得这道题很清新rua
思维巧妙!(参考)
对于第k小,我们可以这样考虑,若是第k小,那么比它小的方案应该是有k-1个。
在排列组合中,若固定i放在j位置,方案数是确定的,即:i固定在j位置,满足这个条件的序列的rank是在一个范围内的。
对于逆序对
常见思考方式是从小到大枚举数字,考虑对逆序对个数做出的贡献(从小到大插入,后插入不会对前插入造成影响)
设f[i][j]为i个数,j个逆序对的方案数,可得转移方程为
f[i][j]=f[i-1][j]+f[i-1][j-1]+……+f[i-1][j-i+1]
(就相当于给一个比序列中所有数都大的数,让你往里插。)
可用前缀和优化(注意是一层一层的前缀和而不是二维前缀和)
然后我们可以一个个从后往前放数,枚举每一位可以放什么数(1~n),统计放这个数所造成的贡献num,以及之前可以放的数(但因为达不到k而被放弃)的方案数tmp。(但tmp在算这一次放的数是否达到逆序对要求时要加上)
注意f数组容易爆LL,但k的范围是1e13,所以超过了就改为k+1即可。
#include<bits/stdc++.h> #define LL long long #define N 303 using namespace std; LL read() LL x=0,f=1;char s=getchar(); while(s<‘0‘||s>‘9‘)if(s==‘-‘)f=-1;s=getchar(); while(s>=‘0‘&&s<=‘9‘)x=x*10+s-‘0‘;s=getchar(); return x*f; LL f[N][N*N],s[N][N*N];//f[i][j]i个数j个逆序对的方案数,s前缀和 int ans[N],vis[N]; void init(LL n,LL k,LL x) f[0][0]=1; for(int i=0;i<=x;++i)s[0][i]=1;//注意逆序对数可以取到0 for(int i=1;i<=n;++i) for(int j=0;j<=x;++j) int l=max(0,j-i+1),r=j; LL tmp=(l==0)?s[i-1][r]:(s[i-1][r]-s[i-1][l-1]); f[i][j]=min(tmp,k+1); s[i][j]=f[i][j]+s[i][j-1]; int main() LL n=read(),k=read(),x=read(); init(n,k,x); // for(int i=1;i<=n;++i) // for(int j=1;j<=x;++j) // printf("%lld\\n",f[i][j]); for(int i=n;i>=1;--i) LL tmp=0; for(int j=1;j<=n;++j) if(vis[j])continue; int num=j-1;//对逆序对个数最多产生的贡献,也就是比它小的都在它后面这种情况 for(int l=1;l<j;++l)num-=vis[l]; if(tmp+f[i-1][x-num]>=k) ans[n-i+1]=j;//可以确定这个位置填j vis[j]=1;x-=num;k-=tmp; break; tmp+=f[i-1][x-num];//!!! for(int i=1;i<=n;++i) printf("%d ",ans[i]);
以上是关于小清新签到题(DP)的主要内容,如果未能解决你的问题,请参考以下文章
[luoguP3694] 邦邦的大合唱站队/签到题(状压DP)
Educational Codeforces Round 110 (Rated for Div. 2) (AB签到,C题双指针,D题DP好题)