传送门:HDU6044
题意:给出n个区间,对于第i个区间[li,ri]有li<=i<=ri,对于任意1<=L<=i<=R<=n,当且仅当li<=L<=i<=R<=ri时P[i]=min(P[L],P[L+1],...,P[R]) P[1..n]为1到n的一种排列,问有多少种满足给定区间条件的排列。
一开始没看到题目中的if and only if,看人家的博客都看不懂。。后来翻到一篇使我醍醐灌顶的博客,点击打开链接
转载至下:
首先要理解题意:当前仅当li<=L<=i<=R<=ri时P[i]=min(P[L],P[L+1],...,P[R])
因此对于P[i]一定有P[i]>P[li-1]且P[i]>P[ri+1],进一步说区间[li,ri](除了[1,n])一定被某个区间[lj,rj]包含,且 j=li-1或j=ri+1
即区间j可分成[lj,j-1]和[j+1,rj]
我们把n个区间按L升序R降序进行排序(这样得到的区间LR正是前序遍历的区间)。得到的第1个区间一定要是[1,n](1比任何数都小),否则不合法,输出0;设这个区间对应的是第i个数,因此区间可再分为[1,i-1]和[i+1,n],看是否有这2个区间,如果没有则不合法,输出0...直到区间不可再分。
现在再来考虑方法数:设f(i)为区间i内的方法数,u,v分别为左右子区间,i内一共有ri-li+1个数,除去中间一个,要从中选i-li个数放入左区间,剩下的放入右区间,因此答案为:f(u)*f(v)*C(ri-li,i-li)
官方题解:
根据 [li,ri] (1≤i≤n) ,我们可以尝试线性地排序并建立一棵笛卡尔树,如果产生矛盾则答案为 0。
具体来说,我们可以依次找到能够覆盖整个区间 [L,R] 的点 u 。如果找不到则无解。如果找到多个,随便选一个,反正会在之后的决策中被中断。在这棵笛卡尔树上, u 的左孩子(如果存在)应该能覆盖 [L,u−1] ,同理它的右孩子(如果存在)应该能覆盖 [u+1,R] ,这意味着我们可以固定区间的一个端点,排序另外一个端点得到孩子节点。最终我们可以建立一棵笛卡尔树。
若存在一棵笛卡尔树,则这棵笛卡尔树是唯一的。每棵子树都基于相似的子问题,所以我们只需要在合并子树时计算子树的组合即可。例如 u 有两个儿子 v1 和 v2 ,它们的子树对应的方案数分别为 f(v1) 和 f(v2) ,子树大小分别为 s(v1) 和 s(v2) ,则 u 的子树对应的方案数为 f(u)=(s(v1)s(v1)+s(v2))⋅f(v1)⋅f(v2) 。
由于使用基数排序,故处理的时间复杂度为 O(n) ,主要时间还是花在了读入上面。我们可以加一些读入优化使得复杂度变成 O(nlog10n) ,其中 log10n≤6 。
结合官方题解和上面的博客,我终于把思路理的差不多了,感觉有点分治的思想,就是先找到一个最大的区间,然后根据上面的分析,可以将其分成左右两个区间,而左右两个区间又满足和大区间一样的性质,有点技巧的地方在于如何判断一个区间在出没出现在输入数据中,很巧妙地一个办法就是按上面说的排序,并且排好序后刚好整个序列就是一个dfs序(dfs过程中先遍历左区间,再遍历右区间),这一点感觉很难想到,也不是很好理解,模拟一下递归的过程以及排序后的结果可以好理解一些,想不到排序的话我们可以用map标记pair,键值为下标,这样也能实现logn判断区间是否出现。如果还是不明就里的话建议好好研读上面给出的博客题解,不要忽略任何一个细节。
代码:
#include<bits/stdc++.h>
#define ll long long
#define pi acos(-1)
#define MAXN 1000100
#define inf 0x3f3f3f3f
using namespace std;
ll fac[MAXN], inv[MAXN];
const int mod = 1e9 + 7;
namespace fastIO {
#define BUF_SIZE 100000
//fread -> read
bool IOerror = 0;
inline char nc() {
static char buf[BUF_SIZE], *p1 = buf + BUF_SIZE, *pend = buf + BUF_SIZE;
if(p1 == pend) {
p1 = buf;
pend = buf + fread(buf, 1, BUF_SIZE, stdin);
if(pend == p1) {
IOerror = 1;
return -1;
}
}
return *p1++;
}
inline bool blank(char ch) {
return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
}
inline void read(int &x) {
char ch;
while(blank(ch = nc()));
if(IOerror)
return;
for(x = ch - '0'; (ch = nc()) >= '0' && ch <= '9'; x = x * 10 + ch - '0');
}
#undef BUF_SIZE
};
using namespace fastIO;
void init()
{
inv[0] = fac[0] = inv[1] = fac[1] = 1;
for(int i = 1; i < MAXN; i++)
fac[i] = fac[i - 1] * i % mod;
for(int i = 2; i < MAXN; i++)
inv[i] = (mod - (mod / i)) * inv[mod % i] % mod;//lucas定理求逆元
for(int i = 1; i < MAXN; i++)
inv[i] = inv[i - 1] * inv[i] % mod;
}
struct node{
int l, r, id;
bool operator < (node &a) const
{
if(l == a.l) return r > a.r;
return l < a.l;
}
}p[MAXN];
ll C(int n, int m)//求组合数 C n,m
{
return (fac[n] * inv[m] % mod) * inv[n - m] %mod;
}
int flag, now;
ll dfs(int l, int r)
{
if(!flag) return 0;//注意这几个判断不要把顺序弄反
if(l > r) return 1;//我就因为这个wa了一晚上。。
if(p[now].l != l || p[now].r != r)
{
flag = 0;
return 0;
}
node &tmp = p[now++];
ll ans;
ans = C(tmp.r - tmp.l, tmp.id - tmp.l) * dfs(tmp.l, tmp.id - 1) % mod;
ans *= dfs(tmp.id + 1, tmp.r);
return ans % mod;
}
int main()
{
init();
int n, kase = 1;
while(read(n), !IOerror)
{
for(int i = 1; i <= n; i++)
read(p[i].l);
for(int i = 1; i <= n; i++)
read(p[i].r), p[i].id = i;
sort(p + 1, p + n + 1);
flag = now = 1;
printf("Case #%d: %lld\n", kase++, dfs(1, n));
}
return 0;
}