cnblogs
如果只是询问方案数,是 P2467 [SDOI2010] 地精部落 这道题,设 \(f_{i,j,1/0}\) 表示以 \(j\) 个数中从小到大的第 \(i\) 个数处于高/低位开头的方案数。
显然有
朴素的转移是 \(O(n^3)\) 的,拿前缀和优化一下可以做到 \(O(n^2)\)。
处理出来这个后,我们利用倍增的思想查找每一位应该停留在哪个数字上,如果加上这个数字的方案数后,当前总方案数大于等于 \(C\),则这一位应该填这个数字。
具体来说,可以通过记录这一位处于高位还是低位,然后找出每一位后递归下一位来实现。具体看代码。
#include<bits/stdc++.h>
#define int long long
typedef long long ll;
typedef unsigned long long ull;
inline int read(){char ch=getchar();int x=0,f=1;for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);return x*f;}
const int N=25;
int n,f[N][N][N],C,ans[N];
inline void work(int x,int now,int sum,int pd){
ans[n-sum]=x;
if(!sum)return;
if(pd==-1||pd==1){
for(int i=1;i<x;++i){
if(now+f[i][sum][0]>=C){
work(i,now,sum-1,0);
return;
}
now+=f[i][sum][0];
}
}
if(pd==-1||pd==0){
for(int i=x;i<=sum;++i){
if(now+f[i][sum][1]>=C){
work(i,now,sum-1,1);
return;
}
now+=f[i][sum][1];
}
}
\\ 递归查找,pd=1表示当前处于高位找低位,pd=0反之,pd=-1表示当前是开头,找高低位无所谓。
}
signed main(){
// freopen("in.in","r",stdin);freopen("out.out","w",stdout);
std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
f[1][1][0]=1;f[1][1][1]=1;
for(int j=2;j<=20;++j){
for(int i=1;i<=j;++i){
for(int k=1;k<i;++k){
f[i][j][1]+=f[k][j-1][0];
}
for(int k=i;k<=j-1;++k){
f[i][j][0]+=f[k][j-1][1];
}
}
}
int T=read();
while(T--){
memset(ans,0,sizeof(ans));
n=read(),C=read();
int st=0,zc=0;
for(int i=1;i<=n;++i){
zc+=f[i][n][0]+f[i][n][1];
if(zc>=C){st=i;zc-=f[i][n][0]+f[i][n][1];break;}
}
work(st,zc,n-1,-1);
std::vector<int> v;
for(int i=1;i<=n;++i)v.push_back(i);
v.erase(v.begin()+st-1);
for(int i=2;i<=n;++i){
int zc=ans[i];
ans[i]=v[ans[i]-1];
v.erase(v.begin()+zc-1);
}
for(int i=1;i<=n;++i)std::cout<<ans[i]<<' ';std::cout<<'\n';
}
}
标签:decorative,ch,int,题解,sum,long,CEOI2002,pd,now
From: https://www.cnblogs.com/Ishar-zdl/p/18240544