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';
}