来自学长的馈赠4 社论

A. 活动投票

主元素问题,一个经典做法是摩尔投票法(Boyer–Moore majority vote algorithm).

大概就是维护一个计数器 \(c\) 和目前答案 \(A\) .

每次考虑加入一个数 \(x\)

  • 如果 \(c=0\),则直接 \(A=x\)\(c=1\) .
  • 若不然,如果 \(A\neq x\),则 \(c\gets c-1\),否则 \(c\gets c+1\) .

正确性显然 只可意会,不可言传

其他做法不想说了 .

Code
using namespace std;
typedef long long ll;
typedef double db;
typedef pair<int, int> pii;
int ans, x, n;
int main()
{
	long long x = ans;
	scanf("%d", &n);
	int cc = 0, ans = -1;
	for (int i=1; i<=n; i++)
	{
		scanf("%d", &x);
		if (ans == -1){++cc; ans = x;}
		else if (x != ans)
		{
			if (cc) --cc;
			else{++cc; ans = x;}
		}
		else ++cc;
	}
	printf("%d\n", ans);
	return 0;
}

关于摩尔:摩尔,即 mol,\(1\text{ mol}\) 是精确包含 \(6.02214076\times10^{23}\) 个原子或分子等基本单元的系统的物质的量 .

B. 大佬

首先根据期望线性性,答案就是 \([1,k]\) 的答案乘 \(n-k+1\) .

于是问题就变成一个序列 \(\{a\}\),每个元素在 \([1,m]\) 均匀随机,问最大值期望 .

这个可以简单容斥,详见 SoyTony .

这个太 simple 了,我们如何让它 exciting 一点呢?

考虑维护序列 \(\{p\}\)\(p_k\) 表示最大值为 \(k\) 的概率,于是答案就是 \(\displaystyle\sum_{k\ge 0}a_kp_k\) .

定义魔怔运算为:

\[f\circ g=\sum_{i=1}^n\left(g_i\sum_{j=1}^if_i+f_i\sum_{j=1}^ig_i-f_ig_i\right) \]

\(\{s\}\) 序列是全为 \(\dfrac 1m\) 的序列 .

考虑在原序列末尾追加一个数产生的贡献,根据简单容斥我们可以发现 \(p\gets p\circ s\)(怎么还要简单容斥

显而易见 \(\circ\) 具有结合律,直接快速幂即可 .

感谢 zcyzcy 提出这个 exciting 的算法 .

魔怔运算可以前缀和做到线性。

时间复杂度 \(O(m\log k)\),简单容斥的复杂度也是一样的 .

Code
using namespace std;
const int P = 1e9+7;
typedef vector<int> vi;
typedef long long ll;
typedef double db;
typedef pair<int, int> pii;
int n, m, k;
inline int qpow(int a, int n)
{
	int ans = 1;
	while (n)
	{
		if (n & 1) ans = 1ll * ans * a % P;
		a = 1ll * a * a % P; n >>= 1;
	} return ans;
}
inline int inv(int x){return qpow(x, P-2);}
inline vi conv(vi a, vi b)
{
	int n = a.size(); assert(a.size() == b.size());
	for (int i=1; i<n; i++) (a[i] += a[i-1]) %= P;
	for (int i=1; i<n; i++) (b[i] += b[i-1]) %= P;
	vi ans(n);
	ans[0] = 1ll * a[0] * b[0] % P;
	for (int i=1; i<n; i++) ans[i] = ((1ll * a[i] * (b[i] - b[i-1]) % P + 1ll * b[i] * (a[i] - a[i-1]) % P - 1ll * (a[i] - a[i-1]) * (b[i] - b[i-1]) % P) % P + P) % P;
	return ans;
}
inline vi qpow(vi a, int n)
{
	vi ans(a.size()); ans[0] = 1;
	while (n)
	{
		if (n & 1) ans = conv(ans, a);
		a = conv(a, a); n >>= 1;
	} return ans;
}
int main()
{
	scanf("%d%d%d", &n, &m, &k); int ii = inv(m);
	vi a; a.resize(m); vi b(m, ii);
	for(int i=0; i<m; i++) scanf("%d", &a[i]);
	b = qpow(b, k);
	int ans = 0;
	for (int i=0; i<m; i++) (ans += 1ll * a[i] * b[i] % P) %= P;
	printf("%lld\n", 1ll * ans * (n-k+1) % P);
	return 0;
}

但是数据范围那么小为什么要 \(O(m\log k)\) 啊 .

听说有 DP 做法 /yun

C. Dp搬运工3

往空位里插排列 . 令 \(dp_{i,j,k}\) 表示做到 \([1,i]\),有 \(j\) 个配对,\(\operatorname{magic}(A,B)=k\) 的答案 .

于是讨论一下就可以转移了,\(O(n^3)\) .

有一个做法是钦定 \(B\) 为标准排列然后算 \(A\),没咋看 .

Code
using namespace std;
const int N = 111, P = 998244353;
typedef vector<int> vi;
typedef long long ll;
typedef double db;
typedef pair<int, int> pii;
int n, kkk, dp[N][N][N*N]; 
int main()
{
	scanf("%d%d", &n, &kkk); dp[0][0][0] = 1;
	for (int i=0; i<n; i++)
		for (int j=max(0, 2*i-n); j<=i; j++)
			for (int k=0; k<=n*n; k++)
			{
				int p = (i - j) << 1, q = n - j - p;
				(dp[i+1][j][k] += 1ll * dp[i][j][k] * q % P * (q-1) % P) %= P;
				(dp[i+1][j+1][k+i+1] += 1ll * dp[i][j][k] * q % P) %= P;
				(dp[i+1][j+1][k+i+1] += 1ll * dp[i][j][k] * p % P * q % P) %= P;
				(dp[i+1][j+2][k+2*(i+1)] += 1ll * dp[i][j][k] * (p>>1) % P * (p>>1) % P) %= P;
			}
	int ans = 0;
	for (int i=kkk; i<=n*n; i++) (ans += dp[n][n][i]) %= P;
	printf("%d\n", ans);
	return 0;
}

D. Beautiful

题目链接 .

NOIP 模拟赛出 *2900 正常吗?

试图魔改 Cantor 展开计算带限制排列字典序,失败

下面复读一波题解 .


约定:错排数为 \(D_n=(n-1)(D_{n-1}+D_{n-2})\) .

广义错排数 \(D_{n,m}\) 表示 \(1\sim n\) 的排列有 \(m\) 个限制(形如第 \(i\) 位不能为 \(i\))的方案数 .

边界:

  • \(m=0\)\(D_{n,m}=n!\) .
  • \(n=m\)\(D_{n,m}=D_n\) .

于是只剩下 \(n<m\) 情况,钦定 \(i\) 不被限制,于是考虑第 \(i\) 位放的是被限制的还是不被限制的即可,递推式:

\[D_{n,m}=mD_{n−1,m−1}+(n−m)D_{n−1,m} \]

这个可以直接 \(O(n^2)\) 干 .


考虑直接扫,然后后面的贡献就是错排数的次幂,前面已经算过了,只需要算当前行的贡献(类似 Cantor 展开)

设当前扫到 \((i,j)\),于是对于当前行剩下 \(n−j\) 个元素,它们的填法受到 \(b_{i−1,j}\sim b_{i−1,n}\)的限制,但不完全受到限制。具体地,求出有多少个数 \(v\) 使得 \(v\) 没有 在 \(b_{i,1}\sim b_{i,j}\) 当中出现过,且没有在 \(b_{i-1,1}\sim b_{i-1,j}\) 当中出现过,记作 \(L\),那么相当于是 \(n−j\) 阶排列有多少个限制了 \(L\) 个位置的错排,就是广义错排数 \(D_{n-j,L}\) .

为了方便记 \(\operatorname B_i(l,r)\)\(b_{i,l}\sim b_{i,r}\) 组成的集合 .

现在考虑如何快速求 \(L\) . 因为唯一不确定因素是 \(b_{i,j}\),所以我们先求出满足 \(v\in\operatorname B_i(1, j-1)\land v\notin\operatorname B(1, j)\)\(v\) 的个数 \(L\)\(b_{i,j}\) 对真正 \(L\) 的贡献最多只是当 \(b_{i,j}\notin\operatorname B_{i-1}(1,j)\) 时要 \(L\gets L-1\) .

因此,只需在 \(v\notin\operatorname B_i(1,j-1)\) 的前提下,维护存在多少 \(v\) 使得 \(v\notin\operatorname B_{i-1}(1,j)\),并且支持查询 \(\le V\)\(v\) 的个数 .

对当前行和上一行分别开一个权值树状数组就可随手维护,注意 \(b_{i,j}\notin\operatorname B_{i-1}(1,j)\) 时要减一下,后面还得加回去 .

时间复杂度 \(O(n^2\log n)\),注意原题是 0-indexed,模拟赛模数变一下,并且 1-indexed,不要直接 cv 了 .

Code (CF1085G)
using namespace std;
const int N = 2222, P = 998244353;
typedef long long ll;
typedef double db;
typedef pair<int, int> pii;
int n, k, fac[N], a[N][N], d[N][N], pwd[N];
struct FenwickTree
{
	int a[N];
	inline void clear(){memset(a, 0, sizeof a);}
	inline int lowbit(int x) {return x & -x;}
	inline void add(int i, int x){if (!i) return ; for (; i<=n; i+=lowbit(i)) (a[i] += x) %= P;}
	inline int query(int i){int ans = 0; for (; i; i&=i-1) (ans += a[i]) %= P; return ans;}
}null, full, T, nT;
void init()
{
	d[0][0] = pwd[0] = 1;
	for (int i=1; i<=n; i++)
		for (int j=0; j<=i; j++)
		{
			if (!j) d[i][j] = 1ll * d[i-1][0] * i % P;
			else if (i==j) d[i][i] = ((i == 1)? 0 : 1ll * (i-1) * (d[i-1][i-1] + d[i-2][i-2]) % P);
			else d[i][j] = (1ll * j * d[i-1][j-1] % P + 1ll * (i-j) * d[i-1][j] % P) % P;
		}
	for (int i=1; i<=n; i++) pwd[i] = 1ll * pwd[i-1] * d[n][n] % P;
	for (int i=1; i<=n; i++) full.add(i, 1);
}
bool A[N], B[N];
int main()
{
	scanf("%d", &n); init();
	for (int i=1; i<=n; i++)
		for (int j=1; j<=n; j++) scanf("%d", a[i]+j);
	int ans = 0; 
	for (int i=1; i<=n; i++)
	{
		memset(A, false, sizeof A); memset(B, false, sizeof B);
		int L = n; T = null; nT = full;
		ll _ = 0;
		for (int j=1; j<=n; j++)
		{
			if (!B[a[i-1][j]]){--L; nT.add(a[i-1][j], -1);}
			(_ += 1ll * T.query(a[i][j] - 1) * d[n-j][(i>1) * L] % P) %= P;
			if (L || (i == 1)) (_ += 1ll * nT.query(a[i][j] - 1) * d[n-j][(i>1) * (L-1)] % P) %= P;
			if (A[a[i][j]]) T.add(a[i][j], -1);
			else nT.add(a[i][j], -1);
			A[a[i-1][j]] = B[a[i][j]] = 1;
			if (!B[a[i-1][j]]) T.add(a[i-1][j], 1);
			if (!A[a[i][j]]) --L;
		}
		(ans += 1ll * _ * pwd[n-i] % P) %= P;
	} printf("%d\n", ans);
	return 0;
}
posted @ 2022-07-24 21:12  yspm  阅读(114)  评论(1)    收藏  举报
😅​