Loading

2022 ICPC网络赛(二) G Good Permutation(树形DP 排列组合 建树)

2022 ICPC网络赛(二) G Good Permutation

题意:

​ 现在有一个长度为n的排列,现在给出m组约束条件,请问有多少种方案满足这个约束条件。

​ 约束条件:给出l, r,表示\([l, r]\)这个区间中的最大值-最小值等于\(r - l\)

思路:

​ 对于约束条件l,r可以进一步转化,转化为在[l, r]区间中放置一段连续的数字。

​ 题目中特别提到了,给出的约束区间是不会相交的,这样就可以做了。对于样例[1, 5], [1, 4], [1, 3],我们可以思考一下怎么求解。如果只有一个约束条件[1, 3]的话,那么答案应该是把[1, 3]打包成一个,内部全排列,和外面的2个点一起全排列。我们发现[1, 4]和[1, 3],还有[1, 4]和[1, 5]是存在一个递归关系的。通过手动计算,发现答案符合样例。那么我们思考怎么完成这个递归的过程。

​ 由于这个区间包含的关系看起来是个树形结构,所以我们考虑对这些约束条件建树,区间长度大的包含着区间长度小的,可以得到一个树形结构。这样我们从[1, n]的区间开始做一次树形DP就可以了。

区间u的方案数就是被其包含的所有子区间v的方案数的乘积 乘上 这个区间内的有多少团东西的全排列的方案数。

\[f[u] = \prod{f[v]} * fac[len[u] - lensum + cnt]; \]

实现:

const int mod = 1e9 + 7;
const int N = 1000005;
int n, m;
vector<int> g[N];
PII a[N];
ll fac[N];
stack<int> stk;

bool cmp(PII a, PII b)
{
	if(a.first == b.first)
		return a.second > b.second;
	return a.first < b.first;
}

ll len(PII v)	{return v.second - v.first + 1;}

void init()
{
	fac[0] = 1; for(int i = 1; i < N; i ++) fac[i] = fac[i - 1] * i % mod;
}

ll dfs(int u)
{
	ll res = 1;
	int cnt = 0, len = 0; //区间个数,区间长度
	for(int v : g[u])
	{
		res *= dfs(v), res %= mod;
		cnt ++;
		len += len(a[v]);
	}
	res *= fac[len(a[u]) - len + cnt], res %= mod;
	return res;
}

signed main()
{
	scanf("%d%d", &n, &m);
    init();
    for(int i = 1; i <= m; i ++)
    {
        int x, y;
        scanf("%d%d", &x, &y);
        a[i] = {x, y};
    }
    a[++ m] = {1, n};
    sort(a + 1, a + m + 1, cmp);
    m = unique(a + 1, a + m + 1) - (a + 1); //去重
    
    stk.push(1); //单调栈构造树形结构
    for(int i = 2; i <= m; i ++)
    {
        while(stk.size() && a[i].first > a[stk.top()].second)   stk.pop();
        if(stk.size())
            g[stk.top()].push_back(i), stk.push(i);
    }
	cout << dfs(1) << '\n';
}
posted @ 2022-10-08 15:04  DM11  阅读(99)  评论(0编辑  收藏  举报