[洛谷P3672]小清新签到题
题目描述
题目还是简单一点好。
给定自然数n、k、x,你要求出第k小的长度为n的逆序对对数为x的1~n的排列a1,a2...an
,然后用仙人图上在线分支定界启发式带花树上下界最小费用流解决问题,保证存在。输入格式
一行三个自然数n、k、x。
输出格式
输出满足条件的排列,一行n个数,用空格分隔。
题目都说了:用仙人图上在线分支定界启发式带花树上下界最小费用流解决问题就可以了
看数据范围猜是DP......
我们可以用$f[i][j]$表示序列长度为i,其中有j对逆序对时的可能组数,转移也很显然:
$$f[i][j]=\sum_{k=max(0,i-j+1)}^j f[i-1][k]$$
大意就是累加i-1时有k对逆序对,放第i+1个数时因为放的地方不一样,新产生的逆序对个数也不一样,全部加起来即可
不过有几个问题
①虽然上面这个转移看起来是$O(n^3)$的,但是因为逆序对个数可以达到$\frac{n*(n-1)}{2}$个,所以其实是$O(n^4)$的
解决方案:观察转移方程我们可以发现$f[i][j]$必定是$f[i-1][j]$中的一段连续和,所以可以用前缀和优化
②显然这样做会爆$int$
解决方案:所以我们需要开$long long$
③显然这样做会爆$long long$
解决方案:我们设定一个$inf$值如果$f[i][j]$已经比$inf$大了继续累加就没有意义了,所以直接把$f[i][j]$设为$inf$即可
④显然开了两个大数组($f[i][j]$和$g[i][j]$)会被毒瘤出题人卡空间
解决方案:观察到$f[i][j]$和$g[i][j]$都之和前一个状态有关,那就把第一维压掉,但是$f[i][j]$在后面输出序列的时候是要用到的,所以压掉$g[i][j]$数组即可
DP结束了之后直接乱搞就好了,枚举每一位的大小$i$,比较$f[now][x-i+1]$和$k$的大小,如果$k>f[now][x-i+1]$就直接减掉,最后刚好凑齐的f[i][j]肯定就是最后答案了
1 #include<bits/stdc++.h> 2 #define int long long 3 #define writep(x) write(x),putchar(' ') 4 using namespace std; 5 inline int read(){ 6 int ans=0,f=1;char chr=getchar(); 7 while(!isdigit(chr)){if(chr=='-') f=-1;chr=getchar();} 8 while(isdigit(chr)){ans=(ans<<3)+(ans<<1)+chr-48;chr=getchar();} 9 return ans*f; 10 }void write(int x){ 11 if(x<0) putchar('-'),x=-x; 12 if(x>9) write(x/10); 13 putchar(x%10+'0'); 14 }const int M = 3e2+5,inf=1e13+1; 15 int f[M][M*M>>1],a[M],n,x,k,ans[M],vis[M],cnt,g[2][M*M>>1]; 16 void Solve(int now){ 17 if(!now)return; 18 for(int i=1;i<=now;i++) 19 if(f[now-1][x-i+1]<k)k-=f[now-1][x-i+1]; 20 else{ans[now]=a[i],x=x-i+1,vis[a[i]]=1; 21 for(int j=1,cnt=0;j<=n;j++)if(!vis[j])a[++cnt]=j; 22 return (void)(Solve(now-1)); 23 } 24 }signed main(){ 25 n=read(),k=read(),x=read(); 26 for(int i=0;i<=n;i++)a[i]=i,f[i][0]=1; 27 for(int i=1;i<=n;i++){ 28 for(int j=1;j<=(i*i-i)/2;j++)f[i][j]=min(inf,g[i&1^1][j+1]-g[i&1^1][max(j-i+1,0ll)]); 29 for(int j=0;j<=(n*n-n)/2;j++)g[i&1][j+1]=g[i&1][j]+f[i][j]; 30 }Solve(n); 31 for(int i=n;i>=1;i--)writep(ans[i]);
32 return 0; 33 }