JOISC 2022 乱做

非传统题不做。

Day1 T1 Jail

给定一棵 \(n\) 个点的树,有 \(m\) 个人,第 \(i\) 个人从 \(s_i\) 出发沿最短路径到 \(t_i\),每次可以指定一个人走一条边。问是否存在一种方案让每个人都到 \(t_i\),且满足任何两个人不同时出现在同一个节点。

\(m \leq n \leq 1.2 \times 10^5\)\(s_i\) 互不相同,\(t_i\) 互不相同,\(s_i \neq t_i\)

solution

我们断言,如果有解,一定存在一组解使得每个人的移动都是连续的,这点通过调整容易说明。考虑什么时候无解,感性理解一下发现就是有若干个人的路径形成了一个环的结构,这启发我们试着对这 \(n\) 个人移动的顺序进行约束。具体来说,对于两个人 \(i,j\),若 \(s_i \in (s_j,t_j)\),那么我们连边 \(i \to j\) 表示 \(i\) 要在 \(j\) 之前移动;若 \(t_i \in (s_j,t_j)\),那么我们连边 \(j \to i\) 表示 \(j\) 要在 \(i\) 之前移动。然后跑拓扑排序即可,无解当且仅当连出来的图成环。连边用倍增优化,时间复杂度 \(O((n+m) \log n)\)


因为没严谨证明早上在 UOJ 群被兔 D 了,现在来补一个(其实是抄了 rqy 老师的,当时证的确实有点口胡了

如果不存在连续的解,我们考虑一个局部,即某个人从 \(x\) 连续走到 \(y\),在 \(y\) 停了一段时间,然后连续走一段到 \(z\)。证明的关键是,这个人在 \(y\) 停留的这段时间里,以 \(y\) 分割出的每个连通块内的步骤都是独立的。我们把 \(z\) 所在的连通块称作 “后连通块”,其余的称作 “前连通块”。

调整前,我们的操作大概长成这样:\(\cdots \to\) 这个人从 \(x\)\(y\) \(\to\) “后连通块” 内的行动 \(\to\) “前连通块” 内的行动 \(\to\) 这个人从 \(y\)\(z\) \(\to \cdots\)。其中由于每个连通块独立,因此前后连通块的顺序是无所谓的。

我们发现,这个顺序可以调整成: \(\cdots \to\) “后连通块” 内的行动 \(\to\) 这个人从 \(x\)\(y\) \(\to\) 这个人从 \(y\)\(z\) \(\to\) “前连通块” 内的行动 \(\to \cdots\)。这是因为从 \(x\) 走到 \(y\) 与后连通块独立,而从 \(y\) 走到 \(z\) 与前连通块独立。

于是这样调整后,总的 “连续走的段数” 严格减少。于是重复这个过程就能够得到一组所有人都连续的解。

code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
#define pii pair<int, int>
#define mp(x, y) make_pair(x, y)
#define pb push_back
#define eb emplace_back
#define fi first
#define se second
#define int long long
#define mem(x, v) memset(x, v, sizeof(x))
#define mcpy(x, y, n) memcpy(x, y, sizeof(int) * (n))
#define lob lower_bound
#define upb upper_bound
#define TIME 1e3 * clock() / CLOCKS_PER_SEC
using namespace std;

inline int read() {
	int x = 0, w = 1;char ch = getchar();
	while (ch > '9' || ch < '0') { if (ch == '-')w = -1;ch = getchar(); }
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
	return x * w;
}

const int MN = 2e5 + 5;
const int MM = 5e6 + 5;
const int Mod = 1e9 + 7;

inline int min(int x, int y) { return x < y ? x : y; }
inline int max(int x, int y) { return x > y ? x : y; }

inline int qPow(int a, int b = Mod - 2, int ret = 1) {
    while (b) {
        if (b & 1) ret = ret * a % Mod;
        a = a * a % Mod, b >>= 1;
    }
    return ret;
}

#define dbg

int N, M, s[MN], t[MN];
int dep[MN], deg[MM], fa[MN][20], up[MN][20], down[MN][20], id;
int dfn[MN], dfc, sz[MN];
vector <int> e[MN], w[MM];
queue <int> q;

inline void DFS(int u, int pr) {
    dep[u] = dep[fa[u][0] = pr] + 1;
    dfn[u] = ++dfc, sz[u] = 1;
    up[u][0] = ++id;
    down[u][0] = ++id;
    for (int i = 1; i <= 17; i++) {
        fa[u][i] = fa[fa[u][i - 1]][i - 1];
        up[u][i] = ++id;
        w[up[u][i - 1]].pb(id); 
        if (fa[u][i - 1]) w[up[fa[u][i - 1]][i - 1]].pb(id);
        down[u][i] = ++id;
        w[id].pb(down[u][i - 1]);
        if (fa[u][i - 1]) w[id].pb(down[fa[u][i - 1]][i - 1]);
    }
    for (int v : e[u]) 
        if (v != pr) DFS(v, u), sz[u] += sz[v];
}
inline void add1(int id, int x, int y) {
    if (dfn[x] <= dfn[y] && dfn[y] < dfn[x] + sz[x]) {
        for (int i = 17; i >= 0; i--)
            if (dep[fa[y][i]] >= dep[x]) w[up[y][i]].pb(id), y = fa[y][i];
    } else {
        x = fa[x][0];
        if (dep[x] < dep[y]) swap(x, y);
        for (int i = 17; i >= 0; i--) 
            if (dep[fa[x][i]] >= dep[y]) w[up[x][i]].pb(id), x = fa[x][i];
        if (x == y) w[up[x][0]].pb(id);
        else {
            for (int i = 17; i >= 0; i--)  
                if (fa[x][i] != fa[y][i]) 
                    w[up[x][i]].pb(id), w[up[y][i]].pb(id), x = fa[x][i], y = fa[y][i];

            w[up[x][0]].pb(id), w[up[y][1]].pb(id);
        }
    }
}
inline void add2(int id, int x, int y) {
    if (dfn[y] <= dfn[x] && dfn[x] < dfn[y] + sz[y]) {
        for (int i = 17; i >= 0; i--)
            if (dep[fa[x][i]] >= dep[y]) w[id].pb(down[x][i]), x = fa[x][i];
    } else {
        y = fa[y][0];
        if (dep[x] < dep[y]) swap(x, y);
        for (int i = 17; i >= 0; i--) 
            if (dep[fa[x][i]] >= dep[y]) w[id].pb(down[x][i]), x = fa[x][i];
        if (x == y) w[id].pb(down[x][0]);
        else {
            for (int i = 17; i >= 0; i--)  
                if (fa[x][i] != fa[y][i]) 
                    w[id].pb(down[x][i]), w[id].pb(down[y][i]), x = fa[x][i], y = fa[y][i];

            w[id].pb(down[x][0]), w[id].pb(down[y][1]);
        }
    }
}
inline void work() {
    N = read(), dfc = 0;
    for (int i = 1; i <= N; i++) e[i].clear();
    for (int i = 1; i <= id; i++) w[i].clear(), deg[i] = 0;
    for (int i = 1, x, y; i < N; i++) {
        x = read(), y = read();
        e[x].pb(y), e[y].pb(x);
    }
    M = read(), id = M;
    DFS(1, 0);
    for (int i = 1, x, y; i <= M; i++) {
        x = read(), y = read();
        w[i].pb(up[x][0]), w[down[y][0]].pb(i);
        add1(i, x, y), add2(i, x, y);
    }
    int fl = 0;
    for (int i = 1; i <= id; i++) for (int x : w[i]) deg[x]++;
    for (int i = 1; i <= id; i++) if (!deg[i]) q.push(i);
    while (!q.empty()) {
        int x = q.front(); q.pop(), fl += x <= M;
        for (int y : w[x]) 
            if (!(--deg[y])) q.push(y);
    }
    puts(fl == M ? "Yes" : "No");
}

signed main(void) {
    int T = read();
    while (T--) work();
    return 0;
}

Day1 T2 Sightseeing in Kyoto

给定 \(n \times m\) 的网格图,第 \(i\) 行所有边权值为 \(a_i\),第 \(j\) 列所有边权值为 \(b_j\),每次只能向右或者向下移动,求 \((1,1)→(n,m)\) 的最短路。

\(n,m \leq 10^5\)

solution

先考虑简单的情况,如果只经过一次转弯要从 \((i,j) \to (x,y)\),那么有两种可能的路径:\((i,j) \to (i,y) \to (x,y)\)\((i,j) \to (x,j) \to (x,y)\)。这两条路径所对应的大小分别是 \((y-j)a_i + (x-i) b_y\)\((x-i) b_j + (y-j) a_x\)。稍作化简可以得到当 \(\frac{a_x - a_i}{x - i} > \frac{b_y - b_j}{y - j}\) 时我们会选择第一条路,否则会选择第二条。

对于当前的所有行和列,把行和列分别看成点 \((i,a_i)\)\((j,b_j)\),上式相当于比较了两个斜率。不妨先关注所有 \(2 \times 2\) 的小方格,处理出所有相邻位置(包括行和列)的斜率,那么对于斜率最大的两个点而言(假设是第 \(i\) 行和第 \(i+1\) 行),容易发现我们一定不会经过第 \(i+1\) 行,因此可以直接将第 \(i+1\) 行删去。

于是我们可以直接模拟上述过程,用 set 维护当前所有相邻点的斜率,每次取出最大的一个,删掉对应行 / 列并更新相邻点的斜率,如果删掉该行 / 列后 \((1,1)\)\((n,m)\) 不连通就更新答案,维护一下从 \((n,m)\) 开始的已经确定的路径延伸到哪里就行了。时间复杂度 \(O((n+m) \log (n+m))\)

还可以继续优化:回顾我们求解答案的过程,每次取斜率最大的位置删除,然后如果该位置是最靠右的点就计入答案;不难发现对于行和列而言,只有在下凸包上的点可能对答案产生贡献。于是我们可以直接对行和列求下凸包,这样相邻位置的斜率都是单调的,那么每次只会删除结尾位置的点,用双指针维护即可。当然也可以从前往后用双指针维护决策,反正是等价的。时间复杂度 \(O(n+m)\)

code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
#define pii pair<int, int>
#define mp(x, y) make_pair(x, y)
#define pb push_back
#define eb emplace_back
#define fi first
#define se second
#define int long long
#define mem(x, v) memset(x, v, sizeof(x))
#define mcpy(x, y, n) memcpy(x, y, sizeof(int) * (n))
#define lob lower_bound
#define upb upper_bound
#define TIME 1e3 * clock() / CLOCKS_PER_SEC
using namespace std;

inline int read() {
	int x = 0, w = 1;char ch = getchar();
	while (ch > '9' || ch < '0') { if (ch == '-')w = -1;ch = getchar(); }
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
	return x * w;
}

const int MN = 1e5 + 5;
const int Mod = 1e9 + 7;

inline int min(int x, int y) { return x < y ? x : y; }
inline int max(int x, int y) { return x > y ? x : y; }

inline int qPow(int a, int b = Mod - 2, int ret = 1) {
    while (b) {
        if (b & 1) ret = ret * a % Mod;
        a = a * a % Mod, b >>= 1;
    }
    return ret;
}

#define dbg

int N, M, a[MN], b[MN], p[MN], q[MN], l, r;

signed main(void) {
    N = read(), M = read();
    for (int i = 1; i <= N; i++) a[i] = read();
    for (int i = 1; i <= M; i++) b[i] = read();
    p[l = 1] = q[r = 1] = 1;
    for (int i = 2; i <= N; i++) {
        while (l > 1 && (a[i] - a[p[l]]) * (p[l] - p[l - 1]) <= (a[p[l]] - a[p[l - 1]]) * (i - p[l])) l--;
        p[++l] = i;
    }
    for (int i = 2; i <= M; i++) {
        while (r > 1 && (b[i] - b[q[r]]) * (q[r] - q[r - 1]) <= (b[q[r]] - b[q[r - 1]]) * (i - q[r])) r--;
        q[++r] = i;
    }
    int x = 1, y = 1, s = 1, t = 1, ans = 0;
    while (x < N || y < M) {
        if (s == l || (t != r && (a[p[s + 1]] - a[p[s]]) * (q[t + 1] - q[t]) > (b[q[t + 1]] - b[q[t]]) * (p[s + 1] - p[s]))) 
            ans += (q[t + 1] - q[t]) * a[x], y = q[++t];
        else
            ans += (p[s + 1] - p[s]) * b[y], x = p[++s];
    }
    printf("%lld\n", ans);
    return 0;
}

Day1 T3 Misspelling

对于由小写字母组成的字符串 \(S\),定义字符串 \(T_i\)\(S\) 删去第 \(i\) 个字符并将前后字符串相接所得的字符串。给定 \(m\) 个条件,每个条件形如 \((s_i,t_i)\) 表示 \(T_{s_i}\) 的字典序不大于 \(T_{b_i}\)。求有多少个长度为 \(n\) 的字符串 \(S\) 满足所有条件。答案对 \(10^9 + 7\) 取模。

\(n,m \leq 5 \times 10^5\)

solution

通过模拟可以得到如下结论:令 \(t_i\)\(s_i\)\(s_{i+1}\) 的大小关系,那么限制 \((i,j)(i < j)\) 表示区间 \([i,j)\) 中第一个不等号是 \(>\),或没有不等号,\((i,j)(i<j)\) 表示第一个不等号是 \(<\),或没有不等号。考虑 DP,设 \(f_{i,j}\) 表示所有 \(l \leq i\) 的限制都被满足,当前位置为 \(<\)\(>\) 且以字符 \(j\) 结尾的方案数。转移枚举一段极长的 \(=\) 号,不难发现合法的转移点一定是一段后缀,这个可以用 multiset 求,然后就可以直接前缀和优化,总时间复杂度 \(O(n \sum + m \log m)\)

code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
#define pii pair<int, int>
#define mp(x, y) make_pair(x, y)
#define pb push_back
#define eb emplace_back
#define fi first
#define se second
#define int long long
#define mem(x, v) memset(x, v, sizeof(x))
#define mcpy(x, y, n) memcpy(x, y, sizeof(int) * (n))
#define lob lower_bound
#define upb upper_bound
#define TIME 1e3 * clock() / CLOCKS_PER_SEC
using namespace std;

inline int read() {
	int x = 0, w = 1;char ch = getchar();
	while (ch > '9' || ch < '0') { if (ch == '-')w = -1;ch = getchar(); }
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
	return x * w;
}

const int MN = 5e5 + 5;
const int Mod = 1e9 + 7;

inline int min(int x, int y) { return x < y ? x : y; }
inline int max(int x, int y) { return x > y ? x : y; }

inline int qPow(int a, int b = Mod - 2, int ret = 1) {
    while (b) {
        if (b & 1) ret = ret * a % Mod;
        a = a * a % Mod, b >>= 1;
    }
    return ret;
}

#define dbg

int N, M, f[MN][26], up[MN][26], dn[MN][26];
vector <int> v1[MN], v2[MN];
multiset <int> s1, s2;

inline void inc(int &x, int y) { x += y; if (x >= Mod) x -= Mod; }
inline void dec(int &x, int y) { x -= y; if (x < 0) x += Mod; }

signed main(void) {
    N = read(), M = read();
    for (int i = 1; i <= M; i++) {
        int x = read(), y = read();
        if (x < y) v2[x + 1].pb(x), v2[y + 1].pb(-x);
        else v1[y + 1].pb(y), v1[x + 1].pb(-y);
    }
    int ans = 26;
    for (int i = 2; i <= N; i++) {
        for (int x : v1[i]) x > 0 ? s1.insert(x) : s1.erase(s1.find(-x));   
        for (int x : v2[i]) x > 0 ? s2.insert(x) : s2.erase(s2.find(-x));
        int l1 = 2, l2 = 2;
        if (!s1.empty()) l1 = *s1.rbegin() + 1;
        if (!s2.empty()) l2 = *s2.rbegin() + 1;
        if (l1 < i) for (int j = 0; j <= 24; j++) 
            inc(f[i][j], dn[i - 1][j + 1]), dec(f[i][j], dn[l1 - 1][j + 1]);
        if (l2 < i) for (int j = 1; j <= 25; j++) 
            inc(f[i][j], up[i - 1][j - 1]), dec(f[i][j], up[l2 - 1][j - 1]);
        if (s1.empty()) for (int j = 0; j <= 25; j++) inc(f[i][j], 25 - j);
        if (s2.empty()) for (int j = 0; j <= 25; j++) inc(f[i][j], j);
        for (int j = 0; j <= 25; j++) 
            inc(ans, f[i][j]);
        up[i][0] = f[i][0], dn[i][25] = f[i][25];
        for (int j = 1; j <= 25; j++) up[i][j] = (up[i][j - 1] + f[i][j]) % Mod;
        for (int j = 24; j >= 0; j--) dn[i][j] = (dn[i][j + 1] + f[i][j]) % Mod;
        for (int j = 0; j <= 25; j++) 
            inc(up[i][j], up[i - 1][j]), inc(dn[i][j], dn[i - 1][j]);  
    }
    printf("%lld\n", ans);
    return 0;
}

Day2 T1 Copy and Paste 3

在一个编辑器中,可以执行如下几种操作来输入某个字符串,设 \(X\) 为屏幕上的字符串,\(Y\) 为剪切板中的字符串,初始均为空串:

  • 操作 \(1\):花费 \(A\) 的代价输入字符 \(c\),即将 \(X\) 更新为 \(X+c\)
  • 操作 \(2\):花费 \(B\) 的代价选择所有字符并剪切,即将 \(Y\) 更新为 \(X\),并将 \(X\) 置为空串。
  • 操作 \(3\):花费 \(C\) 的代价将剪切板中的字符串粘贴到当前字符串末尾,即将 \(X\) 更新为 \(X+Y\)

给定一个长度为 \(n\) 的字符串 \(S\),求输入该字符串的最小代价。

\(n \leq 2500\)\(A,B,C \leq 10^9\)

solution

不妨先观察一下操作的结构,由于我们每次操作 \(2\) 都会把 \(X\) 置为空串,这就意味着原串中至多只会有一种子串是由操作 \(3\) 得到的,其余字符全都由操作 \(1\) 得到。更具体地,我们的操作大概是这样子的:选择 \(S\) 的一个出现次数较多次的子串 \(T\),先想办法凑出 \(T\),然后用一次操作 \(2\)\(Y\) 置为 \(T\),最后用若干次操作 \(1\)\(3\) 得到 \(S\),这样问题就变成了关于 \(T\) 的一个子问题。

这启发我们使用区间 DP 计算,设 \(f_{l,r}\) 表示区间 \([l,r]\) 的答案,转移有 \(2\) 种,使用操作 \(1\)\(f_{l,r} \gets \min(f_{l+1,r},f_{l,r-1}) + A\),或枚举 \([l,r]\) 的所有 border 计算当 \(T = S_{[l,p]}\) 时的答案,转移的正确性显然。

枚举 border 可以对于每个后缀预处理 \(\mathrm{nxt}\) 数组快速完成。剩下的问题在于快速求出区间 \([l,r]\) 中最多能选多少个不重复的 \([l,p]\),可以预处理出 \(g_{i,j,k}\) 表示和 \(S_{[i,i+j-1]}\) 相同的不重复的后面 \(2^k\) 个子串的最小左端点,倍增即可。

最后我们需要一个小优化:对于相同的子串只计算一次,这可以用字符串哈希 + 哈希表完成记忆化(这里如果换成 map 会被卡常)。毛估估一下复杂度,操作 \(1\) 的转移总复杂度为 \(O(n^2)\),对于第 \(2\) 种转移,其执行次数为所有本质不同子串的 border 数量之和。显然当所有子串都本质不同时这个数量为 \(O(n^2)\),而每有一个 border 产生就会减少一个本质不同子串,所以总执行次数仍是 \(O(n^2)\) 的,因此这部分复杂度为 \(O(n^2 \log n)\)。综上可知总时间复杂度为 \(O(n^2 \log n)\)

code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
#define pii pair<int, int>
#define mp(x, y) make_pair(x, y)
#define pb push_back
#define eb emplace_back
#define fi first
#define se second
#define int long long
#define mem(x, v) memset(x, v, sizeof(x))
#define mcpy(x, y, n) memcpy(x, y, sizeof(int) * (n))
#define lob lower_bound
#define upb upper_bound
#define TIME 1e3 * clock() / CLOCKS_PER_SEC
using namespace std;

inline int read() {
	int x = 0, w = 1;char ch = getchar();
	while (ch > '9' || ch < '0') { if (ch == '-')w = -1;ch = getchar(); }
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
	return x * w;
}

const int MN = 2.5e3 + 5;
const int MM = 19260817;
const int base = 13331;
const int Mod = 1e9 + 7;

inline int min(int x, int y) { return x < y ? x : y; }
inline int max(int x, int y) { return x > y ? x : y; }

inline int qPow(int a, int b = Mod - 2, int ret = 1) {
    while (b) {
        if (b & 1) ret = ret * a % Mod;
        a = a * a % Mod, b >>= 1;
    }
    return ret;
}

#define dbg

typedef unsigned long long ull;
#define Pr pair <ull, int>

int N, A, B, C, nxt[MN][MN], f[MN][MN], to[12][MN][MN]; char s[MN];
ull h[MN], pw[MN]; 
ull ky[MM + 500]; int g[MM + 500];
pair <ull, int> vr[MN]; int bak;

#define g(l, r) (h[r] - h[l - 1] * pw[r - (l) + 1])

signed main(void) {
    N = read();
    scanf("%s", s + 1);
    for (int l = 1; l <= N; l++) {
        int j = 0;
        for (int r = l + 1; r <= N; r++) {
            while (j && s[r] != s[l + j]) j = nxt[l][l + j - 1];
            if (s[r] == s[l + j]) j++; 
            nxt[l][r] = j;
        }
    }
    pw[0] = 1;
    for (int i = 1; i <= N; i++) 
        h[i] = h[i - 1] * base + s[i], pw[i] = pw[i - 1] * base; 
    for (int i = 1; i <= N; i++) {
        bak = 0;
        for (int j = 1; j <= N - i + 1; j++) vr[++bak] = mp(g(j, j + i - 1), j);
        sort(vr + 1, vr + bak + 1, [&](Pr x, Pr y){ return x.fi != y.fi ? x.fi < y.fi : x.se > y.se; });
        int k = 1;
        for (int j = 1; j <= bak; j++) {
            while (k < j && vr[k].fi != vr[j].fi) k++;
            while (k < j && vr[j].se + i <= vr[k + 1].se) k++;
            if (k < j && vr[j].se + i <= vr[k].se) to[0][vr[j].se][i] = vr[k].se;
        }
    }  
    for (int k = 1; k <= 11; k++)
        for (int j = 1; j <= N; j++)
            for (int i = 1; i <= N - j + 1; i++) 
                to[k][i][j] = to[k - 1][to[k - 1][i][j]][j];
    A = read(), B = read(), C = read();
    for (int r = 1; r <= N; r++) for (int l = r; l >= 1; l--) {
        if (l == r) { f[l][r] = A; continue; }
        ull v = g(l, r), pv = v % MM;
        while (ky[pv] && ky[pv] != v) pv++;
        if (ky[pv]) { 
            f[l][r] = g[pv]; continue; 
        }
        ky[pv] = v;
        int cur = min(f[l + 1][r], f[l][r - 1]) + A;
        int t = (r - l + 1) / 2, k = nxt[l][r];
        while (k && k > t) k = nxt[l][l + k - 1];
        for (; k; k = nxt[l][l + k - 1]) {
            int cnt = r - l + 1, sum = 2, p = l;
            for (int i = 11; i >= 0; i--) 
                if (to[i][p][k] && to[i][p][k] + k - 1 <= r - k) 
                    p = to[i][p][k], sum += 1 << i;
            cnt -= sum * k;
            cur = min(cur, f[l][l + k - 1] + B + sum * C + cnt * A);
        }
        f[l][r] = g[pv] = cur;
    }
    printf("%lld\n", f[1][N]);
    return 0;
}

Day2 T3 Team Contest

给定 \(n\) 个三元组 \((x_i,y_i,z_i)\),称三元组的三元组 \((a,b,c)\) 合法当且仅当 \(a_1 > b_1,c_1\)\(b_2 > a_2,c_2\)\(c_3 > a_3,b_3\)。求所有合法三元组 \((a,b,c)\)\(a_1+b_2+c_3\) 的最大值。

\(n \leq 1.5 \times 10^5\)\(x_i,y_i,z_i \leq 10^8\)

solution

肯定先对 \(c\) 从小到大排序,然后就发现我不会维护了。所以说思维还是不要僵化了,首先肯定考虑都选最大值,如果它们对应的三元组互不相同那就做完了,否则如果有某个三元组占了多个最大值就把它删掉,容易做到 \(O(n \log n)\)

code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
#define pii pair<int, int>
#define mp(x, y) make_pair(x, y)
#define pb push_back
#define eb emplace_back
#define fi first
#define se second
#define int long long
#define mem(x, v) memset(x, v, sizeof(x))
#define mcpy(x, y, n) memcpy(x, y, sizeof(int) * (n))
#define lob lower_bound
#define upb upper_bound
#define TIME 1e3 * clock() / CLOCKS_PER_SEC
using namespace std;

inline int read() {
	int x = 0, w = 1;char ch = getchar();
	while (ch > '9' || ch < '0') { if (ch == '-')w = -1;ch = getchar(); }
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
	return x * w;
}

const int MN = 2e5 + 5;
const int Mod = 1e9 + 7;

inline int min(int x, int y) { return x < y ? x : y; }
inline int max(int x, int y) { return x > y ? x : y; }

inline int qPow(int a, int b = Mod - 2, int ret = 1) {
    while (b) {
        if (b & 1) ret = ret * a % Mod;
        a = a * a % Mod, b >>= 1;
    }
    return ret;
}

#define dbg

int N, A, B, C, ans = -1;
bool era[MN];

struct node {
    int x, id;
    node(int _x = 0, int _id = 0) : x(_x), id(_id) {}
    inline bool operator < (const node &p) const {
        return x == p.x ? id < p.id : x < p.x;
    }
} a[MN], b[MN], c[MN];

struct dat {
    int a, b, c;
} p[MN];

inline int max(int x, int y, int z) {
    return max(x, max(y, z));
}
inline bool chk() {
    int i = a[A].id, j = b[B].id, k = c[C].id;
    if (i == j || j == k) return era[j] = 1;
    if (i == k) return era[k] = 1;
    int mA = max(p[i].a, p[j].a, p[k].a), mB = max(p[i].b, p[j].b, p[k].b), mC = max(p[i].c, p[j].c, p[k].c);
    if (((p[i].a == mA) + (p[i].b == mB) + (p[i].c == mC)) > 1) return era[i] = 1;
    if (((p[j].a == mA) + (p[j].b == mB) + (p[j].c == mC)) > 1) return era[j] = 1;
    if (((p[k].a == mA) + (p[k].b == mB) + (p[k].c == mC)) > 1) return era[k] = 1;
    return 0;
}

signed main(void) {
    N = read();
    for (int i = 1; i <= N; i++) {
        p[i].a = read(), a[i] = node(p[i].a, i);
        p[i].b = read(), b[i] = node(p[i].b, i);
        p[i].c = read(), c[i] = node(p[i].c, i);
    }
    sort(a + 1, a + N + 1);
    sort(b + 1, b + N + 1);
    sort(c + 1, c + N + 1);
    A = B = C = N;
    while (chk()) {
        while (era[a[A].id]) A--;
        while (era[b[B].id]) B--;
        while (era[c[C].id]) C--;
        if (A == 0 || B == 0 || C == 0) break;
    }
    if (A && B && C) ans = a[A].x + b[B].x + c[C].x;
    printf("%lld\n", ans);
    return 0;
}

Day3 T1 Sprinkler

给定一棵 \(n\) 个点的树,\(i\) 号点有点权 \(w_i\)。支持两种操作:对于到点 \(x\) 距离 \(\leq y\) 的所有点 \(i\) 执行 \(w_i \gets (w_i \times z \ \text{mod} \ L)\),或查询 \(w_x\) 的值。

\(n \leq 2 \times 10^5\)\(q \leq 4 \times 10^5\)\(w_i < L \leq 10^9\)\(y \leq 40\)

solution

把修改拆成关于最多 \(40\) 个祖先的修改,每个修改形如对所有子树内距离为 \(d(d \leq 40)\) 的节点 \(i\) 执行 \(w_i \gets (w_i \times z \ \text{mod} \ L)\),打个标记就行了。查询把最多 \(40\) 个祖先的标记乘起来,然后就做完了。时间复杂度 \(O(nd)\)

code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
#define pii pair<int, int>
#define mp(x, y) make_pair(x, y)
#define pb push_back
#define eb emplace_back
#define fi first
#define se second
#define int long long
#define mem(x, v) memset(x, v, sizeof(x))
#define mcpy(x, y, n) memcpy(x, y, sizeof(int) * (n))
#define lob lower_bound
#define upb upper_bound
#define TIME 1e3 * clock() / CLOCKS_PER_SEC
using namespace std;

inline int read() {
	int x = 0, w = 1;char ch = getchar();
	while (ch > '9' || ch < '0') { if (ch == '-')w = -1;ch = getchar(); }
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
	return x * w;
}

const int MN = 4e5 + 5;
const int Mod = 1e9 + 7;

inline int min(int x, int y) { return x < y ? x : y; }
inline int max(int x, int y) { return x > y ? x : y; }

inline int qPow(int a, int b = Mod - 2, int ret = 1) {
    while (b) {
        if (b & 1) ret = ret * a % Mod;
        a = a * a % Mod, b >>= 1;
    }
    return ret;
}

#define dbg

int N, Q, L, w[MN], tg[MN][41], fa[MN];
vector <int> e[MN];

inline void DFS(int u, int pr) {
    for (int v : e[u]) 
        if (v != pr) fa[v] = u, DFS(v, u);
}

signed main(void) {
    N = read(), L = read();
    for (int i = 1; i < N; i++) {
        int x = read(), y = read();
        e[x].pb(y), e[y].pb(x);
    }
    DFS(1, 0);
    for (int i = 1; i <= N; i++) w[i] = read();
    for (int i = 1; i <= N; i++)
        for (int j = 0; j <= 40; j++) tg[i][j] = 1;
    Q = read();
    while (Q--) {
        int op = read(), x = read(), y, z;
        if (op == 1) {
            y = read(), z = read();
            int r = x, d = y;
            while (r && d >= 0) {
                if (fa[r] && d > 1) tg[r][d] = tg[r][d] * z % L, tg[r][d - 1] = tg[r][d - 1] * z % L;
                else for (int i = 0; i <= d; i++) tg[r][i] = tg[r][i] * z % L;
                r = fa[r], d--;
            }
        } else {
            int r = x, d = 40, coef = 1;
            while (r && d >= 0) {
                coef = coef * tg[r][40 - d] % L;
                r = fa[r], d--; 
            }
            printf("%lld\n", w[x] * coef % L);
        }
    }
    return 0;
}

Day3 T3 Ants and Sugar

数轴上初始为空,有 \(q\) 个操作,每次操作会在数轴上整点 \(x\) 处放置 \(a\) 个红或蓝点。

初始给出一个整数 \(L\),意义是:距离不超过 \(L\) 的一对红点与蓝点之间会连一条无向边。你需要在每次操作后求出这张图的最大匹配数。

\(q \leq 5 \times 10^5\)\(L,a_i,x_i \leq 10^9\)

solution

根据 Hall 定理的推论,所求即 \(\min \limits_{S \subseteq V_L} (|V_L| -|S| + |\bigcup \limits_{v \in S} N(v)|)\)

考虑取到最小值的 \(S\),我们尝试分析其性质。

性质 \(1\):同一个位置上的蚂蚁要么全选,要么全不选。

证明:相对于部分选,全选时 \(|\bigcup \limits_{v \in S} N(v)|\) 不变而 \(|S|\) 增加,显然更优。

将蚂蚁的位置从左到右依次编号为 \(1,\cdots,m\),记 \(x_i\) 为其坐标,\(a_i\) 为蚂蚁数。根据性质 \(1\)\(S\) 可以用 \([m]\) 的子集来描述,其对应的值为 \(\sum a_i - \sum_{i \in S} a_i + |\bigcup \limits_{v \in S} N(v)|\)

性质 \(2\):记 \(\text{next}_S(i)\)\(S\)\(i\) 的后继,则 \(|\bigcup \limits_{v \in S} N(v)| = \sum_{i \in S} (|N(i)| - |N(i) \cap N(\text{next}_S(i)|)\)

证明:由于对于 \(x \in V_L\),其邻居所形成的的区间 \([L_i,R_i]\) 左右端点单调不降,因此 \(\forall x \in V_R\),满足 \(x \in N(i)\)\(i\) 构成一个区间。那么每一个 \(x \in \bigcup \limits_{v \in S} N(v)\) 恰会在该区间与 \(S\) 的交的最后一项中贡献,因此结论成立。

性质 \(3\):若 \(l,r \in S\)\(x_r - x_l \leq 2L\),则 \(\forall i \in [l,r],i \in S\)

证明:若 \(x_r - x_l \leq 2L\),则 \(\forall i \in [l,r],N(i) \subseteq N(l) \cup N(r)\),因此对于 \(i \in [l,r]\),选择后 \(|\bigcup \limits_{v \in S} N(v)|\) 不变而 \(|S|\) 增加,显然更优。

结合性质 \(2\)\(3\),当 \(N(i) \cup N(\text{next}_S(i)) \neq \varnothing\),即 \(x_{\text{next}_S(i)} - x_i \leq 2L\)\(i + 1 \in S\),由此可知 \(\text{next}_S(i) = i + 1\)。综上,所求即为

\[\sum_{i=1}^m a_i + \min_{S \subseteq [m]} \left( \sum_{i \in S}(|N(i) - a_i|) - \sum_{i,i+1 \in S} |N(i) \cap N(i+1)|\right) \]

线段树维护 \(|N(\text{mid}) \cap N(\text{mid} +1)|\) 和区间左右端点是否选择的答案即可。加入蚂蚁时单点修改,加入方糖时区间修改,容易发现区间修改对选择 \(k\) 个区间的方案的影响是 \(+kx\)。但注意到 \(x_r - x_l \leq 2L\),结合性质 \(3\) 可知 \(k \in \{0,1\}\),特判一下不选的情况即可。

总时间复杂度 \(O(n \log n)\),具体细节详见代码。

code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
#define pii pair<int, int>
#define mp(x, y) make_pair(x, y)
#define pb push_back
#define eb emplace_back
#define fi first
#define se second
#define int long long
#define mem(x, v) memset(x, v, sizeof(x))
#define mcpy(x, y, n) memcpy(x, y, sizeof(int) * (n))
#define lob lower_bound
#define upb upper_bound
#define TIME 1e3 * clock() / CLOCKS_PER_SEC
using namespace std;

inline int read() {
	int x = 0, w = 1;char ch = getchar();
	while (ch > '9' || ch < '0') { if (ch == '-')w = -1;ch = getchar(); }
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
	return x * w;
}

const int MN = 5e5 + 5;
const int Mod = 1e9 + 7;
const int inf = 1e18;

inline int min(int x, int y) { return x < y ? x : y; }
inline int max(int x, int y) { return x > y ? x : y; }

inline int qPow(int a, int b = Mod - 2, int ret = 1) {
    while (b) {
        if (b & 1) ret = ret * a % Mod;
        a = a * a % Mod, b >>= 1;
    }
    return ret;
}

#define dbg

int N, M, L, op[MN], x[MN], a[MN], vr[MN], sum;

const int MS = MN << 2;
#define ls o << 1
#define rs o << 1 | 1
#define mid ((l + r) >> 1)
#define LS ls, l, mid
#define RS rs, mid + 1, r
struct Dat {
    int a[4];
} tr[MS];
int tg[MS], val[MS];
inline void upd(int o, int v) {
    tg[o] += v, val[o] += v;
    for (int i = 0; i < 4; i++) tr[o].a[i] += v;
    tr[o].a[0] = min(tr[o].a[0], 0);
}
inline void up(int o) {
    for (int i = 0; i < 4; i++) tr[o].a[i] = inf;
    for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++) {
            int S = (i & 2 | j & 1), p = (i & (j >> 1) & 1);
            tr[o].a[S] = min(tr[o].a[S], tr[ls].a[i] + tr[rs].a[j] - p * val[o]);
        }
}
inline void down(int o) {
    if (tg[o]) upd(ls, tg[o]), upd(rs, tg[o]), tg[o] = 0;
}
inline void build(int o, int l, int r) {
    if (l == r) return tr[o].a[1] = tr[o].a[2] = inf, void();
    build(LS), build(RS), up(o);
}
inline void upd(int o, int l, int r, int p, int v) {
    if (l == r) return tr[o].a[3] += v, void();
    down(o), (p <= mid ? upd(LS, p, v) : upd(RS, p, v)), up(o);
}
inline void upd(int o, int l, int r, int L, int R, int v) {
    if (r < L || l > R) return;
    if (L <= l && R >= r) return upd(o, v);
    down(o), upd(LS, L, R, v), upd(RS, L, R, v);
    if (L <= mid && R > mid) val[o] += v;
    up(o);
}

signed main(void) {
    N = read(), L = read();
    for (int i = 1; i <= N; i++) {
        op[i] = read(), x[i] = read(), a[i] = read();
        if (op[i] == 1) vr[++M] = x[i];
    }
    if (!M) {
        for (int i = 1; i <= N; i++) puts("0");
        return 0;
    }
    sort(vr + 1, vr + M + 1);
    build(1, 1, M);
    for (int i = 1; i <= N; i++) {
        if (op[i] == 1) {
            int p = lob(vr + 1, vr + M + 1, x[i]) - vr;
            sum += a[i], upd(1, 1, M, p, -a[i]);
        } else {
            int pl = lob(vr + 1, vr + M + 1, x[i] - L) - vr;
            int pr = upb(vr + 1, vr + M + 1, x[i] + L) - vr - 1;
            upd(1, 1, M, pl, pr, a[i]);
        }
        int ans = min(min(tr[1].a[0], tr[1].a[1]), min(tr[1].a[2], tr[1].a[3]));
        printf("%lld\n", sum + ans);
    }
    return 0;
}

Day4 T2 Fish 2

给定一个长为 \(n\) 的序列,每个数可以吞并相邻的不大于它的数,变成两数之和。\(q\) 次操作,支持单点修改和查询一个区间内有多少个数可以吞下整个区间。

\(n,q \leq 10^5\)\(a_i \leq 10^9\),时限 \(\text{4.0s}\)

solution

首先显然对于能吃掉的极长区间相等的元素,它们是等价的。

考虑用线段树维护如下信息:对每个前 / 后缀维护前 / 后缀和,有多少元素在线段树节点内能吃的极长区间恰好为这一前 / 后缀,以及当前线段树区间内的答案。注意到若某个位置成为极长区间端点,这意味这前缀和在这个位置至少翻倍了,因此这样的极长区间不会超过 \(O(\log A)\) 个。

于是我们就可以暴力存这些前 / 后缀的信息,合并的时候用双指针求一下答案即可,时间复杂度 \(O(n \log n \log A)\)。细节有点多,具体详见代码。

code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
#define pii pair<int, int>
#define mp(x, y) make_pair(x, y)
#define pb push_back
#define eb emplace_back
#define fi first
#define se second
#define int long long
#define mem(x, v) memset(x, v, sizeof(x))
#define mcpy(x, y, n) memcpy(x, y, sizeof(int) * (n))
#define lob lower_bound
#define upb upper_bound
#define TIME 1e3 * clock() / CLOCKS_PER_SEC
using namespace std;

inline int read() {
	int x = 0, w = 1;char ch = getchar();
	while (ch > '9' || ch < '0') { if (ch == '-')w = -1;ch = getchar(); }
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
	return x * w;
}

const int MN = 5e5 + 5;
const int Mod = 1e9 + 7;
const int inf = 1e18;

inline int min(int x, int y) { return x < y ? x : y; }
inline int max(int x, int y) { return x > y ? x : y; }

inline int qPow(int a, int b = Mod - 2, int ret = 1) {
    while (b) {
        if (b & 1) ret = ret * a % Mod;
        a = a * a % Mod, b >>= 1;
    }
    return ret;
}

#define dbg

int N, M, a[MN];

struct dat {
    int s, p, v, c;
};
struct node {
    int sum, ans;
    vector <dat> L, R;
    friend node operator + (node u, node v) {
        node w;
        w.sum = u.sum + v.sum, w.L = u.L, w.R = v.R, w.ans = 0;
        bool isL = 1, isR = 1;
        for (int i = 0; i < v.L.size(); i++) {
            dat x = v.L[i];
            if (x.v * 2 > u.sum + x.s) {
                w.L.pb((dat){ u.sum + x.s, x.p, x.v, isL * u.ans });
                isL = 0;
            }
        }
        for (int i = 0; i < u.R.size(); i++) {
            dat x = u.R[i];
            if (x.v * 2 > v.sum + x.s) {
                w.R.pb((dat){ v.sum + x.s, x.p, x.v, isR * v.ans });
                isR = 0;
            }
        }
        w.ans += isL * u.ans + isR * v.ans;
        u.R.pb((dat){ u.sum, 0, 0, 0 });
        v.L.pb((dat){ v.sum, 0, 0, 0 });
        int i = 0, j = 0, lst = 0, cur = 0, x = v.R.size(), y = u.L.size();
        while (i < u.R.size()) {
            while (j < v.L.size() - 1 && v.L[j].v <= u.R[i].s + v.L[j].s - u.R[i].v - v.L[j].v) j++;
            lst = i;
            while (i < u.R.size() - 1 && u.R[i].v <= u.R[i].s + v.L[j].s - u.R[i].v - v.L[j].v)
                cur += u.R[++i].c;
            if (i == lst) {
                if (i == u.R.size() - 1 && j == v.L.size() - 1)
                    w.ans += cur;
                else if (i == u.R.size() - 1) {
                    while (w.L[y].p < v.L[j].p) y++;
                    w.L[y].c += cur;
                } else if (j == v.L.size() - 1) {
                    while (w.R[x].p > u.R[i].p) x++;
                    w.R[x].c += cur;
                }
                i++;
                if (i <= u.R.size() - 1)
                    cur = u.R[i].c;
            }
        }
        i = j = cur = 0, x = v.R.size(), y = u.L.size();
        while (j < v.L.size()) {
            while (i < u.R.size() - 1 && u.R[i].v <= u.R[i].s + v.L[j].s - u.R[i].v - v.L[j].v) i++;
            lst = j;
            while (j < v.L.size() - 1 && v.L[j].v <= u.R[i].s + v.L[j].s - u.R[i].v - v.L[j].v)
                cur += v.L[++j].c;
            if (j == lst) {
                if (i == u.R.size() - 1 && j == v.L.size() - 1)
                    w.ans += cur;
                else if (i == u.R.size() - 1) {
                    while (w.L[y].p < v.L[j].p) y++;
                    w.L[y].c += cur;
                } else if (j == v.L.size() - 1) {
                    while (w.R[x].p > u.R[i].p) x++;
                    w.R[x].c += cur;
                }
                j++;
                if (j <= v.L.size() - 1)
                    cur = v.L[j].c;
            }
        }
        return w;
    } 
};

const int MS = MN << 2;
#define ls o << 1
#define rs o << 1 | 1
#define mid ((l + r) >> 1)
#define LS ls, l, mid
#define RS rs, mid + 1, r
struct SGT {
    node tr[MS];
    inline void up(int o) {
        tr[o] = tr[ls] + tr[rs];
    }
    inline void build(int o, int l, int r) {
        tr[o].sum = tr[o].ans = 0;
        tr[o].L.clear(), tr[o].R.clear();
        if (l == r) {
            tr[o].sum = a[l], tr[o].ans = 1;
            tr[o].L.pb((dat){ a[l], l, a[l], 0 });
            tr[o].R.pb((dat){ a[l], l, a[l], 0 });
            return;
        }
        build(LS), build(RS), up(o);
    }
    inline void upd(int o, int l, int r, int p, int v) {
        if (l == r) {
            tr[o].sum = v, tr[o].ans = 1;
            tr[o].L.clear(), tr[o].R.clear();
            tr[o].L.pb((dat){ v, l, v, 0 });
            tr[o].R.pb((dat){ v, l, v, 0 });
            return;
        }
        (p <= mid ? upd(LS, p, v) : upd(RS, p, v)), up(o);
    }
    node qry(int o, int l, int r, int L, int R) {
        if (L <= l && R >= r) return tr[o];
        if (R <= mid) return qry(LS, L, R);
        if (L > mid) return qry(RS, L, R);
        return qry(LS, L, R) + qry(RS, L, R);
    }
} sgt;

signed main(void) {
    N = read();
    for (int i = 1; i <= N; i++) a[i] = read();
    sgt.build(1, 1, N);
    M = read();
    while (M--) {
        int op = read(), x = read(), y = read();
        if (op == 1) sgt.upd(1, 1, N, x, y);
        else printf("%lld\n", sgt.qry(1, 1, N, x, y).ans);
    }
    return 0;
}

Day4 T3 Reconstruction Project

给定 \(n\) 个点 \(m\) 条边带权无向图,\(q\) 次询问,每次给定 \(X_j\),求所有边权变为 \(|X_j−w_i|\) 后最小生成树边权和。

\(n \leq 500\)\(m \leq 10^5\)\(q \leq 10^7\)\(X_j \leq 10^9\),时限 \(\text{5.0s}\)

solution

将所有边按照边权排序,边权随 \(X\) 的变化是一个分段一次函数的形式。求出前 / 后缀最小生成树,那么容易发现对于一次询问 \(X\) 相当于合并前后两颗最小生成树。直接做时间复杂度为 \(O(n(q+m))\),主要问题是 \(q\) 太大了。

不妨考虑每条边会在哪些询问对应的生成树中出现。首先显然每个询问对应的生成树上的边一定是一个区间,并且这些区间的左右端点单调不降,由此可知每条边会在一个区间内的询问中出现。如果我们能够求出这些区间,那么就可以直接维护差分对区间做等差数列加,每次询问查询前缀和就做完了。

现在的问题是如何求出这些区间。那这其实很简单啊,因为 \(n\) 很小,直接暴力每次加一条边删一条边,时间复杂度 \(O(nm+q)\)。也可以使用 LCT 做到 \(O(m(\log m + \log n) + q)\)

code
/*
最黯淡的一个 梦最为炽热
万千孤单焰火 让这虚构灵魂鲜活
至少在这一刻 热爱不问为何
存在为将心声响彻
*/
#include <bits/stdc++.h>
#define pii pair<int, int>
#define mp(x, y) make_pair(x, y)
#define pb push_back
#define eb emplace_back
#define fi first
#define se second
#define int long long
#define mem(x, v) memset(x, v, sizeof(x))
#define mcpy(x, y, n) memcpy(x, y, sizeof(int) * (n))
#define lob lower_bound
#define upb upper_bound
#define TIME 1e3 * clock() / CLOCKS_PER_SEC
using namespace std;

inline int read() {
	int x = 0, w = 1;char ch = getchar();
	while (ch > '9' || ch < '0') { if (ch == '-')w = -1;ch = getchar(); }
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
	return x * w;
}

const int MN = 1e5 + 5;
const int Mod = 1e9 + 7;
const int inf = 1e18;

inline int min(int x, int y) { return x < y ? x : y; }
inline int max(int x, int y) { return x > y ? x : y; }

inline int qPow(int a, int b = Mod - 2, int ret = 1) {
    while (b) {
        if (b & 1) ret = ret * a % Mod;
        a = a * a % Mod, b >>= 1;
    }
    return ret;
}

#define dbg

struct edge {
    int u, v, w;
    inline bool operator < (const edge &p) const {
        return w < p.w;
    }
} e[MN], vr[MN << 2];

int N, M, Q, bak, fa[MN], L[MN], R[MN];
vector <int> s, t;

inline int find(int x) {
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}
inline void merge(int x, int y) {
    x = find(x), y = find(y);
    if (x == y) return;
    fa[x] = y;
}

signed main(void) {
    N = read(), M = read();
    for (int i = 1; i <= M; i++) e[i].u = read(), e[i].v = read(), e[i].w = read();
    sort(e + 1, e + M + 1);
    for (int i = 1; i <= M; i++) {
        for (int j = 1; j <= N; j++) fa[j] = j;
        merge(e[i].u, e[i].v);
        R[i] = inf;
        t.clear(), t.pb(i);
        for (int x : s) {
            if (find(e[x].u) != find(e[x].v)) merge(e[x].u, e[x].v), t.pb(x);
            else R[x] = L[i] = (e[i].w + e[x].w + 1) >> 1;
        }
        s = t;
    }
    for (int i = 1; i <= M; i++) {
        vr[++bak] = (edge){ -1, e[i].w, L[i]};
        vr[++bak] = (edge){ 2, -2 * e[i].w, e[i].w};
        vr[++bak] = (edge){ -1, e[i].w, R[i]};
    }
    sort(vr + 1, vr + bak + 1);
    Q = read();
    int k = 0, b = 0;
    for (int i = 1, j = 1; i <= Q; i++) {
        int x = read();
        while (j <= bak && vr[j].w <= x) k += vr[j].u, b += vr[j].v, j++;
        printf("%lld\n", k * x + b);
    }
    return 0;
}
posted @ 2022-09-08 13:18  came11ia  阅读(166)  评论(0编辑  收藏  举报