luogu P2518 [HAOI2010]计数

很好的一道数位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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值