LOJ#6077. 「2017 山东一轮集训 Day7」逆序对(容斥+生成函数+动态规划)

LOJ#6077. 「2017 山东一轮集训 Day7」逆序对(容斥+生成函数+动态规划)

题目大意

给定 \(n\)\(k\) ,请求出长度为 \(n\) 的逆序对数恰好为 \(k\) 的排列的个数。答案对 P 取模。

数据范围

对于 100% 的数据,\(1 \le n,k \le 100000\)

解题思路

很好的计数题

两种思路

生成函数

第 i 个元素对答案的贡献为 \([0, i-1]\),其生成函数为

\[(1)*(1+x^1)*(1+x^2+x^3)\cdots(1+x^2+\cdots+x^{n-1})=\frac {\prod(1-x^i)}{(1-x)^n}\\ \frac 1{(1-x)^n}=(1+x+x^2+\cdots)^n \]

由此可知,分母的 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 个元素超出贡献

\[Ans = \sum_{i=0}\sum_{j=0}^k(-1)^i*f[i][j]*{k-j+n-1\choose n-1} \]

算 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]\),其生成函数为

\[(1)*(1+x^1)*(1+x^2+x^3)\cdots(1+x^2+\cdots+x^{n-1})=\frac {\prod(1-x^i)}{(1-x)^n}\\ \frac 1{(1-x)^n}=(1+x+x^2+\cdots)^n \]

由此可知,分母的 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 个元素超出贡献

\[Ans = \sum_{i=0}\sum_{j=0}^k(-1)^i*f[i][j]*{k-j+n-1\choose n-1} \]

算 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;
}
posted @ 2020-05-09 08:27  Hs-black  阅读(458)  评论(0编辑  收藏  举报