Baby Ehab Plays with Permutations 题解

前言

题目链接:Codeforces洛谷

题意简述

你有一个长度为 n 的序列 p 满足 pi=i,你可以进行 x 次操作,每次操作找到两个不同的 i,j 并且交换 pi,pj,问最终有几个可能的序列。分别求出 x=1,,k 时的答案。

1n1091k200

题目分析

先考虑暴力 DP。显然原问题等价于求有多少排列经过 x 次交换后得到 pi=i。设 fi(x) 表示有多少长度为 x 的排列,至少经过 i 次操作可以得到原序列。边界 f0(x)=1。考虑转移。对于 fi(x) 考虑 px 的值。若 px=x,有 fi(x)=fi(x1);否则对于 px=1,,x1 需要进行一次交换 fi(x)=(x1)fi1(x1)。综合得到 fi(x)=fi(x1)+(x1)fi1(x1)。由于我们总是能浪费偶数次操作,所以对于一个 x,答案为 fx2t(n)

这么做时间复杂度是 Θ(nk) 的。

显然瓶颈在于 n,考虑优化掉它。发现进行 x 次操作,最多只有 2x 个关键点发生变化,不妨从这里入手。

不妨枚举有 i 个位置发生了变化,类似浪费 2t 次操作,对答案的贡献为 (ni)gx2t(i),其中 gi(x) 表示有多少长度为 x 的序列,每一个位置都发生了变化,至少经过 i 次操作变回原序列。

显然 g 不等价于 f,因为 f 计算的时候可能存在 pi=i。不妨找找二者关系。我们类比用 f 统计答案的过程,钦定只有某些位置发生变化,用 f 表示出 g,有:

f(x)=i=0x(xi)g(i)

这很二项式反演,不会的可以看看我的《学习笔记》

根据经典定理,我们得到:

g(x)=i=0x(1)xi(xi)f(i)

于是问题迎刃而解。时间复杂度 Θ(k3)

代码

部分分 Θ(nk)

点击查看代码
#include <cstdio>
#include <iostream>
using namespace std;
const int mod = 1e9 + 7;
inline int add(int a, int b) {
return a + b >= mod ? a + b - mod : a + b;
}
inline int mul(int a, int b) {
return 1ll * a * b % mod;
}
int n, k;
int f[1000010][220];
signed main() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; ++i) {
f[i][0] = 1;
for (int j = 1; j <= k; ++j) {
f[i][j] = add(f[i - 1][j], mul(i - 1, f[i - 1][j - 1]));
}
}
for (int i = 1; i <= k; ++i) {
int ans = 0;
for (int j = i; j >= 0; j -= 2)
ans = add(ans, f[n][j]);
printf("%d\n", ans);
}
return 0;
}

正解 Θ(k3)

#include <cstdio>
#include <iostream>
using namespace std;
const int mod = 1e9 + 7;
inline int add(int a, int b) {
return a + b >= mod ? a + b - mod : a + b;
}
inline int sub(int a, int b) {
return a - b < 0 ? a - b + mod : a - b;
}
inline int mul(int a, int b) {
return 1ll * a * b % mod;
}
int n, k;
int f[420][420], g[420][420];
int frac[420], Inv[420], ifrac[420], tfrac[420];
void init(int n = 400) {
frac[0] = ifrac[0] = tfrac[0] = 1;
for (int i = 1; i <= n; ++i) {
frac[i] = mul(frac[i - 1], i);
Inv[i] = i == 1 ? 1 : sub(0, mul(mod / i, Inv[mod % i]));
ifrac[i] = mul(ifrac[i - 1], Inv[i]);
tfrac[i] = mul(tfrac[i - 1], ::n - i + 1);
}
}
inline int C(int m) { // C(n, m) = n * ... * (n - m + 1) / m!
return mul(tfrac[m], ifrac[m]);
}
inline int C(int n, int m) {
return mul(frac[n], mul(ifrac[n - m], ifrac[m]));
}
signed main() {
scanf("%d%d", &n, &k), init(), f[0][0] = 1;
for (int i = 1; i <= min(n, k << 1); ++i) {
f[i][0] = 1;
for (int j = 1; j <= k; ++j) {
f[i][j] = add(f[i - 1][j], mul(i - 1, f[i - 1][j - 1]));
}
}
for (int i = 0; i <= min(n, k << 1); ++i)
for (int j = 0; j <= k; ++j)
for (int x = 0; x <= i; ++x) {
if ((i - x) & 1)
g[i][j] = sub(g[i][j], mul(C(i, x), f[x][j]));
else
g[i][j] = add(g[i][j], mul(C(i, x), f[x][j]));
}
for (int i = 1; i <= k; ++i) {
int res = 0;
for (int j = min(i << 1, n); j >= 0; --j) {
if (i >= 2) g[j][i] = add(g[j][i], g[j][i - 2]);
res = add(res, mul(g[j][i], C(j)));
}
printf("%d ", res);
}
return 0;
}
posted @   XuYueming  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 全程使用 AI 从 0 到 1 写了个小工具
· 快收藏!一个技巧从此不再搞混缓存穿透和缓存击穿
· AI 插件第二弹,更强更好用
· Blazor Hybrid适配到HarmonyOS系统
点击右上角即可分享
微信分享提示