T1. 排水系统
题目链接:Link。
-
将每个 " 排水节点 " 看成是一个 " 点 "。
将每个 " 单向排水管道 " 看成是一条 " 单向边 "。
不难发现,得到的图是一张 DAG。 -
直接模拟题意即可。
依次松弛每个节点的蓄水量,直至到达最终排水口。 -
需要注意的是,在松弛任意一个节点 (v) 的蓄水量时,需要保证:
- 对于图中的每一条有向边 ((u, v)),(u) 的蓄水量都被松弛过了。
-
发现可以通过拓扑序来转移。
-
要打高精。
我比较懒,用的__int128
,大家还是好好打高精吧(:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
#define u128 __int128
using namespace std;
inline void print(u128 x) {
if (x > 9) print(x / 10);
putchar('0' + x % 10);
}
u128 gcd(u128 a, u128 b) {
if (!b) return a;
return gcd(b, a % b);
}
const int N = 400100;
struct Node {
u128 x, y;
} a[N];
Node operator + (Node a, Node b) {
Node c;
c.y = a.y * b.y;
c.x = a.x * b.y + b.x * a.y;
u128 S = gcd(c.x, c.y);
if (!S) {
c.x = 0;
c.y = 1;
} else {
c.x /= S;
c.y /= S;
}
return c;
}
Node operator / (Node a, int num) {
Node b;
b.x = a.x;
b.y = 1ll * a.y * num;
u128 S = gcd(b.x, b.y);
if (!S) {
b.x = 0;
b.y = 1;
} else {
b.x /= S;
b.y /= S;
}
return b;
}
int n, m;
vector<int> to[N];
int deg[N];
void topsort() {
queue<int> q;
for (int i = 1; i <= n; i ++)
if (deg[i] == 0) q.push(i);
while (q.size()) {
int u = q.front(); q.pop();
if (to[u].size() == 0) continue;
Node give = a[u] / to[u].size();
for (int i = 0; i < (int)to[u].size(); i ++) {
int v = to[u][i];
a[v] = a[v] + give;
if (-- deg[v] == 0) q.push(v);
}
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1, S; i <= n; i ++) {
scanf("%d", &S);
while (S --) {
int x;
scanf("%d", &x);
to[i].push_back(x);
}
}
for (int i = 1; i <= m; i ++)
a[i].x = 1, a[i].y = 1;
for (int i = m + 1; i <= n; i ++)
a[i].x = 0, a[i].y = 1;
for (int i = 1; i <= n; i ++)
for (int j = 0; j < (int)to[i].size(); j ++) {
int v = to[i][j];
deg[v] ++;
}
topsort();
for (int i = 1; i <= n; i ++)
if (to[i].size() == 0) {
print(a[i].x);
printf(" ");
print(a[i].y);
puts("");
}
return 0;
}
// I hope changle_cyx can pray for me.
T2. 字符串匹配
题目链接:Link。
算法一
特殊性质:(n leq 2^{17})
-
一个较为简单的做法,基本不用怎么思考。
-
注意到答案是要求将字符串划分成 (S = (AB)^iC) 的形式。
-
记 (F(S)) 的表示字符串 (S) 中出现奇数次的字符的数量。
需要先预处理出:- 每一个前缀 (S_{1 .. i}) 出现奇数次的字符的数量,即 (F(S_{1..i}))。
- 每一个后缀 (S_{i .. n}) 出现奇数次的字符的数量,即 (F(S_{i .. n}))。
-
考虑枚举 (T = (AB)),那相当于是枚举一个前缀。
在此基础上,再从小到大枚举一个 (i),使用 hash 判断子串是否完全相等。 -
此时整个字符串的划分结构就已经是确定的了。
(F(C)) 已经预处理好了,那这种情况对答案的贡献,相当于要在 (T) 里数出有多少个 (A) 满足 (F(A) leq F(C))。 -
注意到每一个 (A) 在 (T) 中也是前缀,那直接用树状数组动态维护一下即可。
-
时间复杂度 (mathcal{O}(n ln n + n log |sum|))。
其中 (sum) 表示字符集。 -
期望得分 (84 sim 100)。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2000000;
const unsigned long long P = 13331;
int n;
char S[N];
// suf & pre
bool exist[30];
int num;
int suf[N];
int pre[N];
int c[N][30];
// hash
unsigned long long power[N];
unsigned long long hash[N];
unsigned long long H(int l, int r) {
return hash[r] - hash[l - 1] * power[r - l + 1];
}
void work() {
scanf("%s", S + 1);
n = strlen(S + 1);
// suf
for (int i = 0; i < 26; i ++)
exist[i] = 0;
num = 0;
for (int i = n; i >= 1; i --) {
int ch = S[i] - 'a';
exist[ch] ^= 1;
if (exist[ch]) num ++;
else num --;
suf[i] = num;
}
// pre
for (int i = 0; i < 26; i ++)
exist[i] = 0;
num = 0;
for (int i = 1; i <= n; i ++) {
int ch = S[i] - 'a';
exist[ch] ^= 1;
if (exist[ch]) num ++;
else num --;
pre[i] = num;
}
for (int i = 1; i <= n; i ++) {
for (int j = 0; j <= 26; j ++)
c[i][j] = c[i - 1][j];
for (int j = pre[i]; j <= 26; j ++)
c[i][j] ++;
}
// hash
for (int i = 1; i <= n; i ++)
hash[i] = hash[i - 1] * P + (S[i] - 'a');
// work
long long ans = 0;
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= n / i; j ++) {
int l = (j - 1) * i + 1, r = j * i;
if (H(1, i) != H(l, r)) break;
if (r + 1 > n) break;
ans += c[i - 1][suf[r + 1]];
}
}
printf("%lld
", ans);
}
int main() {
power[0] = 1;
for (int i = 1; i <= 1500000; i ++) power[i] = power[i - 1] * P;
int T; scanf("%d", &T);
while (T --) work();
return 0;
}
// I hope changle_cyx can pray for me.
算法二
特殊性质:(n leq 2^{20})。
- Z 函数高论,可以做到 (mathcal{O}(n))。
- 题解在路上了。
T3. 移球游戏
题目链接:Link。
算法一
特殊性质:(n leq 50),(m leq 300)。
- 可以强行乱搞一下。
- 记 " 将 (x) 号柱子上的球移动到 (y) 号柱子上 " 的操作为 ( ext{move}(x, y))。
- 可以一个颜色一个颜色来考虑。
假设说现在要考虑第 (n) 个颜色:
-
枚举 (i = 1 o (n - 1))。
将 (i) 号柱子里所有颜色为 (n) 的球都移动到 (i) 号柱子的最顶端。
记 (i) 号柱子共有 (c_i) 个颜色为 (n) 的球。- 将 (n) 号柱子移出 (c) 个空位。
即进行 (c_i) 次 ( ext{move}(n, n + 1))。 - 依次考虑 (i) 号柱子里的每一个球。
若该球的颜色为 (n),则进行一次 ( ext{move}(i, n))。
若该球的颜色不为 (n),则进行一次 ( ext{move}(i, n + 1))。 - 将 (n + 1) 号柱子上方的 (m - c_i) 个球移回 (i) 号柱子。
即进行 (m - c_i) 次 ( ext{move}(n + 1, i))。 - 将 (n) 号柱子上方的 (c) 个球移回 (i) 号柱子。
即进行 (c_i) 次 ( ext{move}(n, i))。 - 将 (n + 1) 号柱子上方的 (c) 个球移回 (n) 号柱子。
即进行 (c_i) 次 ( ext{move}(n + 1, n))。
- 将 (n) 号柱子移出 (c) 个空位。
-
枚举 (i = 1 o (n - 1))。
将 (i) 号柱子最顶端所有颜色为 (n) 的球都移动到 (n + 1) 号柱子上。 -
依次考虑 (n) 号柱子里的每一个球。
若该球的颜色为 (n),则进行一次 ( ext{move}(n, n + 1))。
若该球的颜色不为 (n),则将该球补到 (1) 至 (n - 1) 号柱子里的一个空位上。
- 这样的话就得到了一个规模为 (n - 1) 的子问题,直接递归调用到 (1) 即可。
- 复杂度是 (mathcal{O}(n^2m)) 的。
来计算一下该算法的严格操作数。 - 记 (g(n)) 表示解决一个规模为 (n) 的问题,且不向下递归调用时需要的操作数,则:
- 在最坏情况下,(sumlimits_{i = 1}^{n - 1} c_i = m),则:
- 此时:
- 发现刚好可以过掉 (70) 分。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
inline int read() {
int x = 0, f = 1; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -f; s = getchar(); }
while (s >= '0' && s <= '9') { x = x * 10 + s - '0'; s = getchar(); }
return x * f;
}
const int N = 60, M = 450;
int n, m;
int top[N], a[N][M];
int cnt[N][M];
int t;
pair<int, int> ans[820001];
void move(int x, int y) {
ans[++ t] = make_pair(x, y);
int u = a[x][top[x]];
cnt[x][u] --;
cnt[y][u] ++;
top[x] --;
a[y][++ top[y]] = u;
}
void solve(int u) {
if (u == 1)
return;
for (int i = 1; i < u; i ++) {
int c = cnt[i][u];
for (int j = c; j; j --)
move(u, u + 1);
for (int j = m; j; j --)
if (a[i][j] == u) move(i, u);
else move(i, u + 1);
for (int j = m - c; j; j --)
move(u + 1, i);
for (int j = c; j; j --)
move(u, i);
for (int j = c; j; j --)
move(u + 1, u);
}
for (int i = 1; i < u; i ++)
while (a[i][top[i]] == u)
move(i, u + 1);
int p = 1;
for (int j = top[u]; j; j --) {
if (a[u][j] == u) move(u, u + 1);
else {
while (top[p] >= m) p ++;
move(u, p);
}
}
solve(u - 1);
}
int main() {
n = read(), m = read();
for (int i = 1; i <= n; i ++) {
top[i] = m;
for (int j = 1; j <= m; j ++)
a[i][j] = read(), cnt[i][a[i][j]] ++;
}
solve(n);
printf("%d
", t);
for (int i = 1; i <= t; i ++)
printf("%d %d
", ans[i].first, ans[i].second);
return 0;
}
// I hope changle_cyx can pray for me.
算法二
特殊性质:(n leq 50),(m leq 400)。
- 分治,可以做到 (mathcal{O}(nm log n))。
- 题解在路上了。
T4. 微信步数
题目链接:Link。
- 题解在路上了。