Hdu5181 numbers

numbers

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

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
题目大意:一个由1,2,......,n组成的排列,顺次插入栈中,你可以选择在任意时刻弹出栈顶元素,现在有m个要求,要求x必须在y之前出栈,问有多少种合法的出栈顺序.
分析:好题!
   先考虑没有要求的情况. 很显然就是卡特兰数. 可以用区间dp的方法去做:f[i][j] = Σf[i][k - 1] * f[k + 1][j],枚举的k是区间[i,j]中最后被弹出的元素,那么肯定先是区间[i,k-1]的元素被弹出,然后就是区间[k+1,j]的元素被弹出,最后是k被弹出.这是O(n^3)的做法
   如果有限制怎么办呢?能不能在原有的dp方案的基础上改进一下呢? 可以往两个方向思考,要么是给状态加上一维,要么是就沿用这个状态,把不满足限制的方案给排除掉. 加上一维是不现实的,因为复杂度已经达到了O(n^3),加上一维的话复杂度肯定会再上升一个级别.
   那就考虑如何把不满足限制的方案给排除掉.假设x必须要在y之前弹出,令x,y的较小值为min,较大值为max.对于x,y的限制,影响的只有包含x,y这两个点的区间,也就是区间左端点在[1,min],右端点在[max,n]的区间. 
   若x < y,那么x在排列中的位置肯定在y前面.前面说过元素弹出的顺序,如果k在x,y右边或者左边,那么作为一个子问题已经被解决了.如果k在x,y中间.只要k不是x,x永远比y先弹出. 那么对于被x,y影响的区间,一旦k = x,就不能统计进入答案中.
   若x > y,y在左边了.考虑k在y,x中间的情况.k 可以等于 y,这样y在x之后被弹出,但是一旦 y + 1 ≤ k ≤ x,则k是不合法的.
   预处理出不合法的状态是O(n^2m)的复杂度,显然是不可以接受的,怎么优化呢?
   k是要枚举的,但是区间的左右端点所在的区间是确定的,需要做的就是把这些区间快速打上标记.做法还是比较难想到的.
不合法的区间形如:[1,max],[1,max + 1],......,[1,n],[2,max],[2,max + 1],......,[min,n]. 对于二元组,一个处理方法是放到平面直角坐标系上,然后可以发现,这些点所形成的图形是一个矩形. 批量修改一个矩形,怎么快速改? 二维差分!怎么查询单个点的答案?前缀和!问题就这样被解决了.
   总复杂度O(n^3 + n*m).
   这道题综合了许多知识,把问题简化,得到一个做法后该如何推导出原问题的做法?能不能用上简化的问题的做法?如何快速修改左右端点所在区间都知道的所有区间?遇到二元组要怎么处理?如何快速修改一个矩形内所有点的值?如何查询单点答案?这就是这道题的思路.
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long ll;
const ll maxn = 310,mod = 1e9+7;
ll f[maxn][maxn];
int T,n,m,sum[maxn][maxn][maxn],a[maxn][maxn][maxn];
bool flag = true;

struct node
{
    int x,y;
} e[90010];

void add(int x3,int y3,int x4,int y4,int k)
{
    sum[x3][y3][k]++;
    sum[x4 + 1][y4 + 1][k]++;
    sum[x4 + 1][y3][k]--;
    sum[x3][y4 + 1][k]--;
}

void pre()
{
    for (int i = 1; i <= m; i++)
    {
        ll x = e[i].x,y = e[i].y;
        if (x < y)
            add(1,y,x,n,x);
        else
        {
            for (int j = y + 1; j <= x; j++)
                add(1,x,y,n,j);
        }
    }
    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                sum[i][j][k] = sum[i - 1][j][k] + sum[i][j - 1][k] - sum[i - 1][j - 1][k] + sum[i][j][k];
}

void solve()
{
    for (int i = 1; i <= n; i++)
    {
        f[i][i] = 1;
        f[i][i - 1] = 1;
    }
    f[n + 1][n] = 1;
    for (int len = 2; len <= n; len++)
        for (int i = 1; i + len - 1 <= n; i++)
        {
            int j = i + len - 1;
            for (int k = i; k <= j; k++)
            {
                if (sum[i][j][k] == 0)
                {
                    f[i][j] += f[i][k - 1] * f[k + 1][j] % mod;
                    f[i][j] %= mod;
                }
            }
        }
}

int main()
{
    scanf("%d",&T);
    while(T--)
    {
        memset(sum,0,sizeof(sum));
        memset(f,0,sizeof(f));
        flag = true;
        scanf("%d%d",&n,&m);
        for (ll i = 1; i <= m; i++)
        {
            scanf("%d%d",&e[i].x,&e[i].y);
            if (e[i].x == e[i].y)
                flag = false;
        }
        if (!flag)
            puts("0");
        else
        {
            pre();
            solve();
            printf("%lld\n",f[1][n] % mod);
        }
    }

    return 0;
}

 

   
posted @ 2018-02-28 15:35  zbtrs  阅读(301)  评论(0编辑  收藏  举报