[FJOI2016] 建筑师

题目描述

小 Z 是一个很有名的建筑师,有一天他接到了一个很奇怪的任务:在数轴上建 nn 个建筑,每个建筑的高度是 11 到 nn 之间的一个整数。

小 Z 有很严重的强迫症,他不喜欢有两个建筑的高度相同。另外小 Z 觉得如果从最左边(所有建筑都在右边)看能看到 AA个建筑,从最右边(所有建筑都在左边)看能看到 BB 个建筑,这样的建筑群有着独特的美感。现在,小 Z 想知道满足上述所有条件的建筑方案有多少种?

如果建筑 ii 的左(右)边没有任何建造比它高,则建筑 ii 可以从左(右)边看到。两种方案不同,当且仅当存在某个建筑在两种方案下的高度不同。

输入输出格式

输入格式:

第一行一个整数 TT ,代表 TT 组数据。 接下来 TT 行,每行三个整数 n,A,Bn,A,B 。

输出格式:

对于每组数据输出一行答案 \text{mod } 10^9+7mod 109+7 。

输入输出样例

输入样例#1: 
2
3 2 2
3 1 2
输出样例#1: 
2
1

说明

对于 10 \%10% 的数据 : 1 \leq n \leq 101n10 。

对于 20 \%20% 的数据 : 1 \leq n \leq 1001n100 。

对于 40 \%40% 的数据 : 1 \leq n \leq 50000, \ 1 \leq T \leq 51n50000, 1T5 。

对于 100 \%100% 的数据 : 1 \leq n \leq 50000, \ 1 \leq A, B \leq 100, \ 1 \leq T \leq 2000001n50000, 1A,B100, 1T200000 。

    乍一看这不是以前在UVA做过的一道题么。。。。只不过数据范围无限扩大。。。。

    一开始想看看能不能卡常卡过去。。。。于是就把那个题又写了一遍。。。但是跑了40+s才能跑出最大的数据。。。。

    虽然很慢,但是 最基本的dp方程 也在这个程序里出现了,之后对程序的优化也需要用到这个转移的性质,所以还是先贴一下:

/*
    f[i][j][k] -> f[i+1][j+1][k]
    f[i][j][k] -> f[i+1][j][k+1]
    f[i][j][k] * (i-1) -> f[i+1][j][k]
*/
#include<bits/stdc++.h>
#define ll long long
using namespace std;
#define pb push_back
const int ha=1e9+7;
inline void add(int &x,int y){ x+=y; if(x>=ha) x-=ha;}
struct node{ int num,A,B;};
vector<node> g[200005];
int f[2][105][105],T,n,X;
int Y,ans[200005],M,a,b;

inline void solve(){
	f[0][1][1]=1;
	for(int i=2,now=1,pre=0;i<=M;pre=now,now^=1,i++){
		memset(f[now],0,sizeof(f[now]));
		
		for(int j=0;j<=a;j++)
		    for(int k=0;k<=b;k++) if(f[pre][j][k]){
		    	add(f[now][j][k],f[pre][j][k]*(ll)(i-2)%ha);
		    	add(f[now][j+1][k],f[pre][j][k]);
		    	add(f[now][j][k+1],f[pre][j][k]);
			}
		
		node x;
		
		for(int j=g[i].size()-1;j>=0;j--){
			x=g[i][j];
			ans[x.num]=f[now][x.A][x.B];
		}
	}
}

int main(){
	scanf("%d",&T);
	for(int i=1;i<=T;i++){
		scanf("%d%d%d",&n,&X,&Y);
		g[n].pb((node){i,X,Y});
		M=max(M,n),a=max(a,X),b=max(b,Y);
	}
	
	solve();
	
	for(int i=1;i<=T;i++) printf("%d\n",ans[i]);
	return 0;
}

  但显然正解只能允许 50000 * 100的啦。。。。

      我们考虑一下dp方程的转移,对于后两维,每次要么都不变(然后都乘上一个系数),或者两维之一+1。

     所以怎么快速计算这个呢?? 我们考虑把后两位合并,最后再考虑两维+1顺序的问题。。。

    也就是设 f[i][j] 为已经放了 i个桩子,左+右一共能看见j个的方案数(无序的)。

    最后询问 A,B的时候 再乘上C(A+B-2,A-1)  [枚举两维+1的顺序] 就行啦。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int ha=1e9+7;
inline int add(int x,int y){ x+=y; return x>=ha?x-ha:x;}
inline void ADD(int &x,int y){ x+=y; if(x>=ha) x-=ha;}
int f[50005][233],C[233][233],T,n,A,B;

inline void init(){
	C[0][0]=1;
	for(int i=1;i<=200;i++){
		C[i][0]=1;
		for(int j=1;j<=i;j++) C[i][j]=add(C[i-1][j-1],C[i-1][j]);
	}
	
	f[1][2]=1;
	for(int i=1;i<50000;i++)
	    for(int j=2;j<=200;j++) if(f[i][j]){
	    	ADD(f[i+1][j],f[i][j]*(ll)(i-1)%ha);
	    	ADD(f[i+1][j+1],f[i][j]);
		}
}

int main(){
	init(),scanf("%d",&T);
	
	while(T--){
		scanf("%d%d%d",&n,&A,&B);
		printf("%d\n",f[n][A+B]*(ll)C[A+B-2][A-1]%ha);
	}
	
	return 0;
}

  

猜你喜欢

转载自www.cnblogs.com/JYYHH/p/9048111.html