题解 P2518 【[HAOI2010]计数】

C3H5ClO

2019-06-12 14:22:31

Solution

~~总感觉题面很不清楚啊,看了题解才搞明白~~ 搞了半天才发现,这不就是可重复元素排列的康托展开!0~9数字随便排,求一下有多少排列字典序比题目给的排列小。 先说普通的康托展开吧。其实运用了数位DP的思想。 例如,给你一个排列: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已经讲的很清楚了。 ```cpp #include<cstdio> using namespace std; #define ri register int #define ll long long ll ans,c[55][55]; int a[10],len,n[55],cnt; ll multiqpl(int a[],int l) { ll res=1; for(ri i=0;i<=9;i++) { res*=c[l][a[i]]; l-=a[i]; } return res; } int main() { for(char ch=getchar();ch>='0'&&ch<='9';ch=getchar())a[n[++len]=ch-48]++; c[0][0]=1; for(ri i=1;i<=len;i++) { c[i][0]=c[i][i]=1; for(ri j=1;j<i;j++)c[i][j]=c[i-1][j]+c[i-1][j-1]; } for(ri i=1;i<=len;i++) { cnt=0; for(ri j=0;j<n[i];j++) if(a[j]) { a[j]--;//将j放到第i位上 ans+=multiqpl(a,len-i); a[j]++;//将j拿走,换下一个 } a[n[i]]--; } printf("%lld",ans); } ```