LOJ#6077. 「2017 山东一轮集训 Day7」逆序对(容斥+生成函数+动态规划)
LOJ#6077. 「2017 山东一轮集训 Day7」逆序对(容斥+生成函数+动态规划)
题目大意
给定 \(n\),\(k\) ,请求出长度为 \(n\) 的逆序对数恰好为 \(k\) 的排列的个数。答案对 P 取模。
数据范围
对于 100% 的数据,\(1 \le n,k \le 100000\)
解题思路
很好的计数题
两种思路
生成函数
第 i 个元素对答案的贡献为 \([0, i-1]\),其生成函数为
由此可知,分母的 x 项就是 \(x + n-1\choose n-1\) (隔板法)
分子可以视为用 x 个数凑出和为 s 的方案数,同时 x 为奇数 s 就是负的
只需求出 \(g[i] = \sum_{i=0} (-1)^i*f[i][s]\)
则 \(Ans = \sum_{i=0}g[i]*{k - i + n - 1 \choose n-1}\)
容斥
枚举有 k 个元素超出贡献
算 f 数组
不难发现,i 的最大值大概在 \(\sqrt n\) 附近,因为是 x 个不相同的正整数
暴力转移跑背包的复杂度是 \(\Theta(n^2\sqrt n)\) 的,不行啊
由于 “体积” 是连续整数,我们有如下方法
设当前已选集合 S
- 将 S 集合中的数都 +1,\(f[x][s] + f[x][s-x]\)
- 将 S 集合中的数都 +1 并将 1 插入 S 集合,\(f[x][s] + f[x-1][s-x]\)
- +1 时最后一位有可能大于 n,将它减掉 \(f[x][s] - f[x-1][s-n-1]\)
据以上分析,可由 \(\Theta(n \sqrt n)\) 的时间复杂度求出答案
带码
#include <queue>
#include <vector>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define MP make_pair
#define ll long long
#define fi first
#define se second
using namespace std;
template <typename T>
void read(T &x) {
x = 0; bool f = 0;
char c = getchar();
for (;!isdigit(c);c=getchar()) if (c=='-') f=1;
for (;isdigit(c);c=getchar()) x=x*10+(c^48);
if (f) x=-x;
}
template<typename F>
inline void write(F x)
{
static short st[30];short tp=0;
if(x<0) putchar('-'),x=-x;
do st[++tp]=x%10,x/=10; while(x);
while(tp) putchar('0'|st[tp--]);
putchar('\n');
}
template <typename T>
inline void Mx(T &x, T y) { x < y && (x = y); }
template <typename T>
inline void Mn(T &x, T y) { x > y && (x = y); }
const int P = 1e9+7;
const int N = 200050;
const int M = 450;
ll jie[N], inv[N], ans;
ll f[M][N], n, k;
ll C(ll n, ll m) {
return jie[n] * inv[n-m] % P * inv[m] % P;
}
int main() {
read(n), read(k);
inv[0] = jie[0] = inv[1] = jie[1] = 1;
for (int i = 2;i <= n + k; i++)
jie[i] = jie[i-1] * i % P,
inv[i] = (P - P / i) * inv[P%i] % P;
for (int i = 2;i <= n + k; i++)
inv[i] = inv[i-1] * inv[i] % P;
f[0][0] = 1; int lim = M;
for (int i = 1;i < lim; i++) {
for (int j = i;j <= k; j++) {
f[i][j] = (f[i][j] + f[i][j-i] + f[i-1][j-i]) % P;
if (j > n) f[i][j] = (f[i][j] + P - f[i-1][j-n-1]) % P;
// cout << f[i][j] << ' ';
}
}
for (int i = 0;i <= k; i++) {
ll res = 0;
for (int j = 0;j < lim; j++)
j & 1 ? res -= f[j][i] : res += f[j][i];
res = (res % P + P) % P;
ans = (ans + res * C(k - i + n - 1, n - 1)) % P;
}
write(ans);
return 0;
}
------------恢复内容开始------------
LOJ#6077. 「2017 山东一轮集训 Day7」逆序对(容斥+生成函数+动态规划)
题目大意
给定 \(n\),\(k\) ,请求出长度为 \(n\) 的逆序对数恰好为 \(k\) 的排列的个数。答案对 P 取模。
数据范围
对于 100% 的数据,\(1 \le n,k \le 100000\)
解题思路
很好的计数题
两种思路
生成函数
第 i 个元素对答案的贡献为 \([0, i-1]\),其生成函数为
由此可知,分母的 x 项就是 \(x + n-1\choose n-1\) (隔板法)
分子可以视为用 x 个数凑出和为 s 的方案数,同时 x 为奇数 s 就是负的
只需求出 \(g[i] = \sum_{i=0} (-1)^i*f[i][s]\)
则 \(Ans = \sum_{i=0}g[i]*{k - i + n - 1 \choose n-1}\)
容斥
枚举有 k 个元素超出贡献
算 f 数组
不难发现,i 的最大值大概在 \(\sqrt n\) 附近,因为是 x 个不相同的正整数
暴力转移跑背包的复杂度是 \(\Theta(n^2\sqrt n)\) 的,不行啊
由于 “体积” 是连续整数,我们有如下方法
设当前已选集合 S
- 将 S 集合中的数都 +1,\(f[x][s] + f[x][s-x]\)
- 将 S 集合中的数都 +1 并将 1 插入 S 集合,\(f[x][s] + f[x-1][s-x]\)
- +1 时最后一位有可能大于 n,将它减掉 \(f[x][s] - f[x-1][s-n-1]\)
据以上分析,可由 \(\Theta(n \sqrt n)\) 的时间复杂度求出答案
带码
#include <queue>
#include <vector>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define MP make_pair
#define ll long long
#define fi first
#define se second
using namespace std;
template <typename T>
void read(T &x) {
x = 0; bool f = 0;
char c = getchar();
for (;!isdigit(c);c=getchar()) if (c=='-') f=1;
for (;isdigit(c);c=getchar()) x=x*10+(c^48);
if (f) x=-x;
}
template<typename F>
inline void write(F x)
{
static short st[30];short tp=0;
if(x<0) putchar('-'),x=-x;
do st[++tp]=x%10,x/=10; while(x);
while(tp) putchar('0'|st[tp--]);
putchar('\n');
}
template <typename T>
inline void Mx(T &x, T y) { x < y && (x = y); }
template <typename T>
inline void Mn(T &x, T y) { x > y && (x = y); }
const int P = 1e9+7;
const int N = 200050;
const int M = 450;
ll jie[N], inv[N], ans;
ll f[M][N], n, k;
ll C(ll n, ll m) {
return jie[n] * inv[n-m] % P * inv[m] % P;
}
int main() {
read(n), read(k);
inv[0] = jie[0] = inv[1] = jie[1] = 1;
for (int i = 2;i <= n + k; i++)
jie[i] = jie[i-1] * i % P,
inv[i] = (P - P / i) * inv[P%i] % P;
for (int i = 2;i <= n + k; i++)
inv[i] = inv[i-1] * inv[i] % P;
f[0][0] = 1; int lim = M;
for (int i = 1;i < lim; i++) {
for (int j = i;j <= k; j++) {
f[i][j] = (f[i][j] + f[i][j-i] + f[i-1][j-i]) % P;
if (j > n) f[i][j] = (f[i][j] + P - f[i-1][j-n-1]) % P;
// cout << f[i][j] << ' ';
}
}
for (int i = 0;i <= k; i++) {
ll res = 0;
for (int j = 0;j < lim; j++)
j & 1 ? res -= f[j][i] : res += f[j][i];
res = (res % P + P) % P;
ans = (ans + res * C(k - i + n - 1, n - 1)) % P;
}
write(ans);
return 0;
}