HDU 6044 Limited Permutation dfs + 组合数(读入挂)

30 篇文章 0 订阅
8 篇文章 0 订阅

传送门: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)


官方题解:

根据 [l_i, r_i][li,ri] (1 \leq i \leq n)(1in) ,我们可以尝试线性地排序并建立一棵笛卡尔树,如果产生矛盾则答案为 00

具体来说,我们可以依次找到能够覆盖整个区间 [L, R][L,R] 的点 uu 。如果找不到则无解。如果找到多个,随便选一个,反正会在之后的决策中被中断。在这棵笛卡尔树上, uu 的左孩子(如果存在)应该能覆盖 [L, u - 1][L,u1] ,同理它的右孩子(如果存在)应该能覆盖 [u + 1, R][u+1,R] ,这意味着我们可以固定区间的一个端点,排序另外一个端点得到孩子节点。最终我们可以建立一棵笛卡尔树。

若存在一棵笛卡尔树,则这棵笛卡尔树是唯一的。每棵子树都基于相似的子问题,所以我们只需要在合并子树时计算子树的组合即可。例如 uu 有两个儿子 v_1v1 和 v_2v2 ,它们的子树对应的方案数分别为 f(v_1)f(v1) 和 f(v_2)f(v2) ,子树大小分别为 s(v_1)s(v1) 和 s(v_2)s(v2) ,则 uu 的子树对应的方案数为 \displaystyle f(u) = {s(v_1) + s(v_2) \choose s(v_1)} \cdot f(v_1) \cdot f(v_2)f(u)=(s(v1)s(v1)+s(v2))f(v1)f(v2) 。

由于使用基数排序,故处理的时间复杂度为 \mathcal{O}(n)O(n) ,主要时间还是花在了读入上面。我们可以加一些读入优化使得复杂度变成 \mathcal{O}(n \log_{10}{n})O(nlog10n) ,其中 \log_{10}{n} \leq 6log10n6 。

结合官方题解和上面的博客,我终于把思路理的差不多了,感觉有点分治的思想,就是先找到一个最大的区间,然后根据上面的分析,可以将其分成左右两个区间,而左右两个区间又满足和大区间一样的性质,有点技巧的地方在于如何判断一个区间在出没出现在输入数据中,很巧妙地一个办法就是按上面说的排序,并且排好序后刚好整个序列就是一个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;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值