MX galaxy Day8
swap
同一个位置在每一轮操作中经过的位置是不变的,结束时所在的位置也是确定的。
模拟这个过程,将每个过程经过的位置记作 \(S\) ,将每个位置的 \(S\) 与结束所在位置的 \(S\) 合并。
构成了若干个等价类,每个等价类内答案即为 \(S\) 的元素个数。
每次操作会让 \(\sum |S|\) 增加 \(O(1)\) ,使用启发式合并做到总复杂度为 \(O((n + m) \log(n + m))\) 。
点击查看
#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)
const int _ = 2e5 + 7;
typedef long long ll;
int n, k, a, b, p[_], fa[_];
std::vector <int> S[_];
int fnd(int x) { return fa[x] == x ? x : fa[x] = fnd(fa[x]); }
void Mrg(int x, int y) {
x = fnd(x), y = fnd(y);
if (x == y) return;
if (S[x].size() > S[y].size()) std::swap(x, y);
for (int v : S[x]) S[y].push_back(v); S[x].clear();
fa[x] = y;
}
int main() {
scanf("%d%d", & n, & k);
lep(i, 1, n) p[i] = fa[i] = i, S[i].push_back(i);
while (k--) {
scanf("%d%d", & a, & b);
std::swap(p[a], p[b]);
S[p[a]].push_back(a), S[p[b]].push_back(b);
}
lep(i, 1, n) Mrg(i, p[i]);
lep(i, 1, n) if (fnd(i) == i) {
std::sort(S[i].begin(), S[i].end());
S[i].erase(std::unique(S[i].begin(), S[i].end()), S[i].end());
}
lep(i, 1, n) printf("%d\n", (int)S[fa[i]].size());
return 0;
}
path
\(O(n^2)\) 条边的最短路无法接受。
考虑优化建图,发现代价是类似于按位异或 \(\oplus\) 的位运算相关,拆位考虑。
定义状态 \((u, i, j)\) 为当前数字为 \(u\) ,考虑了前 \(i\) 位是否改变,已经改变了 \(j\) 位。
连边就将 \((u, i, j)\) 连向 \((u, i + 1, j)\) 和 \((u \oplus 2^i, i + 1, j + 1)\) 。
边权全部为 \(0\) ,然后将 \((u, k, i)\) 向 \((u, 0, 0)\) 连边权为 \(a_i\) 的边。
边数与状态数同阶,空间复杂度为 \(O(2^k k^2)\) 。
但是 Dijstra 算法求解最短路会让时间复杂度变成 \(O(2^kk^3)\) ,并不是很稳妥。
发现存在大量边权为 \(0\) 的边,所以我们将中转状态全部使用 \(bfs\) 处理,只有非中转状态才会加入 priority_queue 。
时间复杂度也是 \(O(2^kk^2)\) 。
点击查看
#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)
const int _ = 2e5 + 7;
typedef long long ll;
const ll inf = 1e16;
struct node { int v, i, j; };
int n, k, m, s, a[20]; ll dis[_]; bool vis[_], rc[_][18][18];
std::vector <int> e[_], g[_];
std::priority_queue <std::pair<ll, int> > q;
inline void Add(int u, int v, int w) { e[u].push_back(v), g[u].push_back(w); }
void Bfs(int s) { std::queue <node> d;
d.push(node{s, 0, 0}), rc[s][0][0] = true;
while (!d.empty()) {
int v = d.front().v, i = d.front().i, j = d.front().j; d.pop();
if (i == k) {
if (dis[v] > dis[s] + a[j]) { dis[v] = dis[s] + a[j];
if (!vis[v]) q.push({-dis[v], v});
}
}
else {
if (!rc[v][i + 1][j]) {
rc[v][i + 1][j] = true;
d.push(node{v, i + 1, j});
}
if (!rc[v ^ (1 << i)][i + 1][j + 1]) {
rc[v ^ (1 << i)][i + 1][j + 1] = true;
d.push(node{v ^ (1 << i), i + 1, j + 1});
}
}
}
}
int main() {
scanf("%d%d%d", & k, & m, & s), n = 1 << k;
lep(i, 1, k) scanf("%d", a + i); int u, v, w;
lep(i, 1, m) scanf("%d%d%d", & u, & v, & w), Add(u, v, w), Add(v, u, w);
lep(i, 0, n - 1) dis[i] = inf;
dis[s] = 0, q.push({0, s});
while (!q.empty()) {
int u = q.top().second; q.pop();
if (vis[u]) continue; vis[u] = true;
lep(k, 0, (int)e[u].size() - 1) { int v = e[u][k], w = g[u][k];
if (dis[v] > dis[u] + w) { dis[v] = dis[u] + w;
if (!vis[v]) q.push({-dis[v], v});
}
}
Bfs(u);
}
lep(i, 0, n - 1) printf("%lld ", dis[i]);
return 0;
}
string
根据每个字符的出现次数分成若干等价类,等价类之间贡献一定为 \(6666\) 。
下面考虑等价类内部的贡献,发现只可能是 \(0\), \(1\) , \(2\) 三种情况。
相等时为 \(0\) ,去掉 \(LCP\) 和 \(LCS\) 后剩下的部分其中一个串是升序的时为 \(1\) ,否则将两个串之间重排,贡献为 \(2\) 。
发现 \(1\) 是最特殊的,考虑如何算 \(1\) 。
先将所有串按字典序递减排序,依次扫描。
记 \(h_i = LCP(s_i, s_{i+1})\) ,则 \(LCP(i, j)\) 就变成区间 \([i, j)\) \(h\) 的最小值,用单调栈维护。
我们依次扫描每个极长上升子段,规定它为上述 \(1\) 中那个升序的部分,再通过二分栈内找到前缀合法的左端点。
接下来我们要询问一段区间内与 \(s_i\) 的 \(LCS \ge x\) 的串的个数。
将询问差分离线,扫描线,将扫到的串反向插入 \(Tire\) 树,查询时在 \(Tire\) 树上定位,然后返回子树内结尾节点个数。
最后将算重的相同的串的贡献除去。
复杂度 \(O(n\cdot |s|\log n)\) 。
点击查看
#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)
const int _ = 2e5 + 7;
typedef long long ll;
typedef std::pair<int, int> PII;
typedef std::string Str;
struct Tire {
int ch[_ << 5][26], idx = 0, num[_ << 5];
int New() { ++idx; lep(i, 0, 25) ch[idx][i] = 0; num[idx] = 0; return idx; }
void Ins(Str& s) { int nw = 0, len = s.size(); ++num[nw];
rep(i, len - 1, 0) { int k = s[i] - 'a';
if (!ch[nw][k]) ch[nw][k] = New();
nw = ch[nw][k], ++num[nw];
}
}
int Qry(Str& s, int pos) { int nw = 0, len = s.size();
rep(i, len - 1, pos) {
nw = ch[nw][s[i] - 'a'];
if (!nw) return 0;
}
return num[nw];
}
void Cls() { idx = -1; New(); }
}T;
int n, sz, stk[_], len, top, h[_]; Str s, t; ll ans;
std::map< Str, std::vector<Str> >grp;
std::map< Str, int > unq;
std::vector <PII> o[_];
void Solve(std::vector<Str>& g) { int q = g.size();
lep(i, 0, q - 1) {
int pos = 0;
if (i) {
lep(j, 0, len - 1) {
if (j == len - 1 or g[i][j] > g[i][j + 1]) {
int nw = top;
while (nw and h[stk[nw]] >= pos) --nw;
if (h[stk[nw]] < pos) o[stk[nw]].push_back({ -i, j + 1 });
o[i - 1].push_back({ i, j + 1 });
pos = j + 1;
}
}
}
if (i == q - 1) continue;
h[i] = 0;
while (h[i] < len and g[i][h[i]] == g[i + 1][h[i]]) ++h[i];
while (top and h[i] <= h[stk[top]]) --top;
stk[++top] = i;
}
lep(i, 0, q - 2) {
T.Ins(g[i]);
for (auto v : o[i]) { int j = v.first, p = v.second;
if (j > 0) ans -= T.Qry(g[j], p);
else ans += T.Qry(g[-j], p);
}
}
T.Cls(), top = 0;
lep(i, 0, q - 1) o[i].clear();
}
int main() {
std::ios::sync_with_stdio(false),
std::cin.tie(nullptr), std::cout.tie(nullptr);
std::cin >> n;
lep(i, 1, n) {
std::cin >> s; t = s;
std::sort(t.begin(), t.end());
grp[t].push_back(s), ++unq[s];
}
len = s.size();
for (auto u : grp) { auto g = u.second;
ans += 1ll * sz * g.size() * 1337ll, sz += g.size();
std::sort(g.begin(), g.end()); int q = g.size();
ans += q * (q - 1); std::reverse(g.begin(), g.end()); Solve(g);
}
for (auto t : unq) { Str s = t.first; int tot = t.second, cnt = 0;
ans -= tot * (tot - 1);
lep(i, 0, len - 1) if (i == len - 1 or s[i] > s[i + 1]) ++cnt;
ans += 1ll * cnt * tot * (tot - 1) / 2;
}
printf("%lld\n", ans);
return 0;
}
sequence
手模发现进行 \(n - m + 1\) 次操作后会有 \(m-1\) 个元素已经固定,其余元素围绕着它循环移位。
具体的,我们定义一个大小为 \(m-1\) 的小根堆,以及 \(n-m+1\) 元素的队列。
每次排序形如,将队首加入堆,取堆顶加入队列末尾。
这样在 \(n-m+1\) 次操作后,所有元素都已考虑,所以 前 \(m-1\) 大的值一定会一直留在堆里。
我们可以在 \(O(n)\) 的复杂度内将序列还原回 \(k = n - m + 1\) 的情况。
对于 \(k < n - m + 1\) 的情况,说明后面有元素未被涉及,直接删去就好了,贡献一定为 \(1\) 。
现在只考虑 \(k=n-m+1\) 的情况,对于最后 \(m-1\) 个元素,一定是前 \(m-1\) 大且升序排列的,否则无解。
对于后面的元素,在前面出现是本质相同的,所以 \(\times (m-1)!\) 的系数。
顺次考虑前 \(n-m+1\) 个元素,如果其为前缀最大值,则堆内涉及的元素它可以随便选,否则位置就已经确定了。
定义 $d = $ 上升子序列的长度。
则 \(ans = m^d\times (m-1)!\) 。
点击查看
#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)
const int _ = 1e6 + 7;
const int mod = 998244353;
typedef long long ll;
int n, m, k, b[_], a[_], len; ll ans = 1;
int main() {
scanf("%d%d%d", & n, & m, & k);
lep(i, 0, n - 1) scanf("%d", b + i);
if (k < n - m + 1) n = k - 1 + m;
lep(i, k % n, n - 1) a[len++] = b[i];
lep(i, 0, k % n - 1) a[len++] = b[i];
len = m - 1;
rep(i, k % (n - m + 1), 1) b[len++] = a[n - i];
lep(i, m - 1, n - k % (n - m + 1) - 1) b[len++] = a[i];
lep(i, m - 1, n - 1) a[i] = b[i];
int mx = 0;
lep(i, 1, m - 1) ans = ans * i % mod;
lep(i, m - 1, n - 1) if (a[i] > mx) ans = ans * m % mod, mx = a[i];
lep(i, 0, m - 2) if (a[i] < mx or (i and a[i] < a[i - 1])) { puts("0"); return 0; }
printf("%lld\n", ans);
return 0;
}
时间仓促,如有错误欢迎指出,欢迎在评论区讨论,如对您有帮助还请点个推荐、关注支持一下

启发式合并+图论建模+Tire+计数
浙公网安备 33010602011771号