Codeforces 504E Misha and LCP on Tree
在树上进行字符串哈希即可。
本题的第 11 个测试点卡了利用自然溢出的字符串哈希。
时间复杂度 O(NLogN+MLogN)O(NLogN+MLogN)O(NLogN+MLogN) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
const int P = 1e9 + 7;
const int Q = 998244353;
const int MAXLOG = 20;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
struct ull {int a, b; };
int n, m, depth[MAXN], father[MAXN][MAXLOG];
ull bit[MAXLOG], val[MAXN][MAXLOG][2];
char s[MAXN]; vector <int> a[MAXN];
ull operator + (ull a, ull b) {return (ull) {(a.a + b.a) % P, (a.b + b.b) % Q}; }
ull operator * (ull a, ull b) {return (ull) {(1ll * a.a * b.a) % P, (1ll * a.b * b.b) % Q}; }
bool operator == (ull a, ull b) {return a.a == b.a && a.b == b.b; }
void dfs(int pos, int fa) {
depth[pos] = depth[fa] + 1;
father[pos][0] = fa;
val[pos][0][0] = (ull) {s[pos], s[pos]};
val[pos][0][1] = (ull) {s[pos], s[pos]};
for (int i = 1; i < MAXLOG; i++) {
father[pos][i] = father[father[pos][i - 1]][i - 1];
val[pos][i][0] = val[pos][i - 1][0] + bit[i - 1] * val[father[pos][i - 1]][i - 1][0];
val[pos][i][1] = bit[i - 1] * val[pos][i - 1][1] + val[father[pos][i - 1]][i - 1][1];
}
for (auto x : a[pos])
if (x != fa) dfs(x, pos);
}
int lca(int x, int y) {
if (depth[x] < depth[y]) swap(x, y);
for (int i = MAXLOG - 1; i >= 0; i--)
if (depth[father[x][i]] >= depth[y]) x = father[x][i];
if (x == y) return x;
for (int i = MAXLOG - 1; i >= 0; i--)
if (father[x][i] != father[y][i]) {
x = father[x][i];
y = father[y][i];
}
return father[x][0];
}
int up(int x, int y) {
for (int i = 0; i < MAXLOG; i++)
if (y & (1 << i)) x = father[x][i];
return x;
}
int conlcp(int a, int b, int c, int d) {
int len = depth[a] - depth[b] + 1, ans = 0;
assert(depth[d] - depth[c] + 1 == len);
static int x[MAXN]; x[0] = d;
for (int i = 0, t = 1; i < MAXLOG; i++, t <<= 1) {
x[i + 1] = x[i];
if (len & t) x[i + 1] = father[x[i]][i];
}
for (int i = MAXLOG - 1, t = 1 << i; i >= 0; i--, t >>= 1) {
if (len & t) {
if (val[a][i][0] == val[x[i]][i][1]) ans += t, a = father[a][i];
else {
int tmpx = a, tmpy = x[i];
for (int j = i - 1; j >= 0; j--)
if (val[tmpx][j][0] == val[father[tmpy][j]][j][1]) ans += 1 << j, tmpx = father[tmpx][j];
else tmpy = father[tmpy][j];
return ans;
}
}
}
return len;
}
int samelcp(int a, int b, int c, int d) {
int len = depth[b] - depth[a] + 1, ans = 0;
assert(depth[d] - depth[c] + 1 == len);
static int x[MAXN], y[MAXN]; x[0] = b, y[0] = d;
for (int i = 0, t = 1; i < MAXLOG; i++, t <<= 1) {
x[i + 1] = x[i], y[i + 1] = y[i];
if (len & t) {
x[i + 1] = father[x[i]][i];
y[i + 1] = father[y[i]][i];
}
}
for (int i = MAXLOG - 1, t = 1 << i; i >= 0; i--, t >>= 1) {
if (len & t) {
if (val[x[i]][i][0] == val[y[i]][i][0]) ans += t;
else {
int tmpx = x[i], tmpy = y[i];
for (int j = i - 1; j >= 0; j--)
if (val[father[tmpx][j]][j][0] == val[father[tmpy][j]][j][0]) ans += 1 << j;
else tmpx = father[tmpx][j], tmpy = father[tmpy][j];
return ans;
}
}
}
return len;
}
int main() {
read(n), scanf("\n%s", s + 1);
for (int i = 1; i <= n - 1; i++) {
int x, y; read(x), read(y);
a[x].push_back(y);
a[y].push_back(x);
}
bit[0] = (ull) {256, 256};
for (int i = 1; i < MAXLOG; i++)
bit[i] = bit[i - 1] * bit[i - 1];
dfs(1, 0);
read(m);
for (int x = 1; x <= m; x++) {
int xs, xt, ys, yt;
read(xs), read(xt);
read(ys), read(yt);
int xl = lca(xs, xt), yl = lca(ys, yt), ans = 0;
for (int i = MAXLOG - 1; i >= 0; i--)
if (depth[father[xs][i]] >= depth[xl] && depth[father[ys][i]] >= depth[yl] && val[xs][i][0] == val[ys][i][0]) {
ans += 1 << i;
xs = father[xs][i];
ys = father[ys][i];
}
if (xl != xs && yl != ys) {
printf("%d\n", ans);
continue;
}
if (yl != ys) swap(xl, yl), swap(xs, ys), swap(xt, yt);
int xlen = depth[xs] + depth[xt] - 2 * depth[xl] + 1;
int ylen = depth[ys] + depth[yt] - 2 * depth[yl] + 1;
if (ylen >= xlen) yt = up(yt, ylen - xlen), ylen = xlen;
else {
if (depth[xt] - depth[xl] >= xlen - ylen) xt = up(xt, xlen - ylen);
else xl = xt = up(xs, ylen - 1);
xlen = ylen;
}
if (xs != xl) {
int tmp = conlcp(xs, up(xs, depth[xs] - depth[xl] - 1), ys, up(yt, ylen - (depth[xs] - depth[xl])));
ans += tmp;
if (tmp != depth[xs] - depth[xl]) {
printf("%d\n", ans);
continue;
}
}
ans += samelcp(xl, xt, up(yt, ylen - (depth[xs] - depth[xl]) - 1 ), yt);
printf("%d\n", ans);
}
return 0;
}
Codeforces 506C Mr. Kitayuta vs. Bamboos
首先二分答案 AnsAnsAns ,此后,我们需要判断是否能将最高的竹子砍到 AnsAnsAns 以下。
则每棵竹子 iii 存在 reqireq_ireqi 表示至少要砍掉多少长度,显然,至少要砍 ⌈reqip⌉\lceil\frac{req_i}{p}\rceil⌈preqi⌉ 次。
考虑一个合法的方案,存在竹子 iii 被砍了超过 ⌈reqip⌉\lceil\frac{req_i}{p}\rceil⌈preqi⌉ 次,我们可以选择不砍最开始的几次,只砍最后的 ⌈reqip⌉\lceil\frac{req_i}{p}\rceil⌈preqi⌉ 次,同样可以完成任务,因此,可以认为一棵竹子一定会被砍 cnti=⌈reqip⌉cnt_i=\lceil\frac{req_i}{p}\rceilcnti=⌈preqi⌉ 次。
若 ∑cnti>MK\sum cnt_i>MK∑cnti>MK ,则显然无解,否则,分别考虑每一刀,在砍最后一刀时需要保证竹子已经长出了 reqireq_ireqi ,最后第二刀时需要保证竹子已经长出了 reqi−preq_i-preqi−p ,以此类推。并且,不难发现这些条件是充分必要的,因此,每一刀都需要独立地在某个时刻后被砍,可以用后缀和判断是否合法。
时间复杂度 O((N+MK)LogV)O((N+MK)LogV)O((N+MK)LogV) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, m, k, p;
ll a[MAXN], h[MAXN], req[MAXN], cnt[MAXN];
bool check(ll x) {
ll tot = 0;
for (int i = 1; i <= n; i++) {
ll final = a[i] * m + h[i];
if (final <= x) req[i] = cnt[i] = 0;
else {
req[i] = final - x;
cnt[i] = req[i] / p + (req[i] % p != 0);
}
tot += cnt[i];
}
if (tot > m * k) return false;
static int suf[MAXN];
memset(suf, 0, sizeof(suf));
for (int i = 1; i <= n; i++) {
ll tmp = req[i];
while (tmp > 0) {
if (tmp <= h[i]) suf[1]++;
else {
ll tnp = 1 + (tmp - h[i]) / a[i] + ((tmp - h[i]) % a[i] != 0);
if (tnp > m) return false;
suf[tnp]++;
}
tmp -= p;
}
}
for (int i = m; i >= 1; i--) {
suf[i] += suf[i + 1];
if (suf[i] > (m - i + 1) * k) return false;
}
return true;
}
int main() {
read(n), read(m), read(k), read(p);
for (int i = 1; i <= n; i++)
read(h[i]), read(a[i]);
ll l = 0, r = 1e15;
while (l < r) {
ll mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid + 1;
}
cout << r << endl;
return 0;
}
Codeforces 506E Mr. Kitayuta’s Gift
首先有一个简单的 O(∣S∣3+N×∣S∣2)O(|S|^3+N\times|S|^2)O(∣S∣3+N×∣S∣2) 的 dp 做法,即从结果串的两边向中间 dp 。
这个方法同样可以对于所有 M≤NM≤NM≤N 算出所有答案。
由数据范围,本题的正解很可能是矩阵乘法优化 dp ,不妨用上述 dp 计算较小范围的答案,然后尝试用 Berlekamp-Massey 算法解出答案的递推式。
经尝试,答案存在一个不超过 3∣S∣+53|S|+53∣S∣+5 阶的线性递推式。
用 Cayley-Hamilton 定理优化递推即可。
时间复杂度 O(∣S∣3+∣S∣2LogN)O(|S|^3+|S|^2LogN)O(∣S∣3+∣S∣2LogN) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 205;
const int P = 1e4 + 7;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
namespace LinearSequence {
const int MAXN = 1205;
int cnt, delta[MAXN], fail[MAXN];
int k, h[MAXN], r[MAXN];
vector <int> a[MAXN];
int power(int x, int y) {
if (y == 0) return 1;
int tmp = power(x, y / 2);
if (y % 2 == 0) return tmp * tmp % P;
else return tmp * tmp % P * x % P;
}
void times(int *a, int *b, int *res) {
static int tmp[MAXN];
memset(tmp, 0, sizeof(tmp));
for (int i = 0; i <= k - 1; i++)
for (int j = 0; j <= k - 1; j++)
update(tmp[i + j], a[i] * b[j] % P);
for (int i = 2 * k - 1; i >= k; i--) {
int val = tmp[i];
for (int j = 1; j <= k; j++)
update(tmp[i - j], val * r[j] % P);
}
for (int i = 0; i <= k - 1; i++)
res[i] = tmp[i];
}
int getans(int n) {
if (n <= k) return h[n];
static int tmp[MAXN], res[MAXN];
memset(tmp, 0, sizeof(tmp));
memset(res, 0, sizeof(res));
n -= k, res[0] = 1, tmp[1] = 1;
for (int bit = 1; n != 0; bit <<= 1) {
if (bit & n) {
n ^= bit;
times(res, tmp, res);
}
times(tmp, tmp, tmp);
}
int ans = 0;
for (int i = 0; i <= k - 1; i++)
update(ans, res[i] * h[i + k] % P);
return ans;
}
void work(int given, int *val, int n) {
a[cnt = 0].clear();
for (int i = 1; i <= given; i++) {
delta[i] = val[i];
for (unsigned j = 0; j < a[cnt].size(); j++)
update(delta[i], P - a[cnt][j] * val[i - j - 1] % P);
if (delta[i] == 0) continue;
fail[cnt] = i;
if (cnt == 0) {
a[++cnt].clear();
a[cnt].resize(i);
continue;
}
int mul = delta[i] * power(delta[fail[cnt - 1]], P - 2) % P;
a[cnt + 1].clear(), a[cnt + 1].resize(i - fail[cnt - 1] - 1);
a[cnt + 1].push_back(mul);
for (unsigned i = 0; i < a[cnt - 1].size(); i++)
a[cnt + 1].push_back((P - a[cnt - 1][i]) * mul % P);
a[cnt + 1].resize(max(a[cnt + 1].size(), a[cnt].size()));
for (unsigned i = 0; i < a[cnt].size(); i++)
update(a[cnt + 1][i], a[cnt][i]);
cnt++;
}
k = a[cnt].size();
for (int i = 1; i <= 2 * k; i++)
h[i] = val[i];
for (int i = 1; i <= k; i++)
r[i] = a[cnt][i - 1];
writeln(getans(n));
}
}
char s[MAXN];
int len, n, ans[MAXN * 8], done[MAXN * 4];
int dp[MAXN * 4][MAXN][MAXN];
int main() {
scanf("%s", s + 1);
len = strlen(s + 1), read(n);
dp[0][1][len] = 1;
for (int k = 1; k <= len * 4 + 5; k++) {
done[k] = done[k - 1] * 26 % P;
for (int i = 1; i <= len; i++)
for (int j = i; j <= len; j++) {
if (s[i] == s[j]) {
if (i + 1 <= j - 1) update(dp[k][i + 1][j - 1], dp[k - 1][i][j]);
else update(done[k], dp[k - 1][i][j]);
update(dp[k][i][j], dp[k - 1][i][j] * 25 % P);
} else {
if (i == j) update(done[k], dp[k - 1][i][j] * 2 % P);
else {
update(dp[k][i + 1][j], dp[k - 1][i][j]);
update(dp[k][i][j - 1], dp[k - 1][i][j]);
}
update(dp[k][i][j], dp[k - 1][i][j] * 24 % P);
}
}
}
for (int i = 1; i <= len * 8 + 5; i++) {
if (i & 1) {
ans[i] = done[i / 2] * 26 % P;
for (int j = 1; j <= len; j++)
update(ans[i], dp[i / 2][j][j]);
} else ans[i] = done[i / 2];
}
LinearSequence :: work(len * 7 + 5, ans + len, n);
return 0;
}
Codeforces 512D Fox And Travelling
首先,显然各个联通块是相互独立的,最后可以用指数型生成函数卷积计算答案。
对于一个联通块,考虑计算数组 dpidp_idpi 表示在连通块中删去 iii 个节点的方案数。
考虑枚举一个不会被删去,或是最后被删去的根节点,累加各个根节点的贡献,最后将 dpidp_{i}dpi 除去 s−is-is−i 即可,其中 sss 表示连通块大小。
确定根节点后,被删去的叶子一定是若干个无环的子树,它们分别的删除序列数即为各自的外向拓扑排序数,并且同样可以用指数型生成函数卷积计算总方案数,因此,只需要在搜索树上动态规划即可。
时间复杂度 O(N3+N×M)O(N^3+N\times M)O(N3+N×M) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 105;
const int P = 1e9 + 9;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
vector <int> a[MAXN];
bool vis[MAXN], c[MAXN];
int power(int x, int y) {
if (y == 0) return 1;
int tmp = power(x, y / 2);
if (y % 2 == 0) return 1ll * tmp * tmp % P;
else return 1ll * tmp * tmp % P * x % P;
}
vector <int> p;
int v[MAXN], e[MAXN], f[MAXN], dp[MAXN][MAXN];
int n, m, cnt, ans[MAXN], fac[MAXN], inv[MAXN];
void dfs(int pos) {
vis[pos] = true, p.push_back(pos);
for (auto x : a[pos]) if (!vis[x]) dfs(x);
}
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
void work(int pos) {
memset(dp[pos], 0, sizeof(dp[pos]));
dp[pos][0] = f[pos] = 1, c[pos] = true;
v[pos] = 1, e[pos] = a[pos].size();
for (auto x : a[pos]) {
if (!c[x]) {
work(x);
f[pos] = 1ll * f[pos] * f[x] % P;
static int tmp[MAXN];
memset(tmp, 0, sizeof(tmp));
for (int i = 0; i <= v[pos]; i++)
for (int j = 0; j <= v[x]; j++)
update(tmp[i + j], 1ll * dp[pos][i] * dp[x][j] % P);
memcpy(dp[pos], tmp, sizeof(tmp));
v[pos] += v[x];
e[pos] += e[x];
}
}
f[pos] = 1ll * f[pos] * inv[v[pos]] % P * fac[v[pos] - 1] % P;
if (e[pos] <= 2 * v[pos] - 1) update(dp[pos][v[pos]], f[pos]);
}
int main() {
read(n), read(m);
fac[0] = inv[0] = 1;
for (int i = 1; i <= n; i++) {
fac[i] = 1ll * fac[i - 1] * i % P;
inv[i] = power(fac[i], P - 2);
}
for (int i = 1; i <= m; i++) {
int x, y; read(x), read(y);
a[x].push_back(y);
a[y].push_back(x);
}
ans[cnt = 0] = 1;
for (int i = 1; i <= n; i++) {
if (vis[i]) continue;
p.clear(), dfs(i);
static int res[MAXN]; int s = p.size();
memset(res, 0, sizeof(res));
for (auto x : p) {
memset(c, 0, sizeof(c)), work(x);
for (int j = 0; j <= s; j++)
update(res[j], dp[x][j]);
}
static int tmp[MAXN];
memset(tmp, 0, sizeof(tmp));
for (int j = 0; j <= s; j++) {
int lft = s - j;
if (lft != 0) res[j] = 1ll * res[j] * inv[lft] % P * fac[lft - 1] % P;
for (int k = 0; k <= cnt; k++)
update(tmp[j + k], 1ll * res[j] * ans[k] % P);
}
cnt += s;
memcpy(ans, tmp, sizeof(tmp));
}
for (int i = 0; i <= n; i++)
printf("%lld\n", 1ll * ans[i] * fac[i] % P);
return 0;
}
Codeforces 516D Drazil and Morning Exercise
求出树的直径 (x,y)(x,y)(x,y) ,则树可以分为两部分,到每一部分的最远点是 x,yx,yx,y 之一。
若选定子集横跨两个部分,则所选子集一定是对应距离最短的若干个点,可以简单计算。
若选定子集完全在某个部分内,则考虑枚举所选的最小的元素 xxx ,其余元素应当是 xxx 子树内最小的若干元素,可以通过启发式合并堆计算答案。
时间复杂度 O(NLogN+QNLog2N)O(NLogN+QNLog^2N)O(NLogN+QNLog2N) ,可以用并查集优化至 O(NLogN+QNα(N))O(NLogN+QN\alpha(N))O(NLogN+QNα(N)) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int MAXLOG = 20;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, q, rt[MAXN], depth[MAXN], father[MAXN][MAXLOG];
ll l, path[MAXN], dist[MAXN], res[MAXN];
vector <pair <int, int>> a[MAXN];
priority_queue <ll> Heap[MAXN];
void getans(int pos, int fa) {
while (!Heap[pos].empty()) Heap[pos].pop();
Heap[pos].push(res[pos]);
for (auto x : a[pos])
if (x.first != fa) {
getans(x.first, pos);
while (Heap[x.first].size() > Heap[pos].size()) swap(Heap[x.first], Heap[pos]);
while (!Heap[x.first].empty()) {
Heap[pos].push(Heap[x.first].top());
Heap[x.first].pop();
}
}
while (Heap[pos].top() - res[pos] > l) Heap[pos].pop();
rt[pos] = Heap[pos].size();
}
void work(int pos, int fa) {
depth[pos] = depth[fa] + 1;
father[pos][0] = fa;
for (int i = 1; i < MAXLOG; i++)
father[pos][i] = father[father[pos][i - 1]][i - 1];
for (unsigned i = 0; i < a[pos].size(); i++)
if (a[pos][i].first != fa) {
path[a[pos][i].first] = path[pos] + a[pos][i].second;
work(a[pos][i].first, pos);
}
}
int lca(int x, int y) {
if (depth[x] < depth[y]) swap(x, y);
for (int i = MAXLOG - 1; i >= 0; i--)
if (depth[father[x][i]] >= depth[y]) x = father[x][i];
if (x == y) return x;
for (int i = MAXLOG - 1; i >= 0; i--)
if (father[x][i] != father[y][i]) {
x = father[x][i];
y = father[y][i];
}
return father[x][0];
}
void dfs(int pos, int fa) {
for (auto x : a[pos])
if (x.first != fa) {
dist[x.first] = dist[pos] + x.second;
dfs(x.first, pos);
}
}
int main() {
read(n);
for (int i = 1; i <= n - 1; i++) {
int x, y, z;
read(x), read(y), read(z);
a[x].emplace_back(y, z);
a[y].emplace_back(x, z);
}
work(1, 0);
int x = 0, y = 0;
dist[1] = 0, dfs(1, 0);
for (int i = 1; i <= n; i++)
if (dist[i] > dist[x]) x = i;
dist[x] = 0, dfs(x, 0);
for (int i = 1; i <= n; i++)
if (dist[i] > dist[y]) y = i;
ll Min = 1e18; int rx = 0, ry = 0;
for (int i = 1; i <= n; i++) {
ll lenx = path[i] + path[x] - 2 * path[lca(i, x)];
ll leny = path[i] + path[y] - 2 * path[lca(i, y)];
res[i] = max(lenx, leny);
chkmin(Min, res[i]);
if (lenx > leny) {
if (rx == 0 || res[i] < res[rx]) rx = i;
} else {
if (ry == 0 || res[i] < res[ry]) ry = i;
}
}
read(q);
while (q--) {
read(l);
int ans = 0;
for (int i = 1; i <= n; i++)
if (res[i] - Min <= l) ans++;
getans(rx, ry);
getans(ry, rx);
for (int i = 1; i <= n; i++)
chkmax(ans, rt[i]);
printf("%d\n", ans);
}
return 0;
}
Codeforces 516E Drazil and His Happy Friends
若 N,MN,MN,M 不互质,问题可以拆成 gcd(N,M)gcd(N,M)gcd(N,M) 个等价的问题分别处理,考虑 N,MN,MN,M 互质的情况。
若 b=g=0b=g=0b=g=0 ,显然答案为 −1-1−1 。
否则,考虑快乐的传播,若 iii 时刻交流的两人存在快乐的人,那么 i+N,i+Mi+N,i+Mi+N,i+M 时刻交流的人也会存在快乐的人,因此 iii 时刻的快乐能够传播到的范围为 i+xN+yM (x,y∈N)i+xN+yM\ (x,y\in\N)i+xN+yM (x,y∈N) 。
对于每个人,我们关心的是 i+xN+yMi+xN+yMi+xN+yM 模 NNN 或 MMM 的余数,以 111 号人群为例,第 xxx 个人最早被传播的时刻即为使得 i+yM≡x (mod N)i+yM\equiv x\ (mod\ N)i+yM≡x (mod N) 的最小的 i+yMi+yMi+yM 。
0,1,2,…,N−10,1,2,\dots,N-10,1,2,…,N−1 中的所有数按照 0,M mod N,2M mod N,…,(N−1)M mod N0,M\ mod\ N,2M\ mod\ N,\dots,(N-1)M\ mod\ N0,M mod N,2M mod N,…,(N−1)M mod N 的顺序形成了一个环,分别考虑每个关键点在环上的后继即可。
时间复杂度 O((b+g)×(LogN+LogM))O((b+g)\times (LogN+LogM))O((b+g)×(LogN+LogM)) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
vector <int> a[MAXN];
void exgcd(int a, int b, int &x, int &y) {
if (b == 0) {
x = 1;
y = 0;
return;
}
int q = a / b, r = a % b;
exgcd(b, r, y, x);
y -= q * x;
}
ll calc(int n, int m, vector <int> a) {
int x = 0, inv = 0;
exgcd(n, m, x, inv);
inv = (inv % n + n) % n;
static pair <int, int> s[MAXN]; int tot = 0;
for (auto &x : a) s[++tot] = make_pair(1ll * x * inv % n, x);
sort(s + 1, s + tot + 1);
ll ans = 0;
for (int i = 2; i <= tot; i++) {
if (s[i].first == s[i - 1].first) chkmin(s[i].second, s[i - 1].second);
if (s[i].first - s[i - 1].first >= 2) chkmax(ans, (s[i].first - s[i - 1].first - 1ll) * m + s[i - 1].second);
}
if (s[1].first + n - s[tot].first >= 2) chkmax(ans, (s[1].first + n - s[tot].first - 1ll) * m + s[tot].second);
return ans;
}
ll getans(int x, int y, vector <int> a) {
return max(calc(x, y, a), calc(y, x, a));
}
ll get(vector <int> a, int n) {
if (a.size() == n) return 0;
sort(a.begin(), a.end());
while (a.size() != 0 && a.back() == n - 1) a.pop_back(), n--;
return n - 1;
}
int main() {
int n, m; read(n), read(m);
int g = __gcd(n, m);
if (g >= MAXN) {
puts("-1");
return 0;
}
int cnt; read(cnt);
vector <int> tx, ty;
while (cnt--) {
int x; read(x);
a[x % g].push_back(x / g);
tx.push_back(x);
}
read(cnt);
while (cnt--) {
int x; read(x);
a[x % g].push_back(x / g);
ty.push_back(x);
}
ll ans = max(get(tx, n), get(ty, m));
for (int i = 0; i <= g - 1; i++)
if (a[i].empty()) {
puts("-1");
return 0;
} else {
sort(a[i].begin(), a[i].end());
a[i].erase(unique(a[i].begin(), a[i].end()), a[i].end());
if (a[i].size() < max(n, m) / g) chkmax(ans, 1ll * g * getans(n / g, m / g, a[i]) + i);
}
writeln(ans);
return 0;
}
Codeforces 521D Shop
对于一个确定的操作集合,显然应该先操作 111 类操作,再操作 222 类操作,最后操作 333 类操作。
显然,对于同一个元素, 111 类操作最多进行一次,因此,计算其增量,可以将其视为一个 222 类操作。
并且,对于同一个元素的所有 222 类操作,被选中的一定是增量最大的若干个操作,因此,可以认为 222 类操作是按照增量从大到小的顺序进行的,从而 222 类操作同样可以视为对答案乘上一个常数。
由此,将所有操作对答案所乘的常数按大到小排序,即可求得方案。
时间复杂度 O(NLogN+K)O(NLogN+K)O(NLogN+K) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int P = 1e9 + 7;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int k, n, m, tot;
int Maxone[MAXN]; ll v[MAXN];
vector <pair <int, int>> inc[MAXN];
pair <pair <ll, ll>, int> a[MAXN];
int opt[MAXN], who[MAXN], w[MAXN];
pair <ll, ll> prod(ll a, ll b) {
pair <ll, ll> res = make_pair(a / P * b + b / P * a - (a / P) * (b / P) * P, (a % P) * (b % P));
res.first += res.second / P;
res.second %= P;
return res;
}
bool cmp(pair <pair <ll, ll>, int> a, pair <pair <ll, ll>, int> b) {
return prod(a.first.first, b.first.second) > prod(a.first.second, b.first.first);
}
int main() {
read(k), read(n), read(m);
for (int i = 1; i <= k; i++)
read(v[i]);
for (int i = 1; i <= n; i++) {
read(opt[i]), read(who[i]), read(w[i]);
if (opt[i] == 1 && w[i] > w[Maxone[who[i]]]) Maxone[who[i]] = i;
if (opt[i] == 2) inc[who[i]].emplace_back(w[i], i);
if (opt[i] == 3) a[++tot] = make_pair(make_pair(w[i], 1), i);
}
for (int i = 1; i <= k; i++)
if (w[Maxone[i]] > v[i]) inc[i].emplace_back(w[Maxone[i]] - v[i], Maxone[i]);
for (int i = 1; i <= k; i++) {
sort(inc[i].begin(), inc[i].end());
reverse(inc[i].begin(), inc[i].end());
for (auto x : inc[i]) {
a[++tot] = make_pair(make_pair(v[i] + x.first, v[i]), x.second);
v[i] += x.first;
}
}
sort(a + 1, a + tot + 1, cmp);
vector <int> ans;
for (int i = 1; i <= min(m, tot); i++)
ans.push_back(a[i].second);
cout << ans.size() << endl;
sort(ans.begin(), ans.end(), [&] (int x, int y) {return opt[x] < opt[y]; });
for (auto x : ans) printf("%d ", x);
return 0;
}
Codeforces 521E Cycling City
我们需要找到一个环,以及与该环仅交于某两个点的一条路径。
因此,可以分别考虑每个点双联通分量,若某点双不是环,且不是路径,则可以构造出一个方案。
在点双内 DFS 找到一个环,删去环边,再 DFS 找到一条路径即可。
时间复杂度 O(N+M)O(N+M)O(N+M) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
bool instack[MAXN], ine[MAXN], key[MAXN], thr[MAXN], vis[MAXN];
vector <pair <int, int>> a[MAXN];
int n, m, timer, dfn[MAXN], low[MAXN];
int top, Stack[MAXN], se[MAXN], cnte[MAXN];
void getans(int pos, int fa, int dest, vector <int> &cur) {
if (pos == dest) {
cout << cur.size();
for (auto x : cur) printf(" %d", x);
printf("\n");
return;
}
for (auto x : a[pos])
if (x.first != fa && ine[x.second]) {
cur.push_back(x.first);
getans(x.first, pos, dest, cur);
cur.pop_back();
}
}
void findpath(int pos, vector <int> &cur) {
if (instack[pos]) {
cout << cur.size();
assert(pos != cur[0]);
for (auto x : cur) printf(" %d", x);
printf("\n");
vector <int> tmp = {cur[0]};
getans(cur[0], 0, cur.back(), tmp);
exit(0);
}
vis[pos] = true;
for (auto x : a[pos])
if (key[x.first] && !vis[x.first] && !ine[x.second] && !thr[x.second]) {
cur.push_back(x.first);
thr[x.second] = true;
findpath(x.first, cur);
cur.pop_back();
}
}
void dfs(int pos) {
instack[pos] = true;
Stack[++top] = pos;
for (auto x : a[pos])
if (!ine[x.second]) {
ine[x.second] = true;
se[top] = x.second;
if (instack[x.first]) {
for (int i = 1; i <= top; i++) {
if (Stack[i] == x.first) break;
instack[Stack[i]] = false;
ine[se[i]] = false;
}
for (int i = 1; i <= n; i++) {
if (!instack[i]) continue;
for (auto y : a[i])
if (key[y.first] && !ine[y.second]) {
thr[y.second] = true;
vector <int> cur = {i, y.first};
vis[i] = true;
findpath(y.first, cur);
assert(false);
}
}
assert(false);
exit(0);
} else if (key[x.first]) dfs(x.first);
ine[x.second] = false;
}
top--, instack[pos] = false;
}
void solve(vector <int> s) {
for (auto x : s) key[x] = true;
memset(ine, false, sizeof(ine));
memset(instack, false, sizeof(instack));
top = 0, dfs(s[0]);
}
void tarjan(int pos) {
instack[pos] = true;
dfn[pos] = low[pos] = ++timer;
Stack[++top] = pos, cnte[top] = 0;
int upe = 0, tmp = top;
for (auto x : a[pos]) {
if (dfn[x.first] == 0) {
tarjan(x.first);
chkmin(low[pos], low[x.first]);
if (dfn[pos] == low[x.first]) {
vector <int> s = {pos}; int e = 0;
int tmp = Stack[top];
s.push_back(tmp), e += cnte[top--];
while (tmp != x.first) {
tmp = Stack[top];
s.push_back(tmp), e += cnte[top--];
}
if (e >= s.size() + 1) {
puts("YES");
solve(s);
assert(false);
exit(0);
}
}
} else chkmin(low[pos], dfn[x.first]), upe += instack[x.first];
}
cnte[tmp] = upe;
instack[pos] = false;
}
int main() {
read(n), read(m);
for (int i = 1; i <= m; i++) {
int x, y; read(x), read(y);
a[x].emplace_back(y, i);
a[y].emplace_back(x, i);
}
for (int i = 1; i <= n; i++)
if (dfn[i] == 0) tarjan(i);
puts("NO");
return 0;
}
Codeforces 526F Pudding Monsters
考虑分治,我们需要在 O(N)O(N)O(N) 的时间内计算横跨某一位置 midmidmid 的连续区间数量。
对于连续的区间,需要满足 Max−Min=r−lMax-Min=r-lMax−Min=r−l 。
枚举最大,最小值的出现的位置在 midmidmid 的左侧还是右侧。
若最大,最小值出现在同侧,可以枚举对应的左(右)端点,并计算出右(左)端点,再判断是否合法。
若最大,最小值出现在不同侧,例如最大值在左,最小值在右,则有 l−Max=r−Minl-Max=r-Minl−Max=r−Min ,枚举 lll ,可以用哈希表维护合法的 rrr 的数量。并且,为了保证最大值在左,最小值在右, rrr 需要在一个特定的区间内,该区间的两个端点均是单调的,可以用双指针维护。
实现时需要使用 STSTST 表,时间复杂度 O(NLogN)O(NLogN)O(NLogN) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
namespace rmq {
const int MAXN = 3e5 + 5;
const int MAXLOG = 19;
int Max[MAXN][MAXLOG], Min[MAXN][MAXLOG], Log[MAXN];
int queryMax(int l, int r) {
int len = r - l + 1, tmp = Log[len];
return max(Max[l][tmp], Max[r - (1 << tmp) + 1][tmp]);
}
int queryMin(int l, int r) {
int len = r - l + 1, tmp = Log[len];
return min(Min[l][tmp], Min[r - (1 << tmp) + 1][tmp]);
}
void init(int *a, int n) {
for (int i = 1; i <= n; i++) {
Min[i][0] = a[i];
Max[i][0] = a[i];
Log[i] = Log[i - 1];
if ((1 << (Log[i] + 1)) <= i) Log[i]++;
}
for (int t = 1; t < MAXLOG; t++)
for (int i = 1, j = (1 << (t - 1)) + 1; j <= n; i++, j++) {
Max[i][t] = max(Max[i][t - 1], Max[j][t - 1]);
Min[i][t] = min(Min[i][t - 1], Min[j][t - 1]);
}
}
}
int n, a[MAXN], cnt[MAXN * 2]; ll ans;
void solve(int l, int r) {
if (l == r) return;
int mid = (l + r) / 2;
solve(l, mid);
solve(mid + 1, r);
// Both l
for (int i = mid, Min = a[i], Max = a[i], pos = mid; i >= l; i--) {
chkmin(Min, a[i]), chkmax(Max, a[i]);
while (pos + 1 <= r && a[pos + 1] > Min && a[pos + 1] < Max) pos++;
int j = i + Max - Min; ans += j > mid && j <= pos && rmq :: queryMin(i, j) == Min && rmq :: queryMax(i, j) == Max;
}
// Both r
for (int i = mid + 1, Min = a[i], Max = a[i], pos = mid + 1; i <= r; i++) {
chkmin(Min, a[i]), chkmax(Max, a[i]);
while (pos - 1 >= l && a[pos - 1] > Min && a[pos - 1] < Max) pos--;
int j = i - Max + Min; ans += j <= mid && j >= pos && rmq :: queryMin(j, i) == Min && rmq :: queryMax(j, i) == Max;
}
// l Max, r Min
int pa = mid, pb = mid + 1;
for (int i = mid, Min = a[i], Max = a[i]; i >= l; i--) {
chkmin(Min, a[i]), chkmax(Max, a[i]);
while (pa + 1 <= r && a[pa + 1] < Max) {pa++; cnt[pa + rmq :: queryMin(mid + 1, pa)]++; }
while (pb <= r && rmq :: queryMin(i, pb) == Min) {cnt[pb + rmq :: queryMin(mid + 1, pb)]--; pb++;}
if (pb <= pa) ans += cnt[Max + i];
}
if (pb <= pa) {
for (int i = pb; i <= pa; i++)
cnt[i + rmq :: queryMin(mid + 1, i)]--;
} else {
for (int i = pa + 1; i <= pb - 1; i++)
cnt[i + rmq :: queryMin(mid + 1, i)]++;
}
// l Min, r Max
pa = mid, pb = mid + 1;
for (int i = mid, Min = a[i], Max = a[i]; i >= l; i--) {
chkmin(Min, a[i]), chkmax(Max, a[i]);
while (pa + 1 <= r && a[pa + 1] > Min) {pa++; cnt[n + pa - rmq :: queryMax(mid + 1, pa)]++; }
while (pb <= r && rmq :: queryMax(i, pb) == Max) {cnt[n + pb - rmq :: queryMax(mid + 1, pb)]--; pb++;}
if (pb <= pa) ans += cnt[n + i - Min];
}
if (pb <= pa) {
for (int i = pb; i <= pa; i++)
cnt[n + i - rmq :: queryMax(mid + 1, i)]--;
} else {
for (int i = pa + 1; i <= pb - 1; i++)
cnt[n + i - rmq :: queryMax(mid + 1, i)]++;
}
}
int main() {
read(n);
for (int i = 1; i <= n; i++) {
int x, y; read(x), read(y);
a[x] = y;
}
rmq :: init(a, n);
ans = n, solve(1, n);
cout << ans << endl;
return 0;
}
Codeforces 526G Spiders Evil Plan
不妨无视要求链并连通的限制,因为最大的链并必然是连通的。
询问 (x,y)(x,y)(x,y) 可以如下转述:找到至多 2y2y2y 个叶子节点,使得这些点的虚树大小最大,且包含 xxx 。
不计复杂度地,考虑以 xxx 为根进行长链剖分,则应当选出最大的 2y2y2y 条长链,若这个方案使得 xxx 的度数为 111 ,则需要考虑一些边界情况。
注意到上面的做法选出的方案一定会包含直径的一端,可以枚举直径的某一端,以其为根处理询问。
对于询问 (x,y)(x,y)(x,y) ,若最大的 2y−12y-12y−1 条长链包含 xxx ,则答案显然为这些长链的大小之和。否则,则需要删去其中最短的长链,或者最近的长链,具体可见下文代码中的 queryqueryquery 函数。
时间复杂度 O((N+Q)LogN)O((N+Q)LogN)O((N+Q)LogN) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
const int MAXLOG = 18;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, q, depth[MAXN];
vector <pair <int, int>> a[MAXN];
struct Answer {
int depth[MAXN], Max[MAXN], son[MAXN], leaf[MAXN];
int father[MAXN][MAXLOG], Min[MAXN][MAXLOG];
int inc[MAXN], sum[MAXN], vis[MAXN];
pair <int, int> seg[MAXN]; int tot;
void dfs(int pos, int fa) {
father[pos][0] = fa;
Max[pos] = depth[pos];
leaf[pos] = pos;
for (auto x : a[pos])
if (x.first != fa) {
depth[x.first] = depth[pos] + x.second;
dfs(x.first, pos);
if (Max[x.first] > Max[pos]) {
leaf[pos] = leaf[x.first];
Max[pos] = Max[x.first];
son[pos] = x.first;
}
}
}
void efs(int pos, int fa) {
if (pos != son[fa]) seg[++tot] = make_pair(Max[pos] - depth[fa], leaf[pos]);
for (auto x : a[pos]) if (x.first != fa) efs(x.first, pos);
}
void init(int from) {
dfs(from, 0);
efs(from, 0);
sort(seg + 1, seg + tot + 1);
reverse(seg + 1, seg + tot + 1);
for (int i = 1; i <= tot; i++) {
int pos = seg[i].second;
while (vis[pos] == 0) {
vis[pos] = Min[pos][0] = i;
pos = father[pos][0];
}
inc[i] = seg[i].first;
sum[i] = inc[i] + sum[i - 1];
}
for (int p = 1; p < MAXLOG; p++)
for (int i = 1; i <= n; i++) {
father[i][p] = father[father[i][p - 1]][p - 1];
Min[i][p] = min(Min[i][p - 1], Min[father[i][p - 1]][p - 1]);
}
}
int query(int x, int y) {
y = y * 2 - 1;
if (y >= tot) return sum[tot];
if (vis[x] <= y) return sum[y];
int ans = sum[y], pos = x;
for (int i = MAXLOG - 1; i >= 0; i--)
if (Min[pos][i] > y) pos = father[pos][i];
ans += Max[x] - depth[pos];
ans -= min(inc[y], Max[pos] - depth[pos]);
return ans;
}
} s, t;
void getdepth(int pos, int fa) {
for (auto x : a[pos])
if (x.first != fa) {
depth[x.first] = depth[pos] + x.second;
getdepth(x.first, pos);
}
}
int main() {
read(n), read(q);
for (int i = 1; i <= n - 1; i++) {
int x, y, z;
read(x), read(y), read(z);
a[x].emplace_back(y, z);
a[y].emplace_back(x, z);
}
getdepth(1, 0);
int x = max_element(depth + 1, depth + n + 1) - depth;
depth[x] = 0, getdepth(x, 0);
int y = max_element(depth + 1, depth + n + 1) - depth;
s.init(x), t.init(y);
int lastans = 0;
for (int i = 1; i <= q; i++) {
int x, y; read(x), read(y);
x = (x + lastans - 1) % n + 1;
y = (y + lastans - 1) % n + 1;
printf("%d\n", lastans = max(s.query(x, y), t.query(x, y)));
}
return 0;
}