HDU 4779 Tower Defense(组合数学)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wust_zzwh/article/details/51785789

Description

  DRD loves playing computer games, especially Tower Defense games. Tower Defense is a famous computer game with a number of variations. In general, you are to build some defense towers to guard your territory in this game. 
  However, in most Tower Defense games, your defending towers will not attack each other. You will see the shells flying through your towers and finally hit the target on your screen. DRD thinks it to be absurd, and he designed a new tower defense game. 
  In DRD’s game, you have two kinds of defending tower, heavy tower and light tower. You can put the tower on a grid with N rows and M columns and each cell in the grid can hold one tower at most. Both two kinds of towers can attack the cells in the same column or the same row as it is located in, and your towers may attack each other. Moreover, light towers should not be attacked by other towers while heavy towers can be attacked by at most one other tower. 
  You can put some of your towers (at least one tower) in the grid to build a tower formation satisfying the restriction above. And now, DRD wants you to calculate that how many different tower formations could be designed. Note that all the towers of the same type are considered to be identical. While the answer could be quite large, you should output the number mod (10  9 + 7).
 

Input

  There are multiple test cases in the input. The first line of the input file is an integer T demonstrating the number of test cases. (0< T<= 200). 
  For each test case, there is only one line containing 4 integers, N, M, P and Q ,meaning that the grid has N rows and M columns, and you have P heavy towers and Q light towers. You do not have to put all the towers in the grid. (1 <= N, M <= 200, 0 <= P, Q <= 200) 
 

Output

  For each test case, output the number of different formations mod (10  9 + 7) in a single line.
 

Sample Input

 
    
3 2 2 0 1 2 2 2 0 3 3 2 1
 

Sample Output

 
    
4 10 144

题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=4779

题意:给一个N*M个网格,有p个重塔和q个轻塔,选若干塔放到网格里,问有多少种放的方案。放塔有限制条件:塔能攻击它所在的行和列所有格子,轻塔不能被其他塔攻击,重塔最多能被一个塔攻击。所以,轻塔所在的行列不能有其他塔,重塔所在的行列最多有一个塔(要么在同一行,要么在同一列,不能同时有)。

题解:对于每一行或者一列:有两个重塔、有一个重塔、有一个轻塔。只有这三种情况。特殊之处在两个重塔同行或同列,我们枚举有 i 行是被两个重塔占据,有j列被两个重塔占据,然后计算方案数:(后面C表示组合数)

第一种:对于这i行j列,共有2*(i+j)个重塔(因为重塔占的行列不能同时有塔),共有i+2*j行被占据,j+2*i列被占据,这个时候行列是相对的,以计算行为例,分步计算:先选出行数C(n,i),然后选列数C(m,i*2),对于这些行,第一行要从列中选两格来放塔,就是C(i*2,2),选了两个后,     在剩下列数用选两格放在第二行C(i*2-2,2)......依次下去,就是C(a,2)*C(a-2,2)*C(a-4,2)*...*C(2,2),这里我预先打的表,代码中t[a]表示,这样一来方案数就是C(n,i)*C(m,i*2)*t[i*2]。把n,m交换就可以算出j列的方案数。一旦行或列被占,可以视为该行货列被删掉,剩下的行列将组成新的网格(这样就只需要考虑行列减少了多少),那么行减少了i+2*j,列减少了j+2*i;剩下一个(n-(i+2*j) )*(m-(j+2*i) )的网格用来放剩下的p-2*(i+j)个重塔和q个轻塔。

第二种:对于剩下的网格和塔,不妨设剩下网格是n*m的(好吧,和上的n,m重名,不过作用域不同,你们懂得 T_T ),剩下的重塔p个,轻塔q个(T_T),但这个时候就没有两个重塔在一行或列的情况了,只能独行独列,此时行列也是相对的,不妨把小的边给n即n<m,不过注意这是重塔与重塔都是相同的,轻塔也是,那么就是两种不同的塔来组合,上面的枚举已经n^2了,这里就用O(n)来做,我们枚举剩下网格要放个塔的个数k,先选k个位置出来,及C(n,k)*C(m,k)*k!。注意这k格位置可以全排列即k!,因为每一行而言都是两个位置,交换行顺序后是不同的方案。然后把塔放进去,占考虑k个塔中重塔的个数x范围[a,b]的范围,同时轻塔数量要合法,x最大为b=min(n,k),x最小为a=max(0,k-p),那么从剩下的塔中选选x个重塔和k-x和轻塔,由于位置以为是考虑排列的情况,这里选塔是无序的,即C(k,a)+C(k,a+1)+...+C(k,b),这个式子也要前缀和打表用C(k,0)+C(k,1)+...+C(k,b)-(C(k,0)+C(k,1)+...+C(k,a-1))计算

最后第二种的方案数为    C(n,k)*C(m,k)*k!*[C(k,a)+C(k,a+1)+...+C(k,b)]。

两种相乘就是总方案,注意题目说不能为一个塔都不放,要减一。

#pragma comment(linker, "/STACK:10240000,10240000")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<queue>
#include<set>
#include<vector>
#include<map>
#include<stack>
#include<cmath>
#include<algorithm>
using namespace std;
const double R=0.5772156649015328606065120900;
const int N=200+5;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const double eps=1e-8;
const double pi=acos(-1.0);
typedef long long ll;
ll c[N][N],f[N],t[N],sum[N][N];
void init()
{
    c[0][0]=1;//组合数表
    f[0]=1;//阶乘表
    t[2]=1;//用来算第一种情况的
    sum[0][0]=1;//前缀和
    for(int i=1;i<203;i++)
    {
        f[i]=f[i-1]*i%mod;
        c[i][0]=1;
        sum[i][0]=c[i][0];
        for(int j=1;j<i;j++)
        {
            c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
            sum[i][j]=(sum[i][j-1]+c[i][j])%mod;//就是算杨辉三角每一行的前缀和
        }
        c[i][i]=1;
        sum[i][i]=(sum[i][i-1]+c[i][i])%mod;
    }
    for(int i=4;i<N;i+=2)
        t[i]=(t[i-2]*c[i][2])%mod;//就是C(i,2)*C(i-2,2)这样的前缀乘积
}
ll heavy(int n,int m,int x)
{
    if(x==0) return 1;
    ll tmp=c[n][x]*c[m][x*2]%mod;
    return tmp*t[x*2]%mod;
}
ll rest(int n,int m,int h/*重塔数*/,int l/*轻塔数*/)
{
    ll ans=0;
    if(m<n) swap(n,m);//相对
    for(int k=0;k<=min(n,h+l);k++)
    {
        ll tmp=c[n][k]*c[m][k]%mod;
        tmp=tmp*f[k]%mod;
        int low=max(0,k-l);
        int up=min(k,h);//[low,up]范围
        if(low>up) continue;
        if(low==0) tmp=tmp*sum[k][up]%mod;//前缀和相减,下标不能为负数
        else tmp=tmp*(((sum[k][up]-sum[k][low-1])%mod+mod)%mod)%mod;
        ans=(ans+tmp)%mod;
    }
    return ans;
}
int h,l,n,m;
ll solve(int x,int y)
{
    if(2*(x+y)>h || x+2*y>n || 2*x+y>m) return 0;//重塔数、行数、列数都要合法
    ll ans=heavy(n,m,x)*heavy(m-x*2,n-x,y)%mod;//计算第一种方案数,交换了行列即对应的塔数,算两遍即可
    return ans*rest(n-(x+y*2),m-(y+x*2),h-2*(x+y),l)%mod;//乘以第二种情况的方案数
}
int main()
{
    init();//打表
    int T_T;
    cin>>T_T;
    while(T_T--)
    {
        scanf("%d%d%d%d",&n,&m,&h,&l);
        ll ans=-1;
        for(int i=0;i<=n;i++)
            for(int j=0;j<=m;j++)//枚举i行j列被两重塔占据
            ans=(ans+solve(i,j))%mod;
        printf("%I64d\n",ans);
    }
    return 0;
}
/*

*/


猜你喜欢

转载自blog.csdn.net/wust_zzwh/article/details/51785789