首页 > 其他分享 >2022HDU多校第五场 - 1007 Count Set

2022HDU多校第五场 - 1007 Count Set

时间:2022-08-27 14:00:32浏览次数:63  
标签:Count Set return int 第五场 PolyS heap const size

置换群 + 生成函数 + NTT + 启发式合并/分治

题意

给一个 1-n 的排列 p 和一个非负整数 k,求大小为 k 的 {1, 2, 3,... n} 的子集合 T 的数量,满足
image

即 T 的元素按 p 置换一轮后和自身没有交集

思路

  1. \(i\) -> \(p_i\) 连边,找到 m 个环

  2. 设某个环的大小为 a,要找 b 个元素进入 T 集合,若这 b 个被置换一轮后与自身没有交集,即这 b 个元素在这个环上是不相邻的,求出该环的生成函数,\([x^b]f(x)\) 表示当前这个环中,取 b 个不相邻的元素的方案数

  3. 对于这 m 个环,这 m 个多项式求卷积,\([x^k]F(x)\) 即为答案

    然而 m 与 n 是一个数量级的,暴力求卷积的复杂度为 \(n^2logn\), 有启发式合并分治两种方法优化

    由于这 m 个环一共有 n 个元素,所以这 m 个多项式的项数之和是 m,因此可以启发式合并,用堆来找到当前项数最小的两个多项式,优先合并,这样复杂度为 \(nlog^2n\)

    也可以分治,复杂度为 \(T(n)=T(\frac n2)+O(nlogn)\), 解得 \(T(n)=O(nlog^2n)\)

求每个环的生成函数

步骤 2 中求生成函数是本题的关键,抽象为 a 个相同的球围成一个环,从中抽 b 个互不相邻的球的方案数

考虑链式的情况

如果 a 个球排成一排,抽 b 个互不相邻的球,可以先固定这 b 个球,然后把剩下 a - b 个球放到这 b + 1 个空位中,设每个空位放的球的个数为 \(x_0,x_1,x_2...x_b\)

因为这 b 个球要互不相邻,所以 \(x_1,x_2...x_{b-1}\) 至少为 1,\(x_0,x_b\) 可以为 0

若给 \(x_0,x_b\) 都提前放一个球,则转化为求 \(x_0+x_1+...+x_{b-1}+x_b=a-b+2\) 的正整数解个数

用隔板法,\(g(a,b)=\binom {a-b+1}{b}\)

考虑环上的情况

先随便选一个球,这个球有两种情况

  1. 被选在那 b 个球中,所以它相邻两个球不能选,并且可以在这个球处把环断开,方案数为 \(g(a-3,b-1)\)
  2. 没有被选中,也可以在这个球处断开,方案数为 \(g(a-1,b)\)

因此生成函数为 \(f(a,b)=g(a-3,b-1)+g(a-1,b)=\binom {a-b-1}{b-1}+\binom {a-b}b\)

代码

#include <bits/stdc++.h>
using namespace std;
#define clog(x) std::clog << (#x) << " is " << (x) << '\n';

using ll = long long;

namespace NFTS {
const int M = 998244353, g = 3;
std::vector<int> rev, roots{0, 1};
int powMod(int x, int n) {
	int r(1);
	while (n) {
		if (n&1) r = 1ll * r * x % M;
		n >>= 1; x = 1ll * x * x % M;
	}
	return r;
}
void dft(std::vector<int> &a) {
	int n = a.size();
	if ((int)rev.size() != n) {
		int k = __builtin_ctz(n) - 1;
		rev.resize(n);
		for (int i = 0; i < n; ++i) {
			rev[i] = rev[i >> 1] >> 1 | (i & 1) << k;
		}
	}
	if ((int)roots.size() < n) {
		int k = __builtin_ctz(roots.size());
		roots.resize(n);
		while ((1 << k) < n) {
			int e = powMod(g, (M - 1) >> (k + 1));
			for (int i = 1 << (k - 1); i < (1 << k); ++i) {
				roots[2 * i] = roots[i];
				roots[2 * i + 1] = 1ll * roots[i] * e % M;
			}
			++k;
		}
	}
	for (int i = 0; i < n; ++i) if (rev[i] < i) {
		std::swap(a[i], a[rev[i]]);
	}
	for (int k = 1; k < n; k *= 2) {
		for (int i = 0; i < n; i += 2 * k) {
			for (int j = 0; j < k; ++j) {
				int u = a[i + j];
				int v = 1ll * a[i + j + k] * roots[k + j] % M;
				int x = u + v, y = u - v;
				if (x >= M) x -= M;
				if (y < 0) y += M;
				a[i + j] = x;
				a[i + j + k] = y;
			}
		}
	}
}
void idft(std::vector<int> &a) {
	int n = a.size();
	std::reverse(a.begin() + 1, a.end());
	dft(a);
	int inv = powMod(n, M - 2);
	for (int i = 0; i < n; ++i) {
		a[i] = 1ll * a[i] * inv % M;
	}
}
} //namespace NFTS

// 如果需要换模 M,那么就把 M 当做全局变量提出来,NFT 中 g, rev, root 需要重新初始化。
// 如果只需要换模几个常数 M,可使用 template<ll M>(但是不是特别推荐)
class PolyS {
	void reverse() {
		std::reverse(a.begin(), a.end());
	}
public:
	static const int M = NFTS::M;
	static  const int inv2 = (M + 1) / 2;
	std::vector<int> a;
	PolyS() {}
	PolyS(int x) { if (x) a = {x};}
	PolyS(const std::vector<int> _a) : a(_a) {}
	int size() const { return a.size();}
	int& operator[](int id) { return a[id];}
	int at(int id) const {
		if (id < 0 || id >= (int)a.size()) return 0;
		return a[id];
	}
	PolyS operator-() const {
		auto A = *this;
		for (auto &x : A.a) x = (x == 0 ? 0 : M - x);
		return A;
	}
	PolyS mulXn(int n) const {
		auto b = a;
		b.insert(b.begin(), n, 0);
		return PolyS(b);
	}
	PolyS modXn(int n) const {
		if (n > size()) return *this;
		return PolyS({a.begin(), a.begin() + n});
	}
	PolyS divXn(int n) const {
		if (size() <= n) return PolyS();
		return PolyS({a.begin() + n, a.end()});
	}
	PolyS &operator+=(const PolyS &rhs) {
		if (size() < rhs.size()) a.resize(rhs.size());
		for (int i = 0; i < rhs.size(); ++i) {
			if ((a[i] += rhs.a[i]) >= M) a[i] -= M;
		}
		return *this;
	}
	PolyS &operator-=(const PolyS &rhs) {
		if (size() < rhs.size()) a.resize(rhs.size());
		for (int i = 0; i < rhs.size(); ++i) {
			if ((a[i] -= rhs.a[i]) < 0) a[i] += M;
		}
		return *this;
	}
	PolyS &operator*=(PolyS rhs) {
		int n = size(), m = rhs.size(), tot = std::max(1, n + m - 1);
		int sz = 1 << std::__lg(tot * 2 - 1);
		a.resize(sz);
		rhs.a.resize(sz);
		NFTS::dft(a);
		NFTS::dft(rhs.a);
		for (int i = 0; i < sz; ++i) {
			a[i] = 1ll * a[i] * rhs.a[i] % M;
		}
		NFTS::idft(a);
		return *this;
	}
	PolyS operator+(const PolyS &rhs) const {
		return PolyS(*this) += rhs;
	}
	PolyS operator-(const PolyS &rhs) const {
		return PolyS(*this) -= rhs;
	}
	PolyS operator*(PolyS rhs) const {
		return PolyS(*this) *= rhs;
	}
}; // PolyS 全家桶测试:https://www.luogu.com.cn/training/3015#information
const int N = 5e5 + 10;
const int mod = NFTS::M;
int n, k, idx;
int p[N], cnt[N];
bool st[N];
ll fac[N], finv[N];
PolyS a[N/2];
ll qmi(ll a, ll b)
{
	ll ans = 1;
	while(b)
	{
		if (b & 1)
			ans = ans * a % mod;
		b >>= 1;
		a = a * a % mod;
	}
	return ans;
}
void presolve(int n)
{
	fac[0] = finv[0] = 1;
	for (int i = 1; i <= n; i++)
		fac[i] = fac[i-1] * i % mod;
	finv[n] = qmi(fac[n], mod - 2);
	for (int i = n - 1; i >= 1; i--)
		finv[i] = finv[i+1] * (i + 1) % mod;
}
void init()
{
	idx = 0;
	fill(st, st + n + 2, false);
}
ll C(int n, int m)
{
	if (m < 0 || n - m < 0)
		return 0;
	return fac[n] * finv[m] % mod * finv[n-m] % mod;
}

struct cmp
{
	bool operator()(int x, int y)
	{
		return a[x].size() > a[y].size();
	}
};
//启发式合并
ll solve()
{
	priority_queue<int, vector<int>, cmp> heap;
	for (int i = 0; i < idx; i++)
		heap.push(i);
	while(heap.size() > 1)
	{
		int p = heap.top(); heap.pop();
		int q = heap.top(); heap.pop();
		a[p] *= a[q];
		a[p] = a[p].modXn(k + 1);
		heap.push(p);
	}
	int ver = heap.top();
	return a[ver].at(k);
}
//分治
PolyS solve(int l, int r)
{
	if (l == r)
		return a[l];
	int mid = l + r >> 1;
	return solve(l, mid) * solve(mid + 1, r);
}
int main()
{
	int T;
	scanf("%d", &T);
	presolve(N - 5);
	while(T--)
	{
		scanf("%d%d", &n, &k);
		init();
		for (int i = 1; i <= n; i++)
			scanf("%d", p + i);
		for (int i = 1; i <= n; i++)
		{
			if (st[i])
				continue;
			st[i] = true;
			int tot = 1, now = i;
			while(i != p[now])
			{
				tot++;
				st[p[now]] = true;
				now = p[now];
			}
			if (tot == 1)
				continue;
			cnt[idx++] = tot;
		}
		for (int i = 0; i < idx; i++)
		{
			int tot = cnt[i];
			vector<int> tmp(min(k + 1, tot / 2 + 1));
			for (int j = 0; j <= min(k, tot / 2); j++)
				tmp[j] = (C(tot - j, j) + C(tot - j - 1, j - 1)) % mod;
			a[i] = PolyS(tmp);
		}
		ll ans = solve();
		printf("%lld\n", ans);
	}
	return 0;
}

标签:Count,Set,return,int,第五场,PolyS,heap,const,size
From: https://www.cnblogs.com/hzy717zsy/p/16630458.html

相关文章