2017多校训练赛第一场 HDU 6044 Limited Permutation(虚建笛卡尔树+超级读入挂)

Limited Permutation

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)
Total Submission(s): 1293    Accepted Submission(s): 339

Problem Description

As to a permutation p1,p2,⋯,pn from 1 to n, it is uncomplicated for each 1≤in to calculate (li,ri) meeting the condition that min(pL,pL+1,⋯,pR)=pi if and only if liLiRri for each 1≤LRn.
Given the positive integers n, (li,ri) (1≤in), you are asked to calculate the number of possible permutations p1,p2,⋯,pn from 1 to n, meeting the above condition.
The answer may be very large, so you only need to give the value of answer modulo 109+7.

Input

The input contains multiple test cases.
For each test case:
The first line contains one positive integer n, satisfying 1≤n≤106.
The second line contains n positive integers l1,l2,⋯,ln, satisfying 1≤lii for each 1≤in.
The third line contains n positive integers r1,r2,⋯,rn, satisfying irin for each 1≤in.
It's guaranteed that the sum of n in all test cases is not larger than 3⋅106.
Warm Tips for C/C++: input data is so large (about 38 MiB) that we recommend to use fread() for buffering friendly.

size_t fread(void *buffer, size_t size, size_t count, FILE *stream); // reads an array of count elements, each one with a size of size bytes, from the stream and stores them in the block of memory specified by buffer; the total number of elements successfully read is returned.

Output

For each test case, output "Case #x: y" in one line (without quotes), where x indicates the case number starting from 1 and y denotes the answer of corresponding case.

Sample Input

3 1 1 3 1 3 3 5 1 2 2 4 5 5 2 5 5 5

Sample Output

Case #1: 2

Case #2: 3

Source

2017 Multi-University Training Contest - Team 1

 

 

        还是一样,读题很重要……

        本题的破题关键点在于区间满足的条件:if and only if(当且仅当)。所以说,对于一个数字i,它是区间[li,ri]的最小值,这个li和ri不能扩大或者缩小,即a[li-1]和a[ri+1]都比a[i]小。有了这个我们就可以知道,所有的区间,要么是相互包含的,要么没有交集,不会出现有相交的情况。这个如何解释呢?我们们用反证法,假设区间[li,ri]与[lj,rj]相交,对于li<lj<ri<rj的情况,根据当且仅当的条件,我们可以知道a[ri+1]<a[i],又a[j]<a[ri+1],所以a[j]<a[i],所以lj<=li,区间包含,故与区间相交矛盾,所以不可能出现相交。另外的情况同理可证。

        知道这个以后,我们就可以尝试虚拟的建立一棵笛卡尔树。首先对区间按照左端点升序,右端点降序排序。根据笛卡尔树性质,笛卡尔树的先序遍历就是数字从小到大的排序,按照刚刚排好的区间顺序就是一个序列,按照先序依次放上区间即可。然后方案数的话,我们设dp[i]表示以i为根的笛卡尔树,标号为1~sz[i]的满足条件的方案数个数,那么有转移方程dp[i]=dp[lson]*dp[rson]*c(sz[i],sz[lson]),其中sz[i]表示以i为根的子树的大小。为什么要乘多一个组合数呢?这个其实很容易理解,dp[lson]和dp[rson]表示的只是标号为1~sz[lson]或sz[rson]的方案数,现在我总共可以取的标号有sz[i]种,那么从sz[i]任意取sz[lson]个标号都能构成一种不重复的标号,所以总共的不重复的方案数还要再乘上一个c(sz[i],sz[lson])。

        注意到数字比较大(100W),所以我们还要用上费马小确定的阶乘逆元。最后还要注意,输入非常的大,文件大小有38M左右,所以要用读入挂fread,这个比普通的读入优化还要快,这个之间我还不会用,在之前读入优化的版上改了改,就当收了个模板吧。具体见代码:

#include<bits/stdc++.h>
#define mod 1000000007
#define LL long long
#define N 1001000
using namespace std;

namespace IO									//超级读入挂
{
    const int len=4e7;char buf[len];int sz,p;					//4e7大概大小为44M,可以放得下38M的输入
    void begin(){p=0;sz=fread(buf,1,len,stdin);}
    inline bool read(LL &x)
    {
        if (p==sz)return 0;int f=1,d=0;char s=buf[p++];
        while(s<'0'||s>'9'&&p<sz){if(s=='-') f=-1;s=buf[p++];}
        while(s>='0'&&s<='9'&&p<sz){d=d*10+s-'0';s=buf[p++];}
        x=f*d; return p!=sz;
    }
    inline void writeln(LL x)
    {
        if(x==0){putchar('0');putchar('\n');return;}
        if(x<0)putchar('-'),x=-x;int len=0,buf[20];
        while(x)buf[len++]=x%10,x/=10;int i=len-1;
        while (i>=0){putchar(buf[i--]+'0');}putchar('\n');return;
    }
}

struct seg {LL l,r,id;} a[N];

bool cmp(seg a,seg b)
{
    return a.l==b.l?a.r>b.r:a.l<b.l;
}

LL f[N],n,m; bool flag;

LL fastpow(LL x,LL n)
{
    LL ret=1;
    while (n)
    {
        if (n&1) ret=ret*x%mod;
        x=x*x%mod; n>>=1;
    }
    return ret;
}

LL c(LL n,LL m)							//求组合数
{
    LL ret=1;
    ret=ret*f[n]*fastpow(f[m]*f[n-m]%mod,mod-2)%mod;
    return ret;
}

LL dfs(LL l,LL r)						//dfs虚拟建笛卡尔树
{
    if (l>r) return 1LL;					//如果区间左端点大于右端点,说明父亲没有该儿子
    seg now=a[++m];
    if (now.l!=l||now.r!=r||flag)				//如果树区间合法,但不存在这种区间,说明矛盾
    {
        flag=1; return 0LL;
    } if (l==r) return 1LL;					//叶子节点只有一种方案
    LL res=c(now.r-now.l,now.id-now.l)*dfs(now.l,now.id-1)%mod;			//按标号分左右儿子,分别计算
    res=res*dfs(now.id+1,now.r)%mod;
    return res;
}

int  main()
{
    IO::begin();
    int T_T=0; f[0]=1;
    for(int i=1;i<N;i++)
        f[i]=f[i-1]*(LL)i%mod;
    while(IO::read(n))
    {
        for(int i=1;i<=n;i++)
            IO::read(a[i].l);
        for(int i=1;i<=n;i++)
            IO::read(a[i].r),a[i].id=i;
        sort(a+1,a+1+n,cmp);
        printf("Case #%d: ",++T_T);
        m=0; flag=0;
        IO::writeln(dfs(1,n));
    }
}

 

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值