【问题描述】
azui大爷在quack大爷的带领下开始玩集合了,可是他太懒了,不想做quack大爷布置的作业题,便拿来给你做了:
S 集合中有n个不同的元素,我们从1-n标号。考虑S 的子集Si,j,将这些子集排成一个r行c列矩阵的样子。
其中第一行为S1,1,S1,2,…,S1,c,第二行为S2,1,S2,2,..,S2,c一直到第r行为Sr,1, Sr,2,…, Sr,c。
这些集合还满足对于在一行中左右相邻的两个集右,左侧是右侧
的子集,即Si,j∈Si,j+1。
这些集合还满足对于在一列中上下相邻的两个集合,上方是下方
的子集,即Si,j∈Si+1,j。
问对于S 的全部子集,有多少可能的情况排成上述的矩阵(每个子集可以重复使用),结果模109 +7 输出。
你一定知道这么简单的题目怎么做,快帮帮azui大爷吧。
【输入格式】
一行三个数n,r,c.
【输出格式】
一个数表示答案。
【样例输入】
1 2 2
【样例输出】
6
【样例解释】
1 | 2 |
3 | 4 |
如上图标号后,有:1是2和3的子集,1,2,3都是4的子集。
用0表示空集,1表示元素1,从左到右是1、2、3、4的话,有以下6种情况
0000 0001 0011 0101 0111 1111
【数据范围】
对于10%的数据,r*c*n <= 16
对于20%的数据,r*c <= 16, n <= 100
对于另20%的数据,r=1, c<=1000000
对于另10%的数据,n=1
对于100%的数据,r, c <= 1000000, n <= 10^9
对这道题我是萌比的,于是考试的时候只能悄悄地去骗一骗子任务的分QWQ
一号子任务显然是要让我们dfs不说了
二号子任务其实是让我们先将n=1去dfs再n次方也不说了(这里的n次方思路是正解的一部分哦)
三号子任务退化成一维后也不见得很简单,我们先尝试看看。由于不同元素之间并无交集,所以可以先将n等于1,再n次方也没有关系。那么我们观察,由于n=1了,所以要么是0,要么是1并且一定是一段0之后全是1,其实就有c+1种情况(0个1,1个,2个1...c个1),于是便是(c+1)^n并不困难。(这里是正解的一维版本哦)
四号子任务可以DP。。。(当然好像还是是超时的)
好了,研究了子任务后,我们来看看这与正解有何关联。首先必须将三号子任务扩展成二维,那么可以发现:每一种合法解都是呈现每一行的1个数逐渐递增,就像下面的图这样:可以发现,这个排列成了一条折线,于是便有了折线的生成:C(r+c,r)(由于两端开始点都不确定,所以不是C(r+c-2,r-1))
那么只需要用我们的组合数阶乘法就好了~代码如下:
//%>_<%
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
#define ll long long
#define mod 1000000007ll
ll r,c,n,sum=1;
ll ksm(ll a,ll k)
{
if(!k)
return 1;
ll p=ksm(a,k/2);
if(k&1)return p*p%mod*a%mod;
return p*p%mod;
}
ll jc(ll a)
{
ll ans=1;
for(ll i=1;i<=a;i++)
ans=ans*i%mod;
return ans;
}
ll C(ll a,ll b)
{
return jc(a)*ksm(jc(b),mod-2)%mod*ksm(jc(a-b),mod-2)%mod;
}
int main()
{
scanf("%lld%lld%lld",&n,&r,&c);
if(c<r)
swap(c,r);
printf("%lld",ksm(C(r+c,r),n));
}