来自学长的馈赠4 社论

A. 活动投票

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

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

每次考虑加入一个数 x

  • 如果 c=0,则直接 A=xc=1 .
  • 若不然,如果 Ax,则 cc1,否则 cc+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 mol 是精确包含 6.02214076×1023 个原子或分子等基本单元的系统的物质的量 .

B. 大佬

首先根据期望线性性,答案就是 [1,k] 的答案乘 nk+1 .

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

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

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

考虑维护序列 {p}pk 表示最大值为 k 的概率,于是答案就是 k0akpk .

定义魔怔运算为:

fg=i=1n(gij=1ifi+fij=1igifigi)

{s} 序列是全为 1m 的序列 .

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

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

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

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

时间复杂度 O(mlogk),简单容斥的复杂度也是一样的 .

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(mlogk) 啊 .

听说有 DP 做法 /yun

C. Dp搬运工3

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

于是讨论一下就可以转移了,O(n3) .

有一个做法是钦定 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 展开计算带限制排列字典序,失败

下面复读一波题解 .


约定:错排数为 Dn=(n1)(Dn1+Dn2) .

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

边界:

  • m=0Dn,m=n! .
  • n=mDn,m=Dn .

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

Dn,m=mDn1,m1+(nm)Dn1,m

这个可以直接 O(n2) 干 .


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

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

为了方便记 Bi(l,r)bi,lbi,r 组成的集合 .

现在考虑如何快速求 L . 因为唯一不确定因素是 bi,j,所以我们先求出满足 vBi(1,j1)vB(1,j)v 的个数 Lbi,j 对真正 L 的贡献最多只是当 bi,jBi1(1,j) 时要 LL1 .

因此,只需在 vBi(1,j1) 的前提下,维护存在多少 v 使得 vBi1(1,j),并且支持查询 Vv 的个数 .

对当前行和上一行分别开一个权值树状数组就可随手维护,注意 bi,jBi1(1,j) 时要减一下,后面还得加回去 .

时间复杂度 O(n2logn),注意原题是 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 @   yspm  阅读(95)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
😅​
点击右上角即可分享
微信分享提示