2022ICPC网络赛第二场 - G. Good Permutation
组合数学 + 树形DP + 单调栈
题目详情 - G Good Permutation (pintia.cn)
题意
对于一个 \([1,n]\;(1<=n<=10^6)\) 的排列 \(p\),有 \(m\;(1<=m<=10^6)\) 个限制
-
每个限制给定一组下标 \(l,r\), 满足 \(max(p_l,p_{l+1},...,p_r)-min(p_l,p_{l+1},...,p_r)=r-l\)
-
这 \(m\) 个区间只有不相交和完全包含两种关系
求满足上述条件的排列个数
思路
-
区间最大 - 区间最小 == 区间长度 - 1,意味着这个长度为 len 的区间就是连续 len 个数的排列
-
因为区间没有相交的情况,结合第一条,容易想到可以递归子问题求解
-
设大区间为结点 \(u\),它完全包含的各个小区间为结点 \(v_i\), 共有 tot 个,则 \(ans_u=\prod ans_{v_i}*(len_u-\sum len_{v_i}+tot)!\)
意思是把子区间用捆绑法当作是一个点,则一共有 \(len_u-\sum len_{v_i}+tot\) 个点,它们可以任意排列,再乘子区间内部的答案即可
- 现在考虑如何建树,可将区间按左端点递增,右端点递减排序并去重,用单调栈维护当前大区间的右端点(从栈底到栈顶变小),枚举第 \(i\) 个区间时
- 如果其左端点 > 大区间右端点,则当前大区间无法包括第 i 个区间,要 pop 出来,直到当前栈顶的大区间能够包含区间 i
- 把当前区间压栈
注意结构体用 unique 函数要重载 ==
代码
#include <bits/stdc++.h>
using namespace std;
#define endl "\n"
typedef long long ll;
typedef pair<int, int> PII;
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
int n, m;
struct Seg
{
int l, r;
bool operator<(const Seg &x) const
{
if (l == x.l)
return r > x.r;
return l < x.l;
}
bool operator==(const Seg &x) const
{
return l == x.l && r == x.r;
}
};
vector<Seg> c;
stack<int> stk;
vector<vector<int> > G(N);
ll fac[N];
void add(int u, int v)
{
G[u].push_back(v);
}
ll dfs(int u)
{
ll ans = 1;
int sum = c[u].r - c[u].l + 1;
for (int v : G[u])
{
sum -= c[v].r - c[v].l + 1;
ans = ans * dfs(v) % mod;
}
return ans * fac[sum + G[u].size()] % mod;
}
void presolve(int n)
{
fac[0] = 1;
for (int i = 1; i <= n; i++)
fac[i] = fac[i-1] * i % mod;
}
int main()
{
scanf("%d%d", &n, &m);
presolve(n);
c.push_back({1, n});
for (int i = 1; i <= m; i++)
{
int l, r;
scanf("%d%d", &l, &r);
c.push_back({l, r});
}
sort(c.begin(), c.end());
c.erase(unique(c.begin(), c.end()), c.end());
m = c.size();
stk.push(0);
for (int i = 1; i < m; i++)
{
while(!stk.empty() && c[i].l > c[stk.top()].r)
stk.pop();
add(stk.top(), i);
stk.push(i);
}
cout << dfs(0) << endl;
return 0;
}