很好的一道数位DP,昨天下午卡了一个小时,连题解都看不懂,然后就去刷数论题了,今天终于回来将这道题A掉了,看了很多大神的博客,感觉自己对于全排列的认识又增进了几分。
这道题要求能组成的数有几个比给出的数小,其中牵扯到了‘0’的问题,在输入中,只给出了最多能添加几个零,却没有规定一定要添加几个,去看了大神的博客,发现自己太天真了,不添加‘0’相当于把‘0’拿到最前面,然后问题就就转化成了将字符串中的这些数字全排列,有几个比当前的数字小。
然后就开始数位DP过程,看大神的博客中写的都比较简洁,我这里尽力明了一些。
一个数比另一个数小,一定有某个“决定位”,即该位前的数字两数相同,该位上的数字两数一大一小,而我们的数位DP就是以”决定位“为阶段来考虑的。
我们枚举每一位,当这一位是决定位时,次位之前的数字一定和原数字相同,即被占用,我们需要维护一下占用情况。然后只要把一个比原数字小的数放在当前为上,后面的数就可以自由排列了,既然我们一直都维护这数字的占用情况,那么我们就可以DP了,这题并没有什么具体的方程,如果有,那本蒟蒻就给你们写一个语文老师教的方程。
ans=∑dp[1..n] ;dp[i]=数字集合中除去i位之前的数以及假设放在i位上的数字剩下的数字的全排列。注意这里假设的数是小于原数的第i位的,是要枚举的.
至于如何在不爆long long 的情况下求全排列数,这里引用了一位大神的博文:(涨姿势了 Orz)。
“假如现在有m个位置;
我们先把0放放好
C(m,a[0]);
之后就只有m-a[0]个位置;
然后在放1
C(m-a[0],a[1]);
所以答案是
C(m,a[0])xC(m-a[0],a[1])x…xC(m-a[0]-a[1]-..-a[8],a[9]);
就可以算全排列;”
转载出处:http://m.blog.csdn.net/largecub233/article/details/56014712
下面是我的代码:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<algorithm>
#define ll unsigned long long
using namespace std;
int num[100];
int a[10];
int c[1001][1001];
string s;
int n;
int total;
ll ans;
ll getans(int now,int nownum)
{
ll nowans=1;
total=n-now;
for(int i=0;i<=9;i++)
{
if(a[i])
{
nowans*=(c[total][a[i]]);
total-=a[i];
}
}
return nowans;
}
int main()
{
for(int i=0;i<=1000;i++)c[i][0]=1;
for(int i=1;i<=1000;i++)
for(int j=1;j<=1000;j++)c[i][j]=c[i-1][j-1]+c[i-1][j];
cin>>s;
n=s.size();
total=n;
for(int i=1;i<=n;i++)
{
num[i]=s[i-1]-'0';
a[num[i]]++;
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<num[i];j++)
{
a[j]--;
ans+=getans(i,j);
a[j]++;
}
a[num[i]]--;
}
cout<<ans<<endl;
return 0;
}