Loading

【总结】JOISC2022

【总结】JOISC2022

Day1

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

分析一下,对于一个人的操作一定是连续的。如果我们先移动 \(x\),再移动 \(y\),然后移动 \(x\),说明 \(y\) 挡住了 \(x\),而 \(x\) 一定不会挡住 \(y\)。那么我们先移动 \(y\),再移动 \(x\) 一定更优。

所以我们要求一个顺序 \(p\),使得按顺序依次将每个人移动到终点。那么对于两个人 \(i,j\),如果 \(s_i\)\(j\) 的路径上,则 \(i\) 必须在 \(j\) 前面,如果 \(t_i\)\(j\) 的路径上,则 \(i\) 必须在 \(j\) 后面。

经典拓扑序建图,倍增优化建图即可,时空复杂度 \(\mathcal{O}((n + m)\log n)\)

#define N 120005
#define M 4400005
int n, m, s[N], t[N], d[N], in[M], f[N][17], u[N][17], v[N][17], idx, T, dfn[N], tot, sz[N]; 
vector<int>e[N], w[M]; queue<int>q;
void dfs(int x,int fa){
	d[x] = d[f[x][0] = fa] + 1, dfn[x] = ++tot, sz[x] = 1;
	u[x][0] = ++idx, v[x][0] = ++idx;
	rp(i, T){
		f[x][i] = f[f[x][i - 1]][i - 1];
		u[x][i] = ++idx;
		w[u[x][i - 1]].pb(idx); if(f[x][i - 1])w[u[f[x][i - 1]][i - 1]].pb(idx);
		v[x][i] = ++idx;
		w[idx].pb(v[x][i - 1]); if(f[x][i - 1])w[idx].pb(v[f[x][i - 1]][i - 1]);
	}
	go(y, e[x])if(y != fa)dfs(y, x), sz[x] += sz[y];
}
void ins(int id,int x,int y){
	if(dfn[x] <= dfn[y] && dfn[x] + sz[x] > dfn[y]){
		pre(i, T, 0)if(d[f[y][i]] >= d[x])w[u[y][i]].pb(id), y = f[y][i];
	}
	else{
		x = f[x][0];
		if(d[x] < d[y])swap(x, y);
		pre(i, T, 0)if(d[f[x][i]] >= d[y])w[u[x][i]].pb(id), x = f[x][i];
		if(x == y)w[u[x][0]].pb(id);
		else{
			pre(i, T, 0)if(f[x][i] != f[y][i])
				w[u[x][i]].pb(id), w[u[y][i]].pb(id), x = f[x][i], y = f[y][i];
			w[u[x][0]].pb(id), w[u[y][1]].pb(id);
		}
	}
}
void ins_(int id,int x,int y){
	if(dfn[y] <= dfn[x] && dfn[y] + sz[y] > dfn[x]){
		pre(i, T, 0)if(d[f[x][i]] >= d[y])w[id].pb(v[x][i]), x = f[x][i];
	}
	else{
		y = f[y][0];
		if(d[x] < d[y])swap(x, y);
		pre(i, T, 0)if(d[f[x][i]] >= d[y])w[id].pb(v[x][i]), x = f[x][i];
		if(x == y)w[id].pb(v[x][0]);
		else{
			pre(i, T, 0)if(f[x][i] != f[y][i])
				w[id].pb(v[x][i]), w[id].pb(v[y][i]), x = f[x][i], y = f[y][i];
			w[id].pb(v[x][0]), w[id].pb(v[y][1]);
		}
	}
}
void solve(){
	read(n), T = log2(n), tot = 0;
	rp(i, n)e[i].clear();
	rp(i, idx)w[i].clear(), in[i] = 0;
	rp(i, n - 1){
		int x, y; read(x, y);
		e[x].pb(y), e[y].pb(x);
	}read(m), idx = m;
	dfs(1, 0);
	rp(i, m){
		int x, y; read(x, y);
		w[i].pb(u[x][0]), w[v[y][0]].pb(i);
		ins(i, x, y), ins_(i, x, y);
	}
	int sz = 0;
	rp(i, idx)go(x, w[i])in[x]++;
	rp(i, idx)if(!in[i])q.push(i);
	while(!q.empty()){
		int x = q.front(); q.pop(), sz += x <= m;
		go(y, w[x]){
			in[y]--; if(!in[y])q.push(y);
		}
	}if(sz == m)puts("Yes"); else puts("No");
}
int main() {
	int T; read(T);
	while(T--)solve();
	return 0;
}

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

对于 Sub1 直接 \(NM\) 跑 DP即可。

对于 Sub2,对于 \(a_i\),如果前后都有数 \(\le a_i\),那么 \(i\) 一定用不上。所以优化后图的规模是 \(2000\times 2000\),可以通过。

对于 Sub3,我们需要观察一些性质。

如果我们只经过一次转弯从 \((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\)

我们比较并化简一下可以得到 \(\dfrac{a_x - a_i}{x - i} >\dfrac{b_y - b_j}{y - j}\) 时选择第一条路更优,否则选择第二条路。

想必到这里已经很清楚了,我们的答案就是进行若干次这样的选择。而上面的式子显然时比较斜率,斜率越小越好。我们先对 \(a,b\) 求出下凸包,不在凸包上的一定存些斜率更小的两个点可以替代,然后合并两个凸包即可。

#define N 100005
int n, m, p[N], q[N], l, r; LL a[N], b[N];

int main() {
	read(n, m);
	rp(i, n)read(a[i]);
	rp(i, m)read(b[i]);
	p[l = 1] = q[r = 1] = 1;
	rep(i, 2, n){
		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;
	}
	rep(i, 2, m){
		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; LL 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;
}

T3:求有多少个长度为 \(N\) 的字符串满足 \(M\) 个条件,每个条件形如 \((i,j)\) 表示删除字符 \(s_i\) 后剩下串大于等于删除 \(s_j\) 后剩下的串。

观察一下,我们令 \(t_i\) 表示 \(s_i\)\(s_{i + 1}\) 的大小关系,那么限制 \((i,j) (i > j)\) 表示区间 \([i,j)\) 中的第一个不等号是 \(>\)\((i,j)(i < j)\) 表示第一个不等号是 \(<\)

设计 DP,用 \(f_{i,j}\) 表示前 \(i\) 个字符满足所有 \((l,r),l \le i\land i\le r\) 的限制条件,以 \(j\) 结尾的方案。前缀和优化转移即可,时间复杂度 \(\mathcal{O}(N|S|+M\log M)\)

#define N 500005
int n, m, f[N][26], up[N][26], dn[N][26];
vector<int>u[N], v[N];
multiset<int>s, t;
int main() {
	read(n, m);
	rp(i, m){
		int x, y;
		read(x, y);
		if(x < y)v[x + 1].pb(x), v[y + 1].pb(-x);
		else u[y + 1].pb(y), u[x + 1].pb(-y);
	}int ans = 26;
	rep(i, 2, n){
		go(x, u[i])
			if(x > 0)s.insert(x); else s.erase(s.find(-x));
		go(x, v[i])
			if(x > 0)t.insert(x); else t.erase(t.find(-x));
		int sl = 2, sr = 2;
		if(!s.empty())sl = *s.rbegin() + 1;
		if(!t.empty())sr = *t.rbegin() + 1;
			if(sl < i)rep(r, 0, 24)ad(f[i][r], dn[i - 1][r + 1]), su(f[i][r], dn[sl - 1][r + 1]);
			if(sr < i)rp(r, 25)ad(f[i][r], up[i - 1][r - 1]), su(f[i][r], up[sr - 1][r - 1]);
		if(s.empty())rep(r, 0, 25)ad(f[i][r], 25 - r);
		if(t.empty())rep(r, 0, 25)ad(f[i][r], r);
		rep(j, 0, 25)ad(ans, f[i][j]);
		up[i][0] = f[i][0]; rp(j, 25)up[i][j] = (up[i][j - 1] + f[i][j]) % P;
		dn[i][25] = f[i][25]; pre(j, 24, 0)dn[i][j] = (dn[i][j + 1] + f[i][j]) % P;
		rep(j, 0, 25)ad(up[i][j], up[i - 1][j]), ad(dn[i][j], dn[i - 1][j]);
	}cout << ans << endl;
	return 0;
}

Day2

T1:有三个操作,花费 \(A\) 的代价在当前串结尾加一个字符,花费 \(B\) 的代价将当前串剪切进剪切板(注意是剪切不是复制),花费 \(C\) 的代价在当前串结尾粘贴。

我们定义 \(f_{l,r}\) 表示区间 \([l,r]\) 的答案,直接按题意 DP 的复杂度是 \(\mathcal{O}(N^4)\) 的。

具体转移有两种,\(f_{l,r} = A + f_{l, r- 1}\),和枚举 \(k\),用 \(s_{[l,k]}\) 表示这次剪切的内容,然后贪心在 \(s_{[l,r]}\) 中删去最多个不重复的子串 \(s_{[l,k]}\)

用字符串哈希可以快速判断。为减少状态,我们添加一个转移 \(f_{l,r} = A + f_{l + 1,r}\)。然后进行剪切操作当且仅当 \(s_{[l,p]} = s_{[q, r]}\)。这样预处理 KMP,每次跳 next 指针可以保证一定是合法的剪切。对于两个相同的子串我们只用计算一次,每跳一次 next 必然存在两个相同的子串,所以最多转移 \(\mathcal{O}(N^2)\) 次。

问题在于快速求出区间 \([l,r]\) 中最多能选多少个不重复的 \([l,p]\),可以预处理 \(g_{l,r,k}\) 表示和 \(s_{[l,r]}\) 相同的不重复的后面 \(2^k\) 个子串左端点。倍增一下即可。时间复杂度 \(\mathcal{O}(N^2\log N)\)

#define N 2505
typedef unsigned long long ull;
int n, nxt[N][N], ste[12][N][N]; char s[N]; LL A, B, C, f[N][N]; ull h[N], pw[N];
#define g(l, r) (h[r] - h[l - 1] * pw[r - (l) + 1])
#define M 19989997
LL g[M + 500]; ull ky[M + 500];
pair<ull, int>b[N];
int main() {
	read(n);
	scanf("%s", s + 1);
	rp(l, n){
		int j = 0;
		rep(r, l + 1, n){
			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; rp(i, n)h[i] = h[i - 1] * R + s[i], pw[i] = pw[i - 1] * R;
	rp(i, n){
		int T = 0;
		rp(j, n - i + 1)b[++T] = mp(g(j, j + i - 1), j);
		sort(b + 1, b + T + 1, [](Pr x, Pr y){return x.fi != y.fi ? x.fi < y.fi : x.se > y.se;});
		int k = 1;
		rp(j, T){
			while(k < j && b[k].fi != b[j].fi)k++;
			while(k < j && b[j].se + i <= b[k + 1].se)k++;
			if(k < j && b[j].se + i <= b[k].se)ste[0][b[j].se][i] = b[k].se;
		}
	}
	rp(k, 11)rp(j, n)rp(i, n - j + 1)ste[k][i][j] = ste[k - 1][ste[k - 1][i][j]][j];
	
	read(A, B, C); int tem = 0;
	rp(r, n)pr(l, r){
		if(l == r){f[l][r] = A; continue;}
		ull v = g(l, r), now = v % M;
		while(ky[now] && ky[now] != v)now++;
		if(ky[now]){f[l][r] = g[now]; continue;}
		ky[now] = v;
		LL 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;
			pre(i, 11, 0)if(ste[i][p][k] && ste[i][p][k] + k - 1 <= r - k)p = ste[i][p][k], sum += 1 << i;
			cnt -= sum * k;
			cmn(cur, f[l][l + k - 1] + B + sum * C + cnt * A);
		}
		assert(tem <= 100000000);
		f[l][r] = g[now] = cur;
	}
	printf("%lld\n", f[1][n]);
	return 0;
}

T2:通信题,给 Alice 一颗树,Alice 将树上的点重新标号,标号区间为 \([1,2N+19]\)。Bob 现在得知两个点的标号,告诉 Alice 20bits 的信息,Alice 反馈给 Bob \(X\) bits 的信息,Bob 要利用反馈的信息算出这两个点的距离,X 越小得分越高。

20 bits的信息显然不能直接表示两个点 \(x,y\),所以考虑将树上的点分组,然后传组号。

不难想到树分块,令 \(B=10\),每块大小 \(\in [10,19]\)。最多 \(1000\) 块正好 10 bits 可以表示一个组号。

知道组号后把两个块内子树和两个块之间的距离反馈回去。所以我们对树但 dfs 序重标号,然后将 dfs 时入栈和出栈操作作为 \(0/1\) 反馈回去。这样就可以还原出一棵树。

写的好能拿 \(70\) 分左右,满分待补。

T3:给定若干三元组 \((a,b,c)\),求三元组的三元组 \((A,B,C)\) 满足 \(Aa > Ba,Ca\)\(Bb > Ab,Cb\)\(Cc>Ac,Bc\),使 \(Aa+Bb+Cc\) 最大。

考虑按 \(c\) 排序,对于每个三元组 \(X\),将 \(c\) 更小的三元组加入集合 \(S\)。我们只用找到集合中最大的 \(Aa >Ba\)\(Ab < Bb\) 的三元组即可。直接 set 维护即可,时间复杂度 \(\mathcal{O}(N\log N)\)

#define N 150005
int n, ans = ~0, sa, sb;
struct node{
	int a, b, c;
	bool operator<(const node o)const{return c < o.c;}
}a[N];
set<Pr>s;
void ins(int x,int y){
	if(x <= sa && y <= sb)return;
	if(x < sa)sb = y;
	else if(y < sb)sa = x;
	while(true){
		auto cur = s.lower_bound(mp(x + 1, 0));
		if(cur == s.end())break;
		if((*cur).se < y)cmx(sa, (*cur).fi), cmx(sb, y), s.erase(cur);
		else break;
	}
	while(true){
		auto cur = s.lower_bound(mp(x, 0));
		if(cur == s.begin())break;
		cur--; 
		if((*cur).se > y)cmx(sa, x), cmx(sb, (*cur).se), s.erase(cur);
		else break;
	}
	s.insert(mp(x, y));
	while(!s.empty()){
		auto cur = s.begin();
		if((*cur).fi <= sa && (*cur).se <= sb)s.erase(cur);
		else break;
	}
}
void calc(int l,int r){
	rep(i, l, r)if(sa > a[i].a && sb > a[i].b)cmx(ans, sa + sb + a[i].c);
	rep(i, l, r)ins(a[i].a, a[i].b);
}
int main() {
	read(n);
	rp(i, n)read(a[i].a, a[i].b, a[i].c);
	sort(a + 1, a + n + 1);
	for(int i = 1; i <= n;){
		int j = i;
		while(a[j].c == a[i].c)j++;
		calc(i, j - 1), i = j;
	}printf("%d\n", ans);
	return 0;
}

Day3

T1:通信题,Alice 要告诉 Bob 一个 \(<10^{18}\) 的数,Alice 给出两个等长的 \(0/1\)\(s,t\)\(s,t\) 会被归并成一个新串并告诉 Bob,Bob 要还原出这个数,归并策略是任意的(不随机)。

这题确实非常难以下手,这个归并就相当于洗扑克的叠牌,洗了后原来的信息就没了。

由于数 \(<10^{18}\),意味着我们需要保留 \(60\) 个有效位。

对于 \(s,t\),考虑牺牲一个串成全另一个串。首先我们让 \(0/1\) 串等价于 \(-1/1\) 串,那么我们让 \(t = \{1,-1,1,-1,\cdots\}\),那么对于 \(t\) 的前缀和是 \(0/1\)

那么对于串 \(s\),我们将每一位重复 \(3\) 遍。对于归并后的串求前缀和 \(u\),显然 \(u_i = 3k + \{-1/0/1\}\)\(k\) 是唯一的,后面的 \(\{-1/0/1\}\)\(t\) 串带来的波动,而 \(k\) 则是有效信息,如果 \(k\) 增了说明该有效位是 \(1\),否则是 \(0\)

所以需要 \(180\) 个 bits 能得到 \(80\) 分。满分待补。

T2: 给定一棵树,支持两个操作。1 x y z 表示到点 \(x\) 的距离 \(\le y\) 的点都 \(\times z\)2 x 表示查询点 \(x\) 的权值,\(y\le 40\)

定义 \(f_{i,j}\) 表示将点 \(i\) 子树中距离 \(\le j\) 的点都乘 \(f_{i,j}\)。每次修改和询问最多和最多 \(40\) 个祖先有关。直接模拟复杂度是 \(nd^2\),差分一下可以做到 \(\mathcal{O}(nd)\)

#define N 200005
int n, u[N], fa[N], w[N], t, P; vector<int>e[N];
void dfs(int x,int f){
	fa[x] = f; go(y, e[x])if(y != f)dfs(y, x);
}
int f[41][N], c[10];
int main() {
	read(n, P);
	rp(i, n - 1){
		int x, y; read(x, y);
		e[x].pb(y), e[y].pb(x);
	}
	srand(time(0));
	dfs(rand() % n + 1, 0);
	rp(i, n){
		read(u[i]);
		rep(j, 0, 40)f[j][i] = 1;
	}
	int T; read(T);
	while(T--){
		int op, x, y; LL z; read(op, x);
		if(1 == op){
			read(y, z);
			int d = y, r = x;
			while(r && d >= 0){
				if(fa[r] && d > 1)
					f[d][r] = f[d][r] * z % P, f[d - 1][r] = f[d - 1][r] * z % P;
				else rep(i, 0, d)f[i][r] = f[i][r] * z % P;
				d--, r = fa[r];
			}
		}
		else{
			LL cur = u[x];
			y = x; int d = 40;
			while(y && d >= 0){
				cur = cur * f[40 - d][y] % P, d--, y = fa[y];
			}printf("%lld\n", cur);
		}
	}
	return 0;
}

T3:

待补

Day4

T1:交互题。\(n\) 种颜色,每种颜色 \(m\) 个球。\(n\) 个不同颜色的球可以组成一组。每次可以询问一个集合 \(S\) 中的球最多能组成多少组。现在要将所有球分成 \(m\) 组,最多询问 \(50000\) 次,\(n\le 400, m\le 25\)

从左向右一次询问区间 \([1,i]\),第一个返回 \(1\) 的位置 \(x\) 一定是该颜色的第一次出现。

\(x\) 加入当前集合 \(ans\),然后从后向前扫,如果当前扫到位置 \(i\),区间 \([1,i]\) 加上 \(ans\) 集合里的数不能组成一组,则把 \(i + 1\) 加入 \(ans\) 集合。

扫一个来回后 \(ans\) 集合就是一个合法组。直接扫需要询问 \(nm^2\) 次,显然是无法通过的。

我们可以优化一下,向右扫的过程我们可以直接二分答案。向左扫虽然也可以二分,但是需要二分的次数太多反而是负优化,我们类似循环展开每次向前跳 \(3\) 个位置即可。这样期望的询问次数 \(m\log nm + \dfrac{nm^2}{6}\) 勉强可以通过。

但是如果所有球是有序的会被卡成 \(\dfrac{nm^2}{3}\),所以我们开始前对所有标号随机打乱,这样无论数据如何对于我们都是随机均匀的。

mt19937 rd(765234);
#define maxn 10005
int p[maxn], a[maxn], b[maxn], t;
vector<int>c;
int ask(int x){
	vector<int>u = c;
	rp(i, x)u.pb(p[a[i]]);
	return Query(u);
}
void Solve(int n,int m){
	rp(i, n * m)p[i] = i, a[i] = i;
	shuffle(p + 1, p + n * m + 1, rd);
	t = n * m;
	rp(i, m){
		if(i == m){
			rp(j, t)c.pb(p[a[j]]);
			Answer(c); return ;
		}
		int l = n, r = min(t, 4000), w = 0;
		while(l <= r){
			int mid = (l + r) >> 1;
			if(ask(mid))w = mid, r = mid - 1;
			else l = mid + 1;
		}
		c.pb(p[a[w]]), a[w--] = 0;
		while(si(c) < n){
			if(w < 5){
				if(!ask(w - 1)){
					c.pb(p[a[w]]), a[w] = 0;
				}w--;
			}
			else{
				if(ask(w - 3))w -= 3;
				else{
					if(!ask(w - 1)){
						c.pb(p[a[w]]), a[w] = 0, w--;
					}
					else if(!ask(w - 2)){
						c.pb(p[a[w - 1]]), a[w - 1] = 0, w -= 2;
					}
					else c.pb(p[a[w - 2]]), a[w - 2] = 0, w -= 3;
				}
			}
		}
		Answer(c), c.clear();
		int T = 0;
		rp(j, t)if(a[j])b[++T] = a[j];
		t = T; rp(j, t)a[j] = b[j];
	}
}

T2:给定一个序列,支持单点修改和区间查询。每个数可以吞并相邻的不大于它的数,变成两数之和。每次询问区间内有多少个数可以吞下整个区间。

最大数一定可以,我们以最大值为分界,如果左边之和大于最大值,则递归统计左边,对右边同理,类似于笛卡尔树。直接做是 \(\mathcal{O}(QN)\) 可以得到 25 分。如果没有修改操作,我们可以从区间左端点每次跳到右边第一个比自己大的位置,一直到区间最大值,并统计答案,倍增可以优化至 \(\mathcal{O}(Q\log N)\),可以得到 \(48\) 分。

其余待补。

T3:给定带权无向图,每次询问 \(X\),表示如果所有边权变为 \(|X-w_i|\) 的最小生成树。

将所有边按照边权排序,求出前缀最小生成树和后缀生成树。对于一次询问 \(X\) 相当于合并前后两颗树得到新的最小生成树。

直接做时间复杂度 \(\mathcal{O}((q+m)n\alpha(n))\),只有 \(42\) 分。

\(qn\alpha(n)\) 太大了考虑优化掉。

对于预处理的过程,从小到大每次加入一条边,如果成环则删除一条边。

如果没有删边,那么对于所有 \(x\),都要选择当前的边。否则我们设删除的边为 \(p\),当前边为 \(q\),那么 \(p\) 一定是 \(q\) 对应树的路径最大的边,边权 \(<p\) 的其他边一定不在路径上,所以当 \(x\le \dfrac{w_p+w_q}{2}\) 时选择 \(p\),否则选择 \(q\)

直接做是 \(\mathcal{O}(mn\alpha(n) + q)\),由于每次是加一条边并删一条边,可以直接在树上找最大的边,复杂度 \(\mathcal{O}(mn+q)\),也可以用 LCT 维护做到 \(\mathcal{O}(m(\log m+\log n) + q)\)

#define N 505
#define M 100005
int n, m, fa[N];
int get(int x){return fa[x] == x ? x : fa[x] = get(fa[x]);}
struct node{
	int u, v, w;
	bool operator<(const node o)const{return w < o.w;}
}e[M], u[N], v[N];
vector<node>a;
int main() {
	read(n, m);
	rp(i, m)read(e[i].u, e[i].v, e[i].w);
	sort(e + 1, e + m + 1);
	int t = 0;
	rp(i, m){
		rp(i, n)fa[i] = i;
		fa[e[i].u] = e[i].v;
		int p = 0, dl = 0; 
		v[++p] = e[i];
		rp(i, t){
			if(dl)v[++p] = u[i];
			else{
				int x = get(u[i].u), y = get(u[i].v);
				if(x != y)fa[x] = y, v[++p] = u[i];
				else dl = i;
			}
		}
		if(!dl)a.pb(node{-1, e[i].w, 0}), a.pb(node{2, -2 * e[i].w, e[i].w});
		else {
			a.pb(node{-1, u[dl].w, (e[i].w + u[dl].w + 1) >> 1});
			a.pb(node{-1, e[i].w, (e[i].w + u[dl].w + 1) >> 1});
			a.pb(node{2, -2 * e[i].w, e[i].w});
		}
		t = p; rp(i, t)u[i] = v[i];
	}
	sort(a.begin(), a.end());
	int j = 0, sz = si(a); LL l = 0, r = 0;
	int T; read(T);
	while(T--){
		int x; read(x);
		while(j < sz && a[j].w <= x)l += a[j].u, r += a[j].v, j++;
		printf("%lld\n", l * x + r);
	}
	return 0;
}

posted @ 2022-03-24 14:56  7KByte  阅读(869)  评论(0编辑  收藏  举报