hdu5181 numbers

链接

 

numbers

Time Limit: 12000/6000 MS (Java/Others)    Memory Limit: 196608/196608 K (Java/Others)
Total Submission(s): 146    Accepted Submission(s): 45


Problem Description
Now you have a stack and  n numbers 1,2,3,,n. These n numbers are pushed in the order and popped if the number is at the top of the stack. You can read the sample to get more details.
This question is quite easy. Therefore I must give you some limits.
There are m limits, each is expressed as a pair<A,B> means the number A must be popped before B.
Could you tell me the number of ways that are legal in these limits?
I know the answer may be so large, so you can just tell me the answer mod 1000000007(109+7).
 

 

Input
The first line contains an integer  T(about 5),indicating the number of cases.
Each test case begins with two integers n(1n300) and m(1m90000).
Next m lines contains two integers A and B(1An,1Bn)
(P.S. there may be the same limits or contradict limits.)
 

 

Output
For each case, output an integer means the answer mod  1000000007.
 

 

Sample Input
5 1 0 5 0 3 2 1 2 2 3 3 2 2 1 2 3 3 3 1 2 2 3 3 1
 

 

Sample Output
1 42 1 2 0
Hint
The only legal pop-sequence of case 3 is 1,2,3. The legal pop-sequences of case 4 are 2,3,1 and 2,1,3.
 

 

Source
 

 

 

 

首先考虑没有限制的情况,这其实就是一个卡特兰数,我们令f[i][j]表示将编号为i到j的元素进栈出栈一共有多少方案,那么枚举k,表示最后一个出栈元素是谁,那么就将问题划分为了两部分,f[i][k-1],f[k+1][j],可以用类似于区间dp的方法来结局。

如果有了限制,最暴力的办法自然是对于每一次枚举的i,j,k,判断一下是否和某一个条件矛盾,对于一组A,B,矛盾的时候就是i<=A,B<=j,并且A==k||(A>k&&B<k),我们不需要考虑A,B同时小于k大于等于i时会不会出现不合法的情况,因为这是一个子问题,在计算该子问题时已经排除掉了所有不合法的情况。

那么有代码:

 1 #include<cstdio>
 2 #include<cstring>
 3 #define ll long long
 4 using namespace std;
 5 const int inf=305;
 6 const int mod=1e9+7;
 7 int T;
 8 int n,m,A[inf*inf],B[inf*inf];
 9 ll f[inf][inf];
10 int main()
11 {
12     scanf("%d",&T);
13     while(T--){
14         memset(f,0,sizeof(f));
15         scanf("%d%d",&n,&m);
16         for(int i=1;i<=m;i++)scanf("%d%d",&A[i],&B[i]);
17         for(int i=1;i<=n+1;i++)
18             for(int j=0;j<i;j++)
19                 f[i][j]=1;
20         for(int l=1;l<=n;l++){
21             for(int i=1;i+l-1<=n;i++){
22                 int j=i+l-1;
23                 for(int k=i;k<=j;k++){
24                     int flag=0;
25                     for(int o=1;o<=m;o++){
26                         if(A[o]==k&&B[o]>=i&&B[o]<=j){
27                             flag=1;
28                             break;
29                         }
30                         if(A[o]>k&&A[o]<=j&&B[o]<k&&B[o]>=i){
31                             flag=1;
32                             break;
33                         }
34                     }
35                     if(!flag)f[i][j]=(f[i][j]+f[i][k-1]*f[k+1][j])%mod;
36                 }
37             }
38         }
39         printf("%lld\n",f[1][n]);
40     }
41     return 0;
42 }
View Code

 

考虑如何优化dp的过程,如果我们可以在O1的时间内完成对i,j,k是否合法进行判断,那么我们可以得到一个Tn^3的复杂度,那么我们设一个数组c[i][j][k]表示i,j,k是否和某个限制发生了矛盾,那么我们可以将这个数组预处理出来,外层枚举每一个k,内层枚举每一个限制,当某个限制满足A==k||(A>k&&B<k)时,意味着当前的k对于一些i,j是不合法的,而当且仅当i<=A,B<=j时才会出现i,j,k不合法的情况,即i<=min(A,B),j>=max(A,B),如果我们暴力的去更新c[i][j][k]数组,复杂度依旧是Tmn^3的,所以我们可以将i,j视为一个二维平面,因为我们有当前能影响到的i,j的范围,所以相当于对某一个矩阵集体打上标记,而这个过程可以通过差分轻松的实现,然后再统计一下前缀和即可得到每个点被影响了几次,只要被影响了,那么c[i]j[][k]就是1,即不合法。注意求前缀和的数组是三维的,s[k][i][j],表示在枚举某一个k时,哪些i,j会是不合法的。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define ll long long
 5 using namespace std;
 6 const int inf=305;
 7 const int mod=1e9+7;
 8 int T;
 9 int n,m,A[inf*inf],B[inf*inf];
10 ll f[inf][inf];
11 bool c[inf][inf][inf];
12 int s[inf][inf][inf]; 
13 int main()
14 {
15     scanf("%d",&T);
16     while(T--){
17         memset(f,0,sizeof(f));
18         memset(c,0,sizeof(c));
19         memset(s,0,sizeof(s));
20         scanf("%d%d",&n,&m);
21         for(int i=1;i<=m;i++)scanf("%d%d",&A[i],&B[i]);
22         for(int i=1;i<=n+1;i++)
23             for(int j=0;j<i;j++)
24                 f[i][j]=1;
25         for(int k=1;k<=n;k++){
26             for(int j=1;j<=m;j++){
27                 if(A[j]==k||(A[j]>k&&B[j]<k)){
28                     int u=max(A[j],B[j]),v=min(A[j],B[j]);
29                     s[k][1][u]++;s[k][v+1][u]--;
30                 }
31             }
32         }
33         for(int k=1;k<=n;k++){
34             for(int i=1;i<=n;i++){
35                 for(int j=1;j<=n;j++){
36                     s[k][i][j]=s[k][i-1][j]+s[k][i][j-1]-s[k][i-1][j-1]+s[k][i][j];
37                     if(s[k][i][j])c[i][j][k]=1;
38                 }
39             }
40         }
41         for(int l=1;l<=n;l++){
42             for(int i=1;i+l-1<=n;i++){
43                 int j=i+l-1;
44                 for(int k=i;k<=j;k++){
45                     if(!c[i][j][k])f[i][j]=(f[i][j]+f[i][k-1]*f[k+1][j])%mod;
46                 }
47             }
48         }
49         printf("%lld\n",f[1][n]);
50     }
51     return 0;
52 }
View Code

 

转载于:https://www.cnblogs.com/hyghb/p/8320459.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值