正睿 25 年联赛联合训练 Day 11

正睿 25 年联赛联合训练 Day 11

得分

T1 T2 T3 得分 排名
\(100\) \(100\) \(20\) \(220\) \(8/17\)

题解

T1 计数题

考虑对 \(S\) 建立 SAM,由于我们选出的子串不能有后缀关系,所以我们不能选 parent 树上有祖孙关系的节点。于是我们选 parent 树的叶子即可。

#include <bits/stdc++.h>

using namespace std;

const int Maxn = 2e6 + 5;
const int Inf = 2e9;

int n;
string s;

struct SAM {
	int len, link, son[26];
}sam[Maxn];

int deg[Maxn];
int lst = 0, tot = 0;
void insert(int i) {
	sam[++tot].len = sam[lst].len + 1;
	int pos = lst, ch = s[i] - 'a';
	lst = tot;
	while(pos != -1 && sam[pos].son[ch] == 0) {
		sam[pos].son[ch] = tot;
		pos = sam[pos].link;
	}
	if(pos == -1) sam[tot].link = 0;
	else{
		int p = pos, q = sam[pos].son[ch];
		if(sam[p].len + 1 == sam[q].len) {
			sam[tot].link = q;
		}
		else {
			sam[++tot] = sam[q];
			sam[tot].len = sam[p].len + 1;
			sam[q].link = sam[tot - 1].link = tot;
			while(pos != -1 && sam[pos].son[ch] == q) {
				sam[pos].son[ch] = tot;
				pos = sam[pos].link;
			}
		}
	} 
	deg[sam[lst].link]++;
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> s; 
	n = s.size(); s = ' ' + s;
	sam[0].link = -1;
	for(int i = 1; i <= n; i++) {
		insert(i);
	}
	int ans = 0;
	for(int i = 0; i <= tot; i++) {
		if(!deg[i]) ans++;
	}
	cout << ans << '\n';
	return 0;
}

T2 字符串题

考虑先随便造几个数据,然后发现有的数据有很强的规律,比如对于 \(9\) 有构造:

5
1 5 3
2 3 4
3 1 5
4 4 1
5 2 2

发现它可以分成上下两半,前三行和后两行。第一列一定按顺序放,第二列在前一半递减放奇数、后一半递减放偶数,第三列直接用前两列算即可。发现这样可以保证前两列的和均不同,那么构造就是正确的。

然后我们需要算出答案的上界,设其为 \(k\),则不难发现有 \(kn\ge 3(1+2+\cdots +k)\),于是 \(k\le \lfloor\tfrac {2n}3\rfloor-1\),取该上界构造答案即可。注意要分奇偶讨论一下。

#include <bits/stdc++.h>

using namespace std;

const int Maxn = 5e5 + 5;
const int Inf = 2e9;

int n;
int x[Maxn], y[Maxn], z[Maxn];

void solve1(int m) {
	for(int i = 1; i <= m; i++) x[i] = i;
	y[m / 2 + 1] = 1; y[m / 2 + 2] = m - 1;
	for(int i = m / 2; i >= 1; i--) y[i] = y[i + 1] + 2;
	for(int i = m / 2 + 3; i <= m; i++) y[i] = y[i - 1] - 2;
	cout << m << '\n';
	for(int i = 1; i <= m; i++) {
		cout << x[i] << " " << y[i] << " " << n - x[i] - y[i] << '\n';
	}
}

void solve2(int m) {
	for(int i = 1; i <= m; i++) x[i] = i;
	y[m / 2] = 1; y[m] = 2;
	for(int i = m / 2 - 1; i >= 1; i--) y[i] = y[i + 1] + 2;
	for(int i = m; i >= m / 2 + 1; i--) y[i] = y[i + 1] + 2;
	cout << m << '\n';
	for(int i = 1; i <= m; i++) {
		cout << x[i] << " " << y[i] << " " << n - x[i] - y[i] << '\n';
	}
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n;
	if(n <= 2) {
		cout << "0\n";
		return 0;
	}
	int m = (2 * n) / 3 - 1;
	if(m & 1) solve1(m);
	else solve2(m);
	return 0;
}

T3 构造题

首先考虑二项式反演,设 \(F(i)\) 表示序列恰出现 \(i\) 个不合法位置,\(G(i)\) 表示序列至少出现 \(i\) 个不合法位置。则有:

\[F(0)=\sum_{i=0}^n (-1)^i G(i) \]

现在考虑怎样求 \(G(i)\)。我们可以先将序列划分成若干公差为 \(k\) 的等差序列,这样每个等差序列都是独立的。然后我们对每个序列分段,钦定每个段内全部都不合法,设 \(g(i,j)\) 表示将长为 \(i\) 的等差序列划分为 \(j\) 段的方案数,\(f(i,j)\) 表示将前 \(i\) 个序列总共划分出 \(j\) 段的方案数,那么有:

\[f(i,j)=\sum_{x=1}^{l_i} f(i-1,j-x)\times g(l_i,x) \]

其中 \(l_i\) 表示第 \(i\) 个等差序列长度。这个的复杂度是 \(O(\sum l_i\times n)=O(n^2)\) 的。然后考虑怎样求 \(g\),这个很简单,不过要注意的是我们分完段后每个段内是可以反转的,这样不影响段内全部不合法这个条件。所以转移方程为:

\[g(i,j)=\sum_{x=1}^i g(i-x,j-1)\times (1+[x>1]) \]

这个东西直接做是 \(O(n^3)\) 的,不过可以简单利用前缀和优化至 \(O(n^2)\)

最后通过不合法位置数量我们实际上可以算出它有多少个段,由于段之间还可以互相交换,所以再乘上段数的阶乘即可。也就是 \(G(i)=f(m,n-i)\times (n-i)!\),最后用二项式反演计算答案即可。复杂度 \(O(n^2)\)

#include <bits/stdc++.h>
#define int long long

using namespace std;

const int Maxn = 5e3 + 5;
const int Inf = 2e9;
const int Mod = 998244353;

int n, m, k, a[Maxn];
unordered_map <int, int> ps;
int bel[Maxn];
vector <int> grp[Maxn];

int fac[Maxn], inv[Maxn];
int qpow(int a, int b) {
	int res = 1;
	while(b) {
		if(b & 1) res = res * a % Mod;
		a = a * a % Mod; b >>= 1;
	}
	return res;
}

void init() {
	fac[0] = 1;
	for(int i = 1; i <= n; i++) fac[i] = fac[i - 1] * i % Mod;
	inv[n] = qpow(fac[n], Mod - 2);
	for(int i = n - 1; i >= 0; i--) inv[i] = inv[i + 1] * (i + 1) % Mod;
}

int C(int n, int m) {
	if(n < m) return 0;
	return fac[n] * inv[m] % Mod * inv[n - m] % Mod;
}

int f[Maxn][Maxn], dp[Maxn][Maxn], sum[Maxn][Maxn];

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n >> k;
	init();
	for(int i = 1; i <= n; i++) {
		cin >> a[i];
		if(ps.find(a[i] - k) != ps.end()) bel[i] = bel[ps[a[i] - k]];
		else bel[i] = ++m;
		grp[bel[i]].push_back(a[i]);
		ps[a[i]] = i;
	}
	f[0][0] = 1;
	for(int i = 0; i <= n; i++) sum[i][0] = 1;
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= i; j++) {
			f[i][j] = (f[i][j] + sum[i - 1][j - 1]) % Mod;
			if(i > 1) f[i][j] = (f[i][j] + sum[i - 2][j - 1]) % Mod;
			sum[i][j] = (sum[i - 1][j] + f[i][j]) % Mod;
		}
	}
	dp[0][0] = 1;
	for(int i = 1; i <= m; i++) {
		int l = grp[i].size();
		for(int j = 1; j <= n; j++) {
			for(int x = 1; x <= min(l, j); x++) {
				dp[i][j] = (dp[i][j] + dp[i - 1][j - x] * f[l][x] % Mod) % Mod;
			}
		}
	}
	int ans = 0;
	for(int i = 1; i <= n; i++) {
		int res = dp[m][i] * fac[i] % Mod;
		int num = n - i;
		if(num & 1) ans = (ans - res + Mod) % Mod;
		else ans = (ans + res) % Mod;
	}
	cout << ans << '\n';
	return 0;
}
posted @ 2025-03-07 15:33  UKE_Automation  阅读(41)  评论(0)    收藏  举报