HDU 6044 Limited Permutation
原题连接
http://acm.hdu.edu.cn/showproblem.php?pid=6044
在开始之前。提供一种加速读取的方法。
由于题目输入规模很大。所以建议使用下面代码来读入
利用 fread() 批量读入内存。(下文中代码实现就是使用这个读入)
struct Io
{
const char l='0'-1;
const char r='9'+1;
char A[MAXN];
char *ptop,*pend;
Io()
{
ptop=pend=A;
}
void Io_fread()
{
pend=A+fread(A,1,MAXN,stdin);
ptop=A;
}
bool read(int &a)//返回false表示什么也没读到
{
if(ptop==pend)Io_fread();
if(ptop==pend)return false;
while(*ptop==' '||*ptop=='\n'||*ptop=='\r'||*ptop=='\t')
{
ptop++;
if(ptop==pend)
{
Io_fread();
if(ptop==pend)return false;
}
}
a=0;
while(*ptop>l&&*ptop<r&&ptop<pend)
{
a=a*10+(*(ptop++)-'0');
if(ptop==pend) Io_fread();
}
return true;
}
}I;
<script type="math/tex; mode=display" id="MathJax-Element-4"></script>
开始正题。读题有可能忽略掉一个很重要的条件:
当且仅当 li≤L≤i≤R≤ri 时。
min(PL,PL+1,...PR)=Pi
当且仅当。意味着 L,R 不满足上面条件时。
min(PL,PL+1,...PR)≠Pi
其实。如果没有忽略这样一个重要的条件。题目还是很有想头的。
<script type="math/tex; mode=display" id="MathJax-Element-9"></script>
那么对于上文条件。我们
R
取ri+1 时。
min(PL,PL+1,...Pri+1)≠Pi
又:
min(PL,PL+1,...Pri)=Pi
则:
min(PL,PL+1,...Pri+1)=Pri+1
这意味着:
[li,ri]∈[lri+1,ri]
但是单纯从上面的方法出发对题目理解不够全面。
换个角度。
题目用。当且仅当这个词。那意味着。我们随意指定一个 [L,R]
有且仅有一个
i
满足:i∈[L,R],[L,R]∈[li,ri]
题目开始变的简单。为什么?
既然任意指定
L,R
都应找到一个
i
那么意味着:
必然存在覆盖[1,n] 的
i
那么对于这样的一个i
也必然有覆盖: [1,i−1] , [i+1,n] 的限制区间。
似乎 DFS , BFS 都可以解决。
因为我们得到了一个子问题:
我们记子区间
[L,R]
对应的答案为:
slove(L,R)
<script type="math/tex; mode=display" id="MathJax-Element-29"></script>
因为任意指定
[L,R]
都有且仅有一个
i
满足:i∈[L,R],[L,R]∈[li,ri]
所以如果存在 [lt,rt] 有: i∈[lt,rt] , [lt,rt]∈[li,ri]
则: slove(li,ri)=0
如果不存在某 lt=li,rt=i−1
则 slove(li,ri)=0
因为开始的时候。 answer=slove(1,n)
因为 [1,n] 是全集。所以不存在不属于 [1,n] 的区间。与 [1,n] 有交。
<script type="math/tex; mode=display" id="MathJax-Element-42"></script>
归纳总结有:如果 answer>0 .则不存在这样的 i,j 有:
li≤lj≤ri≤rj
首先:
令 lu=1,ru=n , slove(1,n)>0 .由之前的结论有:
不存在这样的
i
有:li≤u≤ri
那么对于任意
i
。有:[li,ri]∈[1,u−1] 或者
[li,ri]∈[u+1,n]
那么对于任意 t∈[1,u−1] 都有: [lt,rt]∈[1,u−1]
任意 t∈[u+1,n] 都有: [lt,rt]∈[u+1,n]
其次:
记: lp=1,rp=u−1 或者 lp=u+1,rp=n
slove(lp,rp)>0
,则不存在这样的
t
有:p∈[lt,rt] , [lt,rt]∈[lp,rp]
因为任意 t∈[1,u−1] 都有: [lt,rt]∈[1,u−1]
因为任意 t∈[u+1,n] 都有: [lt,rt]∈[u+1,n]
则 slove(lp,rp) 是原问题的一个子问题。
当前区间。除了
p
以外 , 在这个区间内的元素对应的区间要么属于[lp,p−1] 或者属于
[p+1,rp]
那么有:
slove(li,ri)=slove(li,i−1)slove(i+1,ri)(ri−lii−li)
解释:计算完左右子树后。位置
i
必须是最小值。但左右子树互不影响。
其中。两个slove() 是左右两边排列方案数。
(ri−lii−li) 是左右两边分配方案数。应用乘法乘法原理得到上式。
我们可以建立一棵区间包含关系的树。
根节点为区间 [1,n]
root 的左孩子 [1,u−1]
root 的右孩子 [u+1,n]
其他节点依次类推。直到节点对应一个值
如果对于数组 l[],r[] 可以成功建立上述区间包含关系的树。那意味着答案有解。
如果不能建立或者建立的树不合法。则 answer=0
我们可以对区间排序。来建立这棵区间包含关系的树。
建议使用桶排序。由于输入时间较长。系统自带 sort 不确定是否可过。
当然。也可以不建立树。直接 DFS (但排序不可少)
也可以使用单调栈。
下面是 AC 代码
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define MAXN 1000006
using namespace std;
typedef long long LL;
const int P=1e9+7;
struct Io
{
const char l='0'-1;
const char r='9'+1;
char A[MAXN];
char *ptop,*pend;
Io()
{
ptop=pend=A;
}
void Io_fread()
{
pend=A+fread(A,1,MAXN,stdin);
ptop=A;
}
bool read(int &a)
{
if(ptop==pend)Io_fread();
if(ptop==pend)return false;
while(*ptop==' '||*ptop=='\n'||*ptop=='\r'||*ptop=='\t')
{
ptop++;
if(ptop==pend)
{
Io_fread();
if(ptop==pend)return false;
}
}
a=0;
while(*ptop>l&&*ptop<r&&ptop<pend)
{
a=a*10+(*(ptop++)-'0');
if(ptop==pend) Io_fread();
}
return true;
}
}I;
int tmp[MAXN];
void Sort(int *A,int *B,int *ton,int *Rank,int size)//桶排序。先比较A属性。后比较B属性。
{
for(int i=0;i<size;i++) ton[i]=0;
for(int i=1;i<size;i++) ton[B[i]]++;
for(int i=1;i<size;i++) ton[i]+=ton[i-1];
for(int i=size-1;i;i--) tmp[ton[B[i]]--]=i;//对B属性排序
for(int i=0;i<size;i++) ton[i]=0;
for(int i=1;i<size;i++) ton[A[i]]++;
for(int i=1;i<size;i++) ton[i]+=ton[i-1];
for(int i=size-1;i;i--) Rank[ton[A[tmp[i]]]--]=tmp[i];
}
int L[MAXN];
int R[MAXN];
int chL[MAXN];
int chR[MAXN];
int fact[MAXN]={1,1};
int Ifact[MAXN]={1,1};
int ton[MAXN];
int Rank[MAXN];
int Q[MAXN];
int ans[MAXN];
int slove(int);
int main ()
{
for(int i=2;i<MAXN;i++) Ifact[i]=P-(int)((LL)(P/i)*Ifact[P%i]%P);
for(int i=1;i<MAXN;i++)
{
Ifact[i]=(LL)Ifact[i-1]*Ifact[i]%P;
fact[i]=(LL)fact[i-1]*i%P;
}
int n,c=1;
while(I.read(n))
{
int size=n+1;
for(int i=1;i<size;i++) I.read(L[i]);
for(int i=1;i<size;i++) I.read(R[i]);
Sort(L,R,ton,Rank,size);
for(int i=1;i<size;i++)
{
if(L[Rank[i]]==L[Rank[i-1]]) chL[Rank[i]]=Rank[i-1];
else chL[Rank[i]]=0;
}
Sort(R,L,ton,Rank,size);
for(int i=n-1; i ;i--)
{
if(R[Rank[i]]==R[Rank[i+1]]) chR[Rank[i]]=Rank[i+1];
else chR[Rank[i]]=0;
}
chR[Rank[n]]=0;
printf("Case #%d: %d\n",c++,slove(size));
}
return 0;
}
int slove(int size)
{
int rt=0,n=size-1;
for(int i=1;i<size;i++)
if(L[i]==1&&R[i]==n)
{
rt=i;
break;
}
if(!rt)return 0;
int l=0,r=1;
Q[0]=rt;
while(l<r)
{
int v=Q[l++];
if(chL[v]>0)
{
if(R[chL[v]]!=v-1)return 0;
Q[r++]=chL[v];
}
else if(L[v]!=v) return 0;
if(chR[v]>0)
{
if(L[chR[v]]!=v+1 )return 0;
Q[r++]=chR[v];
}
else if(R[v]!=v)return 0;
}
while(r--)
{
int v=Q[r];
int cl=chL[v];
int cr=chR[v];
if(cl==0||cr==0)
{
ans[v]=ans[cl]+ans[cr];
if(ans[v]<1)ans[v]=1;
continue;
}
int n=R[v]-L[v];
ans[v]=(LL)ans[cl]*ans[cr]%P;
ans[v]=(LL)ans[v]*fact[n]%P*Ifact[v-L[v]]%P*Ifact[R[v]-v]%P;
}
return ans[rt];
}