正睿 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\) 个不合法位置。则有:
现在考虑怎样求 \(G(i)\)。我们可以先将序列划分成若干公差为 \(k\) 的等差序列,这样每个等差序列都是独立的。然后我们对每个序列分段,钦定每个段内全部都不合法,设 \(g(i,j)\) 表示将长为 \(i\) 的等差序列划分为 \(j\) 段的方案数,\(f(i,j)\) 表示将前 \(i\) 个序列总共划分出 \(j\) 段的方案数,那么有:
其中 \(l_i\) 表示第 \(i\) 个等差序列长度。这个的复杂度是 \(O(\sum l_i\times n)=O(n^2)\) 的。然后考虑怎样求 \(g\),这个很简单,不过要注意的是我们分完段后每个段内是可以反转的,这样不影响段内全部不合法这个条件。所以转移方程为:
这个东西直接做是 \(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;
}

浙公网安备 33010602011771号