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$,因此咱們能夠使用惟一素數分解定理把階乘拆成質因子的乘積,而後再乘起來

上代碼:

ios

#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;
}
相關文章
相關標籤/搜索