P2518 [HAOI2010] 计数

//URL:https://www.luogu.com.cn/problem/P2518
/*
现在给定一个数,你可以删掉这个数中的任意多个数位 00(或不删)
并将其他的数位任意重新排序。
请求出能产生出多少个不同的这个数小的数(注意这个数不会有前导 0)

例如,给你一个排列:2 4 5 3 1,求字典序比它小的排列有几个。
先看第一位。当第一位小于2时后4位无论怎样都比原排列小。而小于2且未被使用的数只有1一个。
因此答案加上1×4!。当第一位等于2时,后4位必须小于原排列后4位。
此时将2标记为已使用,继续循环。
看第二位。第一位小于4的数去除已使用的2还有2个,因此答案加上2×3!。
然后将4标记为已使用,继续处理第二位为4的情况。
以此类推,可算得原排列康托展开为1×4!+2×3!+2×2!+1×1!+0×0!=41
可重复元素排列的康托展开也用同样的方法处理。

先存储0~9中每个元素的个数。对于每一位,枚举所有小于原排列中当前位的数
。然后计算其他未被使用的数的全排列加入答案。
最后将原排列中当前位的元素的值的个数减一,继续循环。具体看代码。
算可重复元素全排列个数的方法其他几位dalao已经讲的很清楚了

有重复元素全排列个数是:n!/n1!∗n2!∗⋯∗nk!
==C(n1,n)*C(n1+n2,n-n1)*..C(n1+..n_n-1,n-n1-..n_n-1)

*/
/*
1020

7
*/
/*
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<queue>
#include<vector>
#include<bits/stdc++.h>
typedef long long ll;
#define ddd printf("-----------------------\n");
using namespace std;
const int maxn=1e1 +10;
const int mod=998244353;
const int inf=0x3f3f3f3f;

ll ans,c[52][52];
int a[11],n[52],len;

ll muti(int a[],int l)
{
    ll res=1;
    for(int i=0;i<=9;i++)
    {
        res*=c[l][a[i]];
        l-=a[i];
    }
    return res;
}

int main()
{
    ios::sync_with_stdio(false);
   // ll tmp;cin>>tmp;
   for(char ch=getchar();ch>='0'&&ch<='9';ch=getchar()) a[n[++len]=ch-'0']++;          
    
    //cout<<len<<endl<<n[4]<<endl<<a[1]<<endl;
    
    c[0][0]=1;
    for(int i=1;i<=len;i++)
    {
        c[i][i]=c[i][0]=1;
        for(int j=1;j<i;j++) c[i][j]=c[i-1][j]+c[i-1][j-1];
    }
    for(int i=1;i<=len;i++)
    {
        for(int j=0;j<n[i];j++)
        {
            if(a[j]){
                a[j]--;
                ans+=muti(a,len-i);
                a[j]++;
            }
        }
        a[n[i]]--;
    }
    cout<<ans<<'\n';
    
    return 0;
}
*/

#include<cstdio>
#include<cstring>
#include<cctype>
#include<utility>
#include<algorithm>
#include<iostream>
using namespace std;
#define res register int
#define ll long long
const ll prime[16]={0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47}; //质数表,只要打到这里就够了。
char s[51];
int tong[16],num[10],len1,sum,a[51],len; //tong数组记录质因子出现次数
inline void ins(int x) { //乘法
    for(res i=1;i<=15;++i)
      while(!(x%prime[i])&&x) ++tong[i],x/=prime[i];
}
inline void del(int x) { //除法
    for(res i=1;i<=15;++i)
      while(!(x%prime[i])&&x) --tong[i],x/=prime[i];
}
inline ll query(int len,int s) {//计算全排列 sum(ai)/muti(ai!)
    if(len<sum-s) return 0; //这里有个细节:如果剩下的非0个数仍大于需要填的数,此时不可能达到排列,直接返回0。
    memset(tong,0,sizeof(tong));
    for(res i=1;i<=len;++i) ins(i);
    for(res i=1;i<=9;len-=num[i++]) for(res j=1;j<=num[i];++j) del(j);
    for(res i=1;i<=len;++i) del(i); //剩下的len就是0的个数,也需要除
    ll tot=1;
    for(res i=1;i<=15;++i)
      for(res j=1;j<=tong[i];++j) tot*=prime[i]; //最后把每一项乘起来就能得到答案了。
    return tot;
}
ll dfs(int len,int s,bool ff) {
    ll tot=0;
    if(!len) return s==sum;//return 1
    if(!ff) return query(len,s); //没有限制直接求答案
    int end=ff?a[len]:9;
    for(res i=0;i<=end;++i) {
        if(!num[i]&&i) continue;//i!=0&&num[i]==0
        --num[i],tot+=dfs(len-1,s+(i>0),ff&&i==end),++num[i]; //回溯
    }
    return tot;
}
int main()
{
    cin.sync_with_stdio(false); //sync加速(貌似这题没什么用)
    cin>>s,len1=strlen(s);
    for(res i=len1-1;~i;--i) {
        if(s[i]^48) ++num[s[i]-48],++sum; //sum表示非0数个数,num记录每个数出现次数。
        a[++len]=s[i]-48;
    }
    printf("%lld\n",dfs(len,0,true)-1);
    return 0;
}

 

posted @ 2024-02-05 04:22  JMXZ  阅读(15)  评论(0)    收藏  举报