Luogu P2727 【01串 Stringsobits】
看到题解里好像都是用DP解决的,本着禁止DP的原则,我来提供一发纯数学其实和DP本质相同的题解,前两天刚反演题,脑子炸了,本来说换换脑子,结果还是数学
首先受进制思想启发,我们不妨按位考虑,考虑这一位选一对排列编号造成的影响——即让整个数的编号向后推移了多少
容易想到,这一位选一,编号增加了之后几位满足条件任选的方案数,即第i位选一,cnt表示前几位选了几个一
id+=\sum_{j=0}^{min(i-1,L-cnt)}calc(i-1,j)
clac(x,y)表示前面y位,选x位为一的方案数,这个就是一个可重集排列问题,即
clac(x,y)=\frac{y!}{x!*(y-x)!}
因为n!太大会爆long~long,所以我们可以使用唯一素数分解定理把阶乘拆成质因子的乘积,然后再乘起来
上代码:
#include<iostream> #include<cstdio> #include<cstring> #define int long long using namespace std; int pr[10]={2,3,5,7,11,13,17,19,23,29}; int n,k,rk,cnt,ans[50],cp[20]; void add(int x,int c) { //唯一素数分解 for(int i=1;i<=x;i++) for(int tmp=i,j=0;j<10&&tmp>1;j++) while(tmp%pr[j]==0) tmp/=pr[j],cp[j]+=c; } int make(int x,int y) { //可重集排列 int ret=1; memset(cp,0,sizeof(cp)); add(x,1),add(y,-1),add(x-y,-1); for(int i=0;i<10;i++) for(int j=1;j<=cp[i];j++) ret*=pr[i]; return ret; } signed main() { scanf("%lld%lld%lld",&n,&k,&rk); rk--; //因为有=0的情况,所以rk-1 if(!rk) { for(int i=1;i<=n;i++) printf("0"); printf("\n"); return 0; } for(int i=n;i;i--) { //按位考虑选或不选 int sum=0; for(int j=0;j<=min(i-1,k-cnt);j++) sum+=make(i-1,max(j,i-1-j)); if(rk>=sum) rk-=sum,ans[i]=1,cnt++; } for(int i=n;i;i--) printf("%lld",ans[i]); printf("\n"); return 0; }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步