2024/09/26 模拟赛总结
rk4,\(0+30+40+30=100\),T1挂惨了
分类讨论,由于 \(a_i\ge0\),当 \(x < 0\) 时,可以直接升序排列
当 \(x > 0\) 时,大部分情况下可以降序排列,但可能会出现 \(a_1=x\) 的情况,就可以找到第一个不为 \(x\) 且不为 \(0\) 的数,swap 掉即可
然后是最麻烦的 \(x=0\),当出现最多的数出现次数大于 \(\lceil\frac{n}{2}\rceil\),一定无解,如果等于,还要分类。如果这个最多的数不是 \(0\),就直接和其他数插空放,如果是 \(0\),当 \(n\) 是偶数时,可以将所有偶数位放 \(0\),其他位插空放,如果时奇数,则无解
否则,由于最大出现次数小于 \(\lceil\frac{n}{2}\rceil\),排序后直接按顺序先放奇数位再放偶数位即可,此时如果第一位等于 \(x\),就把数组 reverse
掉
// BLuemoon_
#include <bits/stdc++.h>
using namespace std;
const int kMaxN = 1e5 + 5;
int t, n, a[kMaxN], c[kMaxN], x, mx = -1, k;
map<int, int> mp;
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
freopen("difference.in", "r", stdin), freopen("difference.out", "w", stdout);
for (cin >> t; t; t--, mp.clear(), mx = -1) {
cin >> n >> x;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
if (n == 1) {
cout << (x == a[1] ? "no" : "yes") << '\n';
(x != a[1]) && (cout << a[1] << '\n');
continue;
}
if (x < 0) {
sort(a + 1, a + n + 1);
} else if (x > 0) {
sort(a + 1, a + n + 1, greater<int>());
bool flag = 1;
if (a[1] == x) {
for (int i = 2; i <= n; i++) {
if (a[i] != a[1]) {
(a[i] == 0) ? (flag = 1) : (swap(a[1], a[i]), flag = 0);
break;
}
}
if (flag) {
cout << "no\n";
continue;
}
}
} else {
int e = (n / 2) + (n % 2), v;
for (int i = 1; i <= n; i++) {
mp[a[i]]++, (mx < mp[a[i]]) && (mx = mp[a[i]], v = a[i]);
}
if (mx > e) {
cout << "no\n";
continue;
}
if (mx == e) {
if (v == x) {
if (n & 1) {
cout << "no\n";
continue;
}
for (int i = 2; i <= n; i += 2) {
c[i] = v;
}
for (int i = 1, j = 1; i <= n; i += 2, j++) {
for (; a[j] == v; j++) {
}
c[i] = a[j];
}
cout << "yes\n";
for (int i = 1; i <= n; i++) {
cout << c[i] << ' ';
}
cout << '\n';
continue;
}
for (int i = 1; i <= n; i += 2) {
c[i] = v;
}
for (int i = 2, j = 1; i <= n; i += 2, j++) {
for (; a[j] == v; j++) {
}
c[i] = a[j];
}
} else {
sort(a + 1, a + n + 1), k = 1;
if (a[1] == x) {
reverse(a + 1, a + n + 1);
}
for (int i = 1; i <= n; i += 2, k++) {
c[i] = a[k];
}
for (int i = 2; i <= n; i += 2, k++) {
c[i] = a[k];
}
}
for (int i = 1; i <= n; i++) {
a[i] = c[i];
}
}
cout << "yes\n";
for (int i = 1; i <= n; i++) {
cout << a[i] << ' ';
}
cout << '\n';
}
return 0;
}
比较典的拆贡献题,显然对于总长度和总最大值可以分开解决,总长度可以使用换根 dp,而最大值部分稍麻烦一些
对于每一个点,定义其“影响域”为,一个最大的包含此点的连通块,其内任意路径过此点的两点之间的 \(max_{val}\) 为这个点的权值
然后对于“影响域”内的每一个点,它到“影响域”内其他点路径经过关键点的个数乘上关键点的权值。即令关键点为 \(x\),\(y\) 为“影响域”内与 \(x\) 相连的点,那么 \(x\) 的贡献为 \(\sum (size_{x}-sz_{y})\times sz_{y}\),其中 \(size\) 为“影响域”大小,\(sz\) 为“影响域”内的子树大小
按权值升序排序后计算贡献即可
// BLuemoon_
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const LL kP = 1e9 + 7;
const int kMaxN = 1e6 + 5;
struct P {
LL id, x;
bool operator<(const P& _) const {
return x < _.x;
}
};
int n;
P a[kMaxN];
LL ans, sz[kMaxN], fa[kMaxN];
bool vis[kMaxN];
vector<int> g[kMaxN];
int F(int x) {
return (fa[x] == x) ? x : (fa[x] = F(fa[x]));
}
void DFS(int u, int fa) {
sz[u] = 1;
for (int v : g[u]) {
if (v != fa) {
DFS(v, u), sz[u] += sz[v], ans -= sz[v] * (n - sz[v]) % kP, (ans += kP) %= kP;
}
}
}
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
freopen("tour.in", "r", stdin), freopen("tour.out", "w", stdout);
cin >> n;
for (int i = 1, x; i <= n; i++) {
cin >> x, a[i] = (P){i, x}, fa[i] = i;
}
for (int i = 1, u, v; i < n; i++) {
cin >> u >> v, g[u].push_back(v), g[v].push_back(u);
}
DFS(1, 0), sort(a + 1, a + n + 1);
fill(sz + 1, sz + n + 1, 1);
for (int u = 1; u <= n; u++) {
for (int v : g[a[u].id]) {
if (vis[v]) {
(ans += (a[u].x * sz[F(a[u].id)] % kP) * sz[F(v)] % kP) %= kP;
int X = F(a[u].id), Y = F(v);
(X != Y) && (fa[X] = Y, sz[Y] += sz[X]);
}
}
vis[a[u].id] = 1;
}
cout << (ans << 1) % kP << '\n';
return 0;
}
std 写什么【】矩阵乘法啊,还是我们 csr 有实力
令 \(dp_{i,j}\) 为已经填了 \(i\sim n\) 位,第 \(i\) 位填了 \(j\) 的最大值,直接枚举转移即可
然后由于 \(k\) 只有 \(1000\),我们可以 DFS 暴力枚举所有填数序列,当当前值已经大于 \(k\) 时直接剪枝掉,就可以愉快的卡过去了
// BLuemoon_
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int kMaxN = 1e3 + 5;
int n, x, k, a[5][5];
LL dp[kMaxN][5];
map<char, int> mp;
string s, ans, tmp = "NOIP", O = "INOP";
void DFS(int t, LL e) {
if (t > n) {
if (e >= x) {
if (!--k) {
cout << ans.substr(1) << '\n', exit(0);
}
}
return;
}
if (s[t] == '?') {
for (int i = 3; ~i; i--) {
LL f = dp[t][i];
(t > 1) && (f += a[mp[ans[t - 1]]][i]);
if (f + e >= x) {
LL cur = e;
(t > 1) && (cur += a[mp[ans[t - 1]]][i]);
ans[t] = O[i], DFS(t + 1, cur);
}
}
return;
}
int i = mp[s[t]];
ans[t] = s[t];
LL f = dp[t][i];
(t > 1) && (f += a[mp[ans[t - 1]]][i]);
if (f + e >= x) {
LL cur = e;
(t > 1) && (cur += a[mp[ans[t - 1]]][i]);
ans[t] = O[i], DFS(t + 1, cur);
}
}
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0), mp['I'] = 0, mp['N'] = 1, mp['O'] = 2, mp['P'] = 3;
freopen("permutation.in", "r", stdin), freopen("permutation.out", "w", stdout);
cin >> n >> x >> k >> s, s = ' ' + s;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
cin >> a[mp[tmp[i]]][mp[tmp[j]]];
}
}
fill(dp[0], dp[kMaxN - 1], -1e18);
(s[n] == '?') ? (dp[n][0] = dp[n][1] = dp[n][2] = dp[n][3] = 0) : (dp[n][mp[s[n]]] = 0);
for (int i = n - 1; i; i--) {
if (s[i] == '?') {
for (int j = 0; j < 4; j++) {
for (int k = 0; k < 4; k++) {
dp[i][j] = max(dp[i][j], dp[i + 1][k] + a[j][k]);
}
}
} else {
for (int j = 0; j < 4; j++) {
dp[i][mp[s[i]]] = max(dp[i][mp[s[i]]], dp[i + 1][j] + a[mp[s[i]]][j]);
}
}
}
ans.resize(n + 1), DFS(1, 0);
cout << "-1\n";
return 0;
}
std 写什么【】后缀数组啊,还是我们 csr 有实力
直接大力 SAM,由于两个相同的子串可以唯一确定一个子串的一个 border。所以令每种字符串长度为 \(len\),出现次数为 \(p\),那么它的贡献为 \(len \times \mathrm{C}_{p}^{2}\)
建出 \(link\) 树后进行树形 dp 算式子即可
// BLuemoon_
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int kMaxN = 1e5 + 5, kMaxC = 26 + 5;
LL n, ans;
struct SAM {
struct P {
LL lnk, maxl, to[kMaxC], sz;
vector<int> g;
};
int tot, lst;
P s[kMaxN << 1];
SAM() {
lst = tot = 0, s[0].maxl = 0, s[0].lnk = -1;
}
void Insert(int ch, int c, int p = 0) {
for (s[c].maxl = s[p = lst].maxl + 1, s[c].sz = 1; (p != -1) && (!s[p].to[ch]); s[p].to[ch] = c, p = s[p].lnk) {
}
if (p == -1) {
return s[lst = c].lnk = 0, void();
}
int ed = s[p].to[ch];
if (s[p].maxl == s[ed].maxl + 1) {
return s[lst = c].lnk = ed, void();
}
for (s[++tot] = s[ed], s[tot].sz = 0, s[tot].maxl = s[p].maxl + 1; (~p) && (s[p].to[ch] == ed); s[p].to[ch] = tot, p = s[p].lnk) {
}
s[ed].lnk = s[lst = c].lnk = tot;
}
void Update(string str, int sz) {
for (int i = 0; i < sz; i++) {
Insert(str[i] - 'a' + 1, ++tot);
}
}
void Link() {
for (int i = 0; i <= tot; i++) {
(~s[i].lnk) && (s[s[i].lnk].g.push_back(i), 1);
}
}
void DFS(int u, int fa) {
for (int v : s[u].g) {
DFS(v, u), s[u].sz += s[v].sz;
}
ans += (s[u].maxl + s[fa].maxl + 1) * (s[u].maxl - s[fa].maxl) * s[u].sz * (s[u].sz - 1) / 4;
}
};
SAM sam;
string s;
int main() {
freopen("border.in", "r", stdin), freopen("border.out", "w", stdout);
cin >> n >> s, sam.Update(s, n), sam.Link(), sam.DFS(0, 0);
cout << ans << '\n';
return 0;
}