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; }