numbers
Time Limit: 12000/6000 MS (Java/Others) Memory Limit: 196608/196608 K (Java/Others)
Total Submission(s): 146 Accepted Submission(s): 45
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).
Each test case begins with two integers n(1≤n≤300) and m(1≤m≤90000).
Next m lines contains two integers A and B(1≤A≤n,1≤B≤n)
(P.S. there may be the same limits or contradict limits.)
首先考虑没有限制的情况,这其实就是一个卡特兰数,我们令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 }
考虑如何优化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 }