hdu 5181 numbers

Posted 日拱一卒 功不唐捐

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了hdu 5181 numbers相关的知识,希望对你有一定的参考价值。

http://acm.hdu.edu.cn/showproblem.php?pid=5181

 

题意:
有一个栈,其中有n个数1~n按顺序依次进入栈顶,在某个时刻弹出。
其中m个限制,形如数字A必须在数字B之前弹出。
求方案总数
 
dp[i][j]表示数字i~j的出栈方案数
枚举最后一个出栈的数k,若k合法
dp[i][j]+=dp[i][k]*dp[k+1][j]
如何判断k是否合法?
 
对于一组i,j,k来说,它的弹出顺序是 [i,k-1]早于[k+1,j]早于k
对于一个限制 A必须在B之前出栈 
它只会对 i<=min(A,B),j>=max(A,B) 的 dp[i][j] 产生影响
若A<B,
a、i<=k<A或B<k<=j,产生的影响已在子DP中求出
b、k=A,A最后出栈,显然不合法
c、k=B,B最后出栈,显然合法
d、A<k<B,[A,k-1]早于[k+1,B]早于k出栈,所以合法
若B<A,
a、i<=k<B或A<k<=j,产生的影响已在子DP中求出
b、k=A,A最后出栈,显然不合法
c、k=B,B最后出栈,显然合法
d、B<k<A,[B,k-1]早于[k+1,A]早于k出栈,所以不合法
综上所述
对于一个限制A必须在B之前出栈
若A<B,当k=A时不合法
若B<A,当k∈(B,A]时不合法
这样的话之间复杂度时O(n^3 * m
 
对于限制A必须在B之前出栈,如果确定了不能用k转移
考虑这些区间有哪些
令mi=min(A,B),mx=max(A,B)
那么区间
[1,mx] [1,mx+1] [1,mx+2]……[1,n]
[2,mx] [2,mx+1] [2,mx+2]……[2,n]
……
[mi][mx] [mi,mx+1] [mi,mx+2]……[mi,n]
不能用k转移
如果把这些区间的左右端点当做二维平面上的一个点对
那么这些区间就是 以[1,mx]为左上角,以[mi,n]为右下角的一个矩形
那么对于每个k,利用差分和前缀和预处理出这些矩形,就可以做到O(1)查询k转移dp[l,r]是否合法
时间复杂度为O(n^3 + nm)
 
 
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int mod=1e9+7;

#define N 302
#define M 90001

int n,m;

int lim[M][2];

int a[N][N][N];

int dp[N][N];

void read(int &x)
{
    x=0; char c=getchar();
    while(!isdigit(c)) c=getchar(); 
    while(isdigit(c)) { x=x*10+c-0; c=getchar(); }
}

void add(int xl,int yl,int xr,int yr,int k)
{
    a[xl][yl][k]++;
    a[xl][yr+1][k]--;
    a[xr+1][yl][k]--;
    a[xr+1][yr+1][k]++;
}

void pre()
{
    memset(a,0,sizeof(a));
    int mi,mx;
    for(int i=1;i<=m;++i)
    {
        mi=min(lim[i][0],lim[i][1]);
        mx=max(lim[i][0],lim[i][1]);
        if(lim[i][0]<lim[i][1]) add(1,mx,mi,n,lim[i][0]);
        else
        for(int j=lim[i][1]+1;j<=lim[i][0];++j) add(1,mx,mi,n,j);
    }
    for(int k=1;k<=n;++k)
    {
        for(int i=1;i<=n;++i)
            for(int j=1;j<=n;++j)
                a[i][j][k]=a[i-1][j][k]+a[i][j-1][k]-a[i-1][j-1][k]+a[i][j][k];
    }
}

void DP()
{
    memset(dp,0,sizeof(dp));
    for(int i=1;i<=n;++i) dp[i][i]=1;
    for(int i=1;i<=n+1;++i) dp[i][i-1]=1;
    for(int i=n-1;i;--i)
        for(int j=i+1;j<=n;++j)
            for(int k=i;k<=j;++k)
                if(!a[i][j][k]) 
                    dp[i][j]=(dp[i][j]+(long long)dp[i][k-1]*dp[k+1][j])%mod;
    cout<<dp[1][n]<<\n;
}

int main()
{
    int T;
    read(T);
    bool tag;
    while(T--)
    {
        read(n); read(m);
        tag=true;
        for(int i=1;i<=m;++i) 
        {
            read(lim[i][0]),read(lim[i][1]);
            if(lim[i][0]==lim[i][1]) tag=false;
        }
        if(!tag) { puts("0"); continue; }
        pre();
        DP();
    }
}

 

 

以上是关于hdu 5181 numbers的主要内容,如果未能解决你的问题,请参考以下文章

hdu 5181 numbers

[Luogu5181][COCI2009]GENIJALAC

代码片段:Shell脚本实现重复执行和多进程

HDU2848 Number Cutting Game

HDU 1771 Number Sequence

HDU 5972 Regular Number