• ZR省选十连测


    #1

    A

    考虑其等价于把\(0\)看作\(<\),\(1\)看作\(>\)求满足条件的排列数量。

    可以使用容斥加分治\(FFT\)解决其。

    B

    太难了,补不动。

    C

    考虑\(g_{i,j},j \in [0,3]\) 为通过\(i\)往上延伸的\(j\)长度的链的子树内部权值。

    这里只考虑\(k = 4\) 的情况。

    思考我们需要记录前缀合并情况里\(s_1 > s_3\) 的个数,以及\(s_2\) 的寄偶性。

    如果随机序列\(1,-1\) 其前缀和大概率不大于\(\sqrt n\),我们随机打碎儿子,然后记录即可。

    点击查看代码
    #include <bits/stdc++.h>
    #define fi first
    #define se second
    #define mp make_pair
    using namespace std;
    
    typedef pair <int, int> pii;
    
    const long long INF = 0x3f3f3f3f3f3f3f3f;
    
    long long dp[200010][5];
    vector <pii> G[200010];
    int n, k;
    long long f[2010][2];
    
    void dfs(int x, int last) {
    	int son = 0;
    	for (auto it : G[x]) {
    		int v = it.fi, w = it.se;
    		if (v == last) continue;
    		dfs(v, x), son++;
    	}
    	int S = min(2001, son << 1 | 1);
    	for (int i = 0; i <= S; i++) {
    		f[i][0] = f[i][1] = -INF;
    	}
    	random_shuffle(G[x].begin(), G[x].end());
    	int be = S >> 1;
    	f[be][0] = 0;
    	for (auto it : G[x]) {
    		int v = it.fi, w = it.se;
    		if (v == last) continue;
    		long long lst[2] = {-INF, -INF};
    		for (int i = 0; i < S; i++) {
    			long long cur[2] = {f[i][0], f[i][1]};
    			for (int j = 0; j < 2; j++) {
    				f[i][j] += max(dp[v][0], dp[v][k - 1] + w);
    				f[i][j] = max(f[i][j], lst[j] + dp[v][0] + w);
    				f[i][j] = max(f[i][j], f[i + 1][j] + dp[v][k - 2] + w);
    				if (k == 4) f[i][j] = max(f[i][j], cur[j ^ 1] + dp[v][1] + w);
    			}
    			lst[0] = cur[0], lst[1] = cur[1];
    		}
    	}
    	dp[x][0] = f[be][0];
    	dp[x][1] = f[be + 1][0];
    	dp[x][k - 1] = be ? f[be - 1][0] : -INF;
    	if (k == 4) dp[x][2] = f[be][1];
    }
    
    int main() {
    	srand((int)time(NULL));
    	scanf("%d%d", &n, &k);
    	for (int i = 1; i < n; i++) {
    		int x, y, w; scanf("%d%d%d", &x, &y, &w);
    		G[x].push_back(mp(y, w));
    		G[y].push_back(mp(x, w));
    	}
    	dfs(1, 1);
    	printf("%lld\n", dp[1][0]);
    	return 0;
    }
    

    #4

    A

    一个诈骗题,考虑对每个点向外BFS寻找非平凡回路,只要\(u \to v\)时,\(v\)在之前遇到过,则该点的答案为\(dis_v\)

    点击查看代码
    #include <cstdio>
    #include <vector>
    #include <cstring>
    #include <algorithm>
    const int Maxn=5000;
    const int Inf=0x3f3f3f3f;
    int n;
    std::vector<int> g[Maxn+5];
    int dis[Maxn+5];
    bool vis[Maxn+5];
    int qu[Maxn+5],qu_f,qu_t;
    int init_bfs(int S){
    	memset(dis,0x3f,sizeof dis);
    	memset(vis,0,sizeof vis);
    	dis[S]=0,vis[S]=1;
    	qu_f=1,qu_t=0;
    	qu[++qu_t]=S;
    	while(qu_f<=qu_t){
    		int u=qu[qu_f++];
    		for(int v:g[u]){
    			if(!vis[v]){
    				dis[v]=dis[u]+1;
    				vis[v]=1;
    				qu[++qu_t]=v;
    			}
    			else if(dis[u]<=dis[v]){
    				return dis[u]+1;
    			}
    		}
    	}
    	return -1;
    }
    int main(){
    	scanf("%d",&n);
    	for(int i=2;i<=n;i++){
    		static char s[Maxn+5];
    		scanf("%s",s+1);
    		int len=0;
    		while(s[++len]!='\0');
    		len--;
    		for(int j=1;j<i;j++){
    			if(s[j]=='1'){
    				g[i].push_back(j),g[j].push_back(i);
    			}
    		}
    	}
    	for(int i=1;i<=n;i++){
    		printf("%d\n",init_bfs(i));
    	}
    	return 0;
    }
    
    

    B

    image

    #5

    A

    写做\(f_i = \sum (mex(j,i) * f_{j - 1})\)

    考虑我们只要每次每次后缀加入一个数动态维护\(mex\)即可,考虑我们每次加入后缀时,直接查询在\(mex = a_i\)\([l,r]\)里查询前缀\(mex\)的位置,并直接更新其前缀为\(mex <- mex+1\),考虑每次都把对应位置的\(mex\)变为大于其权值,一个数只会变一次,所以复杂度有所保证。

    B

    考虑有结论

    结论一:当\(a < b < c\),有\(a \oplus c >= \min(a \oplus b,b\oplus c)\),那么即最小值异或对只会出现在相邻的\(i,j\)

    结论二:当\(b >= a\),\(a \oplus a >= b - a\),当有这两个结论时我们就可以做\(sub1,sub2,sub3\)

    我们考虑当\(w > \min (a_i - a_j)\)时,可以构造出答案为\(\min(a_i - a_j)\),那么我们只要每次都枚举小于\(\min(a_i - a_j)\)\(x\),记录其答案即可。

    考虑实际上加入一个数时其\(mn\)递减,那么只要枚举时对新加入的对统计即可,那么复杂度为\(O(\sum \frac{2^V}{i}) = O(V2^V)\)

    点击查看代码
    
    //晦暗的宇宙,我们找不到光,看不见尽头,但我们永远都不会被黑色打倒。——Quinn葵因
    #include<bits/stdc++.h>
    #define ll long long
    #define N 1000005
    
    using std::set;
    
    set<int>A;
    
    int V,n;
    
    #define inf (1ll << 21)
    
    int mn = inf;
    
    ll ans[inf];
    
    int main(){
    	scanf("%d%d",&V,&n);
    	for(int i = 0;i < inf;++i)
    	ans[i] = inf;
    	for(int i = 1;i <= n;++i){
    		int opt,x;
    		scanf("%d%d",&opt,&x);
    		if(opt == 1){
    			A.insert(x);
    			set<int>::iterator it = A.lower_bound(x);
    			set<int>::iterator cur = it;
    			if(it != A.begin()){
    				-- it;
    //				std::cout<<"("<<*it<<" "<<x<<")"<<"\n"; 
    				mn = std::min(mn,x - *it);
    				for(int i = 0;i <= mn;++i)
    				ans[i] = std::min(ans[i],1ll * (i + *it) ^ (i + x)),ans[i] = (i == 0) ? ans[i] : std::min(ans[i - 1],ans[i]);
    			}
    			it = ++cur;
    			if(it != A.end()){
    				mn = std::min(mn,*it - x);
    //				std::cout<<"("<<x<<" "<<*it<<")"<<"\n"; 				
    				for(int i = 0;i <= mn;++i)
    				ans[i] = std::min(ans[i],1ll * (i + *it) ^ (i + x)),ans[i] = (i == 0) ? ans[i] : std::min(ans[i - 1],ans[i]);				
    			} 
    //			std::cout<<mn<<"\n";
    		}else{
    			if(x > mn)
    			std::cout<<mn<<"\n";
    			else
    			std::cout<<ans[x]<<"\n"; 
    		}
    	}
    }
    
    

    结论三:考虑到答案的最终贡献方式,一定是让所有相邻对的某一位的后\(k\)位归\(0\),即可贡献维护的答案\(x\)不超过\(Vn\)个,考虑直接对这\(Vn\)个维护即可。

    点击查看代码
    ans[i] = std::min(ans[i],1ll * (i + *it) ^ (i + x)),ans[i] = (i == 0) ? ans[i] : std::min(ans[i - 1],ans[i]);
    			}
    			it = ++cur;
    			if(it != A.end()){
    				mn = std::min(mn,*it - x);
    //				std::cout<<"("<<x<<" "<<*it<<")"<<"\n"; 				
    				for(int i = 0;i <= mn;++i)
    				ans[i] = std::min(ans[i],1ll * (i + *it) ^ (i + x)),ans[i] = (i == 0) ? ans[i] : std::min(ans[i - 1],ans[i]);				
    			} 
    //			std::cout<<mn<<"\n";
    		}else{
    			if(x > mn)
    			std::cout<<mn<<"\n";
    			else
    			std::cout<<ans[x]<<"\n"; 
    
    

    C

    考虑每次维护中间块\([OL,OR] = k\)这一块的最小生成树,然后左边的在中间\(k\)个点虚树上的边,后缀维护,还有右边整块的前缀。

    然后考虑删除时直接暴力重构。

    #6

    给我心态打崩了。
    坐牢,签到都没签上。

    A

    怎么开局一直想着枚举左端点。
    考虑强制钦定最小值的位置,设\(l_i\)为左边比\(h_i\)的大的数,\(r_i\)为在右边比他大的数。
    \(x = l_i + r_i + 1\)
    \(i = l_i + 1\)
    考虑其对于一个询问\(k\)的贡献实际上是
    \(\min(i,x - i + 1,k,x - k + 1)\)

    考虑对于所有\(k\)都维护一个这样的贡献,那么其实际上可以通过拆成系数和常数分开维护。

    (借一下rsx的代码)

    #6 A
    #include <bits/stdc++.h>
    const int MAXN = 1e5 + 10;
    using std::cin;
    using std::cout;
    template <typename T>
    inline T min(const T &x, const T &y) {
    	return x < y ? x : y;
    }
    template <typename T>
    struct Fenwick_Tree {
    	int limit;
    	T t[MAXN];
    	void add(int x, T y) {
    		for (; x <= limit; x += x & -x) {
    			t[x] += y;
    		}
    		return;
    	}
    	T sum(int x) {
    		T ret = 0;
    		for (; x; x -= x & -x) {
    			ret += t[x];
    		}
    		return ret;
    	}
    	void build(int _N) {
    		limit = _N;
    	}
    };
    Fenwick_Tree <int> cyc;
    int N, L, R, A[MAXN], Pos[MAXN];
    long long K[MAXN], B[MAXN];
    void add(long long *qjy, int l, int r, long long v) {
    	qjy[l] += v;
    	qjy[r + 1] -= v;
    }
    int main() {
    	std::ios::sync_with_stdio(0);
    	cin.tie(0);
    	cout.tie(0);
    	cin >> N >> L >> R;
    	for (int i = 1; i <= N; ++i) {
    		cin >> A[i];
    		Pos[A[i]] = i;
    	}
    	cyc.build(N);
    	for (int i = 1; i <= N; ++i) {
    		cyc.add(Pos[N - i + 1], 1);
    		int Tk = cyc.sum(Pos[N - i + 1]);
    		int Tmp = min(Tk, i - Tk + 1);
    		add(K, 1, Tmp, 1);
    		add(B, Tmp + 1, i - Tmp, Tmp);
    		add(K, i - Tmp + 1, i, -1);
    		add(B, i - Tmp + 1, i, i + 1);
    	}
    	for (int i = 1; i <= N; ++i) {
    		K[i] += K[i - 1];
    		B[i] += B[i - 1];
    	}
    	long long SUM = 0;
    	for (int i = L; i <= R; ++i) {
    		SUM ^= K[i] * i + B[i];
    	}
    	cout << SUM << '\n';
    	return 0;
    }
    

    B

    考虑对格子上的史莱姆进行\(dp\),这样发现其可以发现史莱姆合并的操作不影响答案。

    则有\(X_{i,j} = \frac{1}{4} * (\sum_{(x,y)\ is\ next\ to\ (i,j) X_{x,y}}) + T_{x,y}\)

    于是可以直接高斯消元得到\(40\)分。

    #6 B 40分
    #include<bits/stdc++.h>
    #define ll long long 
    #define N 105
    #define mod 998244353
    #define I4 748683265
    #define pii std::pair<int,int>
    #define mp std::make_pair
    
    
    int dx[] = {-1,0,1,-0};
    int dy[] = {0,-1,0,1};
    
    int n,X[N],Y[N],a[N][N],T[N],ans;
    
    #define mpi std::map<pii,int>
    
    mpi P,C;
    
    inline ll qpow(ll a,ll b){
    	ll res = 1;
    	while(b){
    		if(b & 1)res = res * a % mod;
    		a = a * a % mod;
    		b >>= 1; 
    	}
    	return res;
    }
    
    inline void guass(int n){	
    	for(int i = 1;i <= n;++i){
    		int pos = i;
    		for(int j = i;j <= n;++j)
    		if(a[j][i]){
    			pos = j;
    			break;
    		}
    		if(pos ^ i)std::swap(a[i],a[pos]);
    		int w = qpow(a[i][i],mod - 2);
    		for(int j = 1;j <= n;++j){
    			if(j == i)continue;
    			int t = 1ll * a[j][i] * w % mod;
    			for(int k = i;k <= n + 1;++k)
    			a[j][k] = ((a[j][k] - 1ll * t * a[i][k] % mod) % mod + mod) % mod;
    		}		
    	}	
    	for(int i = 1;i <= n;++i)
    	a[i][n + 1] = 1ll * (1ll * a[i][n + 1] * (mod - 1) % mod * qpow(a[i][i],mod - 2)) % mod;
    }
    
    inline void solve(){
    	for(int i = 1;i <= n;++i){
    //		std::cout<<"DEL "<<i<<std::endl;
    		for(int j = 0;j < 4;++j){
    			int x = X[i] + dx[j],y = Y[i] + dy[j];
    //			std::cout<<x<<" "<<y<<"\n";
    			if(P.find(mp(x,y)) == P.end())continue;
    			a[i][P[mp(x,y)]] = (I4) % mod; 
    		}
    		a[i][i] = mod - 1;
    		a[i][n + 1] = T[i];
    	}
    	guass(n);
    	for(auto i : C){
    		int sx = i.first.first,sy = i.first.second,res = 0;
    //		std::cout<<sx<<" "<<sy<<std::endl; 
    		for(int j = 0;j < 4;++j){
    			int nx = sx + dx[j];
    			int ny = sy + dy[j];
    			if(P.find(mp(nx,ny)) == P.end())continue;
    			res = (res + a[P[mp(nx,ny)]][n + 1]) % mod;
    		}
    	ans = ans ^ (1ll * I4 * res % mod);
    	}
    	std::cout<<ans<<"\n";
    }
    
    int main(){
    	scanf("%d",&n);
    	for(int i = 1;i <= n;++i){
    		scanf("%d%d%d",&X[i],&Y[i],&T[i]);
    		P[mp(X[i],Y[i])] = i;
    	}
    	for(int i = 1;i <= n;++i)
    	for(int j = 0;j < 4;++j){
    		int x = X[i] + dx[j],y = Y[i] + dy[j];
    		if(P.find(mp(x,y)) == P.end())C[mp(x,y)] = 1;
    	}
    	solve();
    }
    
    
    

    \(n \leq 2000\)时,考虑当我们每一列的元素只在\([i - m,i + m]\)中有值可以稀疏矩阵消元。

    但是我发现有份代码直接对矩阵随机打乱然后记录有元素的值,跑的也很快。

    #6 B 60分
    inline void guass(int n){
    	std::random_shuffle(a+1,a+n+1);
    	for(R int i=1;i<=n;++i)
    		for(R int j=1;j<=n+1;++j) if(a[i][j]) mw[i].insert(j);
    	for(R int i=1;i<=n;++i){
    		R int j=i;
    		for(;j<=n;++j) if(a[j][i]) break;
    		if(j^i) std::swap(a[i],a[j]),std::swap(mw[i],mw[j]);
    		R int ha=qpow(a[i][i],P-2);
    		for(j=1;j<=n;++j){
    			if(j==i) continue;
    			if(mw[j].find(i)==mw[j].end())continue;
    			R int qc=1LL*(P-a[j][i])*ha%P;
    			for(auto k:mw[i]){
    				if(!a[j][k]) mw[j].insert(k);
    				a[j][k]=(a[j][k]+1LL*qc*a[i][k])%P;
    				if(!a[j][k]) mw[j].erase(k);
    			}
    		}
    	}
    	for(R int i=1;i<=n;++i){
    		a[i][n+1]=(a[i][n+1]*(P-1LL)%P*qpow(a[i][i],P-2))%P;
    	}
    }
    
    

    考虑矩阵情况,可以直接手动消元把除第一列的元素都用第一列表示,最后使用最后一列的关系对其消元,这样只有\(O(\sqrt n)\)个元,其复杂度为\(O(n^\frac{3}{2})\)

    #7

    A

    考虑一次操作二会改变奇偶性。
    一定是先操作二一次,然后再检查子区间是否有全偶区间即可。
    当带0时,其把序列划分位若干子区间。
    考虑扫描线即可。
    找到前面第一个不是偶数的子区间即可。

    image

    B

    \(xpp\)原本想考的是随机下最大上升子序列只有根号。

    但是随机太好了。

    被某些根号算法草爆了。

    这里提供一个线段树覆盖代替原本的暴力并查集的做法。

    点击查看代码
    #include<bits/stdc++.h>
    #define ll long long 
    #define N 15005
    
    int n,m,q,mx,a[N];
    
    int sum[35000];
    
    #define mid ((l + r) >> 1)
    #define ls(x) (x << 1)
    #define rs(x) (x << 1 | 1)
    
    using std::bitset;
    using std::array;
    using std::vector;
    
    bitset<35000>tag;
    
    vector<array<int,4>>M;
    
    inline void cov(int u,int l,int r,int tl,int tr){
    	if(tag[u])return ;
    	if(tl <= l && r <= tr){tag[u] = 1,sum[u] = r - l + 1;return ;}
    	if(tl <= mid)
    	cov(ls(u),l,mid,tl,tr);
    	if(tr > mid)
    	cov(rs(u),mid + 1,r,tl,tr);
    	tag[u] = tag[ls(u)] & tag[rs(u)],sum[u] = sum[ls(u)] + sum[rs(u)];
    }
    
    int main(){
    	scanf("%d%d%d",&n,&m,&q);
    	for(int i = 1;i <= n;++i){
    		scanf("%d",&a[i]);
    		M.push_back({i,i,a[i],0}); 
    	}
    	for(int i = 1;i <= m;++i){
    		int l,r,x;
    		scanf("%d%d%d",&l,&r,&x);
    		M.push_back({l,r,x,i});
    	}
    	std::sort(M.begin(), M.end(), [&](array<int, 4> x, array<int, 4> y) { return x[2] < y[2]; });
    	while(q -- ){
    		int l1,r1,l2,r2;
    		scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
    		memset(sum,0,sizeof(sum)),tag.reset();
    		ll ans = 0;
    		int lst = 0;
    		for (auto &p : M) if (!p[3] || p[3] >= l1 && p[3] <= r1) {
    			int l = std::max(l2, p[0]), r = std::min(r2, p[1]);
    			if (l <= r) cov(1, 1, n, l, r);
    			ans += 1ll * p[2] * (sum[1] - lst);
    			if ((lst = sum[1]) == r2 - l2 + 1) break;
    		}
    		printf("%lld\n", ans);		
    	}	
    }
    

    #8

    A

    考虑 \((s_i,s_{i + 1},t_i,t_{i + 1})\) 一定有两个相同,所以最长公共子序列长度至少为 \(n\).

    考虑限制很紧,一不小心就会大于 \(n\)

    考虑只有两种情况:

    \(ABABABAB\ \ C*C*C*C\)

    \(ACACBCBC\ \ BCBCACAC\)

    对着检查即可。

    点击查看代码
    //晦暗的宇宙,我们找不到光,看不见尽头,但我们永远都不会被黑色打倒。——Quinn葵因
    #include<bits/stdc++.h>
    #define ll long long
    #define N 2000005
    #define mod 998244353
    
    char s[N],t[N];
    
    int n;
    
    int T;
    
    bool pre[N],suf[N];
    
    bool check(char a,char b){
    	return a == '?' || a == b;
    }
    
    ll ans = 0;
    
    inline void calc(char a,char b,char c){
    	int now = 1;
    	for(int i = 1;i <= (n << 1) + 1;++i)
    	if(i & 1)
    	now = now * check(s[i],a) * check(t[i],c) % mod;
    	else
    	now = now * check(s[i],b) * (check(t[i],a) + check(t[i],b)) % mod;
    	ans = (ans + now) % mod;
    	now = 1;
    	for(int i = 1;i <= (n << 1) + 1;++i)
    	if(i & 1)
    	now = now * check(t[i],a) * check(s[i],c) % mod;
    	else
    	now = now * check(t[i],b) * (check(s[i],a) + check(s[i],b)) % mod;
    	ans = (ans + now) % mod;
    	pre[0] = suf[2 * n + 2] = 1;
    	for(int i = 1;i <= (n << 1) + 1;++i){
    		if(i & 1)
    		pre[i] = pre[i - 1] & check(s[i],a) & check(t[i],b);
    		else
    		pre[i] = pre[i - 1] & check(s[i],c) & check(t[i],c);
    	}	
    	for(int i =(n << 1) + 1;i >= 1;--i){
    		if(i & 1)
    		suf[i] = suf[i + 1] & check(s[i],b) & check(t[i],a);
    		else
    		suf[i] = suf[i + 1] & check(s[i],c) & check(t[i],c);
    	}		
    	for(int i = 1;i <= (n << 1) + 1;i += 2)
    	ans += (i == ((n << 1) + 1)) ? - (pre[i] & suf[i + 1]) : pre[i] & suf[i + 1] ;
    } 
    
    int main(){
    	scanf("%d",&T);
    	while(T -- ){
    		ans = 0; 
    		scanf("%d%s%s",&n,s + 1,t + 1);
          	calc('A','B','C');
            calc('A','C','B');
            calc('B','A','C');
            calc('B','C','A');
            calc('C','A','B');
            calc('C','B','A');	
    		std::cout<<ans<<"\n";
    	}
    }
    /*
    2
    ?????
    ?????
    */ 
    

    B

    考虑树上枚举 \((x,y),LCA z\)

    考虑当\(z < x < y\)或者 \(x < y < z\) 相当于给定了一个矩形,其矩形内的点均无效。

    那么直接对询问扫描线即可。

    考虑只要枚举相邻的\(x,y\),那么直接树上启发式合并即可。

    #10

    C

    先考虑菊花的做法,发现其等价于给定点权,一条边的代价为 \(abs(a_x + a_y)\)

    其可以使用 \(Brovka\) 解决。

    考虑改为任意树,可以使用点/边分治,将路径改为上述形式解决。

    每一轮,每点均找到异子树内的最小权边,共有 \(nlog\) 条边。

    使用边分治更好处理。

    点击查看代码
    //晦暗的宇宙,我们找不到光,看不见尽头,但我们永远都不会被黑色打倒。——Quinn葵因
    #include<bits/stdc++.h>
    #define ll long long
    #define N (int)2e6
    
    using std::vector;
    using std::pair;
    
    #define pii pair<int,int>
    #define mp std::make_pair
    
    int head[N];
    
    struct P{
    	int to,next,w;
    }E[N * 20];
    
    int cnt = 1;
    
    inline void add(int u,int v,int w){
    	E[++cnt].to = v;
    	E[cnt].next = head[u];
    	E[cnt].w = w;
    	head[u] = cnt;
    }
    
    inline void adde(int u,int v,int w){
    	add(u,v,w);
    	add(v,u,w);
    }
    
    vector<pii>G[N];
    
    int m;
    
    inline void reb(int u,int fa){
    	int lst = 0;
    	for(auto vi : G[u]){
    		int v = vi.first;
    		int w = vi.second;
    		if(v == fa)continue;
    		if(!lst){
    			adde(u,v,w);
    			lst = u;
    		}else{
    			adde(lst,++m,0);
    			adde(m,v,w);
    			lst = m;
    		}
    		reb(v,u);
    	}
    }
    
    int siz[N],re,minn,sum;
    
    int vis[N];
    
    inline void getroot(int u,int fa){
    	siz[u] = 1;
    	for(int i = head[u];i;i = E[i].next){
    		int v = E[i].to;
    		if(v == fa || vis[i])continue;
    		getroot(v,u);
    		siz[u] += siz[v];
    		if(std::max(siz[v],sum - siz[v]) < minn)
    		minn = std::max(siz[v],sum - siz[v]),re = i;
    	}
    }
    
    ll dis[N];
    
    int ent;
    
    struct nod {
    	int u,v;
    	ll w;
    	nod(){}
    	nod(int _u,int _v,ll _w){u = _u,v = _v,w = _w;}
    }e[N * 40];
    
    bool operator < (nod A,nod B){
    	return A.w < B.w;
    }
    
    int fa[N];
    
    inline int find(int x){return (x == fa[x]) ? x : fa[x] = find(fa[x]);}
    
    pair<ll,int>Vec[3][N];
    
    int sig[3];
    
    inline void calc(int u,int tag,int fa){
    	Vec[tag][++sig[tag]] = mp(dis[u],u);
    	for(int i = head[u];i;i = E[i].next){
    		int v = E[i].to;
    		int w = E[i].w;
    		if(v == fa || vis[i])continue;
    		dis[v] = dis[u] + w;
    		calc(v,tag,u);
    	}
    }
    
    inline ll llabs(ll x){return x > 0 ? x : -x;}
    
    inline void solve(int u){
    	if(sum == 1)return;
    	minn = 1e9;getroot(u,0);
    	vis[re] = vis[re ^ 1] = 1;
    //	std::cout<<u<<" "<<sum<<" "<<re<<"\n";
    	int x = E[re].to,y = E[re ^ 1].to;
    	sig[1] = sig[2] = 0;
    	dis[x] = E[re].w;calc(x,1,0);
    	dis[y] = 0;calc(y,2,0);
    	std::sort(Vec[1] + 1,Vec[1] + sig[1] + 1);
    	std::sort(Vec[2] + 1,Vec[2] + sig[2] + 1);
    	for(int i=1,j=sig[2];i<=sig[1];i++) {
    		while(j&&Vec[2][j].first>-Vec[1][i].first) j--;
    		if(j+1<=sig[2]) e[++ent]=nod(Vec[1][i].second,Vec[2][j+1].second,llabs(Vec[1][i].first+Vec[2][j+1].first));
    		if(j) e[++ent]=nod(Vec[1][i].second,Vec[2][j].second,llabs(Vec[1][i].first+Vec[2][j].first));
    	}
    	std::swap(sig[1],sig[2]);
    	for(int i=1;i<=std::max(sig[1],sig[2]);i++) swap(Vec[1][i],Vec[2][i]);
    	for(int i=1,j=sig[2];i<=sig[1];i++) {
    		while(j&&Vec[2][j].first>-Vec[1][i].first) j--;
    		if(j+1<=sig[2]) e[++ent]=nod(Vec[1][i].second,Vec[2][j+1].second,llabs(Vec[1][i].first+Vec[2][j+1].first));
    		if(j) e[++ent]=nod(Vec[1][i].second,Vec[2][j].second,llabs(Vec[1][i].first+Vec[2][j].first));
    	}
    	int now = sum - siz[x];
    	sum = siz[x];
    	solve(x);
    	sum = now;
    	solve(y);
    }
    
    int n;
    
    int main(){
    //	freopen("q.in","r",stdin);
    //	freopen("q.ans","w",stdout);
    	scanf("%d",&n);
    	m = n;
    	for(int i = 1;i < n;++i){
    		int x,y,w;
    		scanf("%d%d%d",&x,&y,&w);
    		G[x].emplace_back(y,w);
    		G[y].emplace_back(x,w);
    	}
    	reb(1,0);
    	sum = m;
    	solve(1);
    	std::sort(e + 1,e + ent + 1);
    	ll ans = 0;
    	for(int i = 1;i <= m;++i)fa[i] = i;
    	for(int i = 1;i <= ent;++i){
    //		std::cout<<e[i].u<<" "<<e[i].v<<" "<<e[i].w<<"\n";
    		if(find(e[i].u) ^ find(e[i].v)){
    			fa[fa[e[i].u]] = fa[e[i].v];
    			ans += e[i].w;
    		}
    	}
    	std::cout<<ans<<"\n";
    }
    
  • 相关阅读:
    MySQL性能优化的最佳20+条经验
    初窥Linux 之 我最常用的20条命令
    Java内存模型
    未能加载文件或程序集“System.Net.Http.Formatting, Version=4.0.0.0, Culture=n
    Nginx 负载均衡之 upstream 参数 和 location 参数
    Nginx 简单配置方法
    关于使用 autoFac 的注入方法
    关于 VS 调用存储过程加载很慢和SQL 执行很快的那些事
    C# 客户端读取共享目录文件
    NodeJS、NPM安装配置步骤
  • 原文地址:https://www.cnblogs.com/dixiao/p/15942900.html
Copyright © 2020-2023  润新知