AcWing309 装饰围栏(计数dp+试填法)
我们想要求某一个真正的序列,很多情况下都是用试填法来求取,填一个数上去看看是否满足条件。
所以我们要知道以如果填当前数,那么以当前数为首的剩下的数和c的关系。所以我们知道要预处理出所有满足条件的数的个数,也就是计数问题。
很自然的,前两位要设计为i个数,第一位,也就是最左边这位填的数是第j大,为什么会想到设计最左边,因为我们肯定是从高位开始填,才能每次判断是否已经大于c了。
我们设计相对位置,因为题目的要求也是相对的大小。
继而发现信息还不够,因为题目要求高低的关系,所以再多加一维表示,该位是高位还是低位,高位就从i-1的低位转移,反之同理。
现在考虑转移方程,f[i][j][1]=sum(f[i-1][k][0])这里的k一定要小于j,因为i是高位,反之同理。
之后就是试填法计算是哪个数,首先要特判第一位,因为第一位是高位还是低位没定,后面的都是相对前一位的状态
显然先枚举高位,因为这样的数比较小
还要从小到大枚举x,表示填的数是第几大。因此要st数组记录数有没有被用过。
#include<iostream> #include<algorithm> #include<cstring> using namespace std; typedef long long ll; ll f[21][21][2]; int st[21]; int a[21]; int n; void init(){ int i,j; f[1][1][0]=f[1][1][1]=1; int k; for(i=2;i<=20;i++){ for(j=1;j<=i;j++){ for(k=j;k<i;k++) f[i][j][0]+=f[i-1][k][1]; for(k=1;k<j;k++) f[i][j][1]+=f[i-1][k][0]; } } } int main(){ int t; cin>>t; init(); while(t--){ ll c; cin>>n>>c; int i,j; int k=0; memset(st,0,sizeof st); for(i=1;i<=n;i++){ if(f[n][i][1]>=c){ a[1]=i; st[i]=1; k=1; break; } else{ c-=f[n][i][1]; } if(f[n][i][0]>=c){ a[1]=i; st[i]=1; k=0; break; } else{ c-=f[n][i][0]; } } for(i=2;i<=n;i++){ k^=1; int cnt=1; for(j=1;j<=n;j++){ if(st[j]) continue; if(k==1&&j>a[i-1]||k==0&&j<a[i-1]){ if(f[n-i+1][cnt][k]>=c){ st[j]=1; a[i]=j; break; } else c-=f[n-i+1][cnt][k]; } cnt++; } } for(i=1;i<=n;i++){ cout<<a[i]<<" "; } cout<<endl; } }
没有人不辛苦,只有人不喊疼