[组合数学 思路题] BZOJ 4402 Claris的剑


好神的题:http://blog.csdn.net/lych_cys/article/details/50603389


实际上,任意一种可行的方案的序列都可以转化为本质相同的下述序列:

       先是若干个(1,2),然后是若干个(3,2),然后是若干个(3,4)……以此类推,当然如果长度为奇数就在最后面加上一个数,举个例子:

       {1,2,3,2,3,2,3,2,3,4,5,4,5,4,5,4,5,6,5,6,5,6,7}

       可以这么证明,首先对于1,1的后面一定跟着一个2,前面也是1个2,那么就把所有的(1,2)都不断移到最前面,显然这个序列仍然符合要求;然后再把(3,2)移到若干个(1,2)的后面,再把(3,4)移到(3,2)的后面……最后就变成上面所描述的序列了。

       这样,如果两个序列,符合上面的描述且至少有一位不同,显然这两个序列是本质不同的。这样就解决了本质不同这个问题;同时,将序列有序化了。

       假设序列长度为n,最大值为m,n>=m,那么此时有多少种本质不同的方案呢?首先得到序列{1,2,...,m},接下来就是考虑往序列里面加东西使它的长度到大n。显然如果加了,就只能一对一对加,即(1,2)(3,2)(3,4)(5,4)……这些2个2个往里面加,这样总共可以加[(n-m)/2]次,如果最后多了一个,那也只有一种情况了:加在最后面。由于加入的先后顺序没有影响,所以相当于[(n-m)/2]个球里放在(m-1)个盒子里,允许空盒的方案数。这个用组合计数解决就行了。

       (补充:m个相同的球放在n个盒子里,允许空盒的方案数为C(m+n-1,n-1),把这个记为F(m,n))

       当n,m一定时,答案为F([(n-m)/2],m-1),但这样是O(MN)的。注意到F(0,m-1)+F(1,m-1)+F(2,m-1)+...+F([(n-m)/2],m-1)=F([(n-m)/2],m),这样就变为O(M+N)的了。


#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define P 1000000007
using namespace std;
typedef long long ll;

inline char nc()
{
	static char buf[100000],*p1=buf,*p2=buf;
	if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
	return *p1++;
}

inline void read(int &x){
	char c=nc(),b=1;
	for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
	for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}

const int maxn=2000000;
ll fac[maxn+5],inv[maxn+5];

inline void Pre(){
	fac[0]=1;
	for (int i=1;i<=maxn;i++)
		(fac[i]=fac[i-1]*i)%=P;
	inv[1]=1;
	for (int i=2;i<=maxn;i++)
		(inv[i]=(P-P/i)*inv[P%i])%=P;
	inv[0]=1;
	for (int i=1;i<=maxn;i++)
		(inv[i]*=inv[i-1])%=P;
}

inline ll C(int n,int m){
	return fac[n]*inv[m]%P*inv[n-m]%P;
}

inline ll F(int n,int m){
	if (m<0) return 0;
	return C(m+n-1,n-1);
}

int n,m;
ll ans;

int main()
{
	freopen("t.in","r",stdin);
	freopen("t.out","w",stdout);
	Pre();
	read(n); read(m);
	if (!n || !m) return printf("0\n"),0;
	ans=1; m=min(n,m);
	for (int i=2;i<=m;i++)
	{
		(ans+=F(i,(n-i)>>1))%=P;
		(ans+=F(i,(n-i-1)>>1))%=P;
	}
	printf("%lld\n",ans);
	return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值