CF2187C 思路分享(博弈论,树上启发式合并,树状数组)

https://codeforces.com/problemset/problem/2187/C

题意

给定 \(n\) 个点构成有向图,对于 \(1\le i \lt n\),有 \(i \rightarrow i+1\) 的边.

另有 \(m\) 条边 \(u \rightarrow v\),均满足 \(u \lt v\).

另外,对于给定边 \(u_1 \rightarrow v_1\)\(u_2 \rightarrow v_2\),不存在 \(u_1 \lt u_2 \lt v_1 \lt v_2\) 的情况.

\(A\)\(B\) 进行游戏,每回合,\(A\) 必须沿着一条边走一步,\(B\) 可以选择不走,当 \(A\)\(B\) 位于同一位置时,\(B\) 立即获胜;当 \(A\) 位于 \(n\) 时,\(A\) 获胜.

定义 \(f(x,y)\) 为:\(A\) 初始位于 \(x\)\(B\) 初始位于 \(y\),若 \(B\) 无法获胜,\(f(x,y)=0\);否则 \(f(x,y)= B\) 获胜需要走的最少步数.

\[\sum_{x,y,x\ne y}{f(x,y)} \]

\(2\le n \le 2\cdot 10^5\).

思路

首先,\(A\)\(B\) 每步一定会走最远的边,因此只保留最远的边,变成一棵树,\(B\) 获胜条件是 \(depth_y \le depth_x\).

即计算

\[\sum_{x,y,x\ne y}{[depth_y \le depth_x](depth_y-depth_{LCA(x,y)})} \]

将上式化成有序的形式

\[\sum_{x\lt y}{\min(depth_x,depth_y)-depth_{LCA(x,y)}}+\sum_{x\lt y}{[depth_x=depth_y](depth_x-depth_{LCA(x,y)})} \]

使用树上启发式合并,用树状数组维护区间的 \(depth\) 之和以及区间的 \(depth\) 数量.

左边部分看作是 \(\le depth_y\)\(\gt depth_y\) 的两部分,讨论求和即可.

时间复杂度 \(\mathcal{O}(n\log^2 n)\).

代码

//author:kzssCCC

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

class fenwick{
public:
	int n;
	vector<ll> a;

	fenwick(int _n){
		n = _n;
		a.assign(n+1,0);
	}

	void build(vector<ll>& b){
		a = b;
		for (int i=1;i<=n;i++){
			int j = i+(i&-i);
			if (j<=n){
				a[j] += a[i];
			}
		}
	}

	void update(int x,ll val){
		if (x<1 || x>n) return;
		for (int i=x;i<=n;i+=i&-i){
			a[i] += val;
		}
	}

	ll query(int x){
		if (x<1 || x>n) return 0; 
		ll res = 0;
		for (int i=x;i>=1;i-=i&-i){
			res += a[i];
		}
		return res;
	}

	ll query_range(int l,int r){
		if (l<1 || l>n || r<1 || r>n || l>r) return 0;
		return query(r)-query(l-1); 
	}
};

void solve(){
	int n,m;
	cin >> n >> m;

	vector<int> par(n+1,-1);
	for (int i=1;i<n;i++){
		par[i] = i+1;
	}
	for (int i=0;i<m;i++){
		int u,v;
		cin >> u >> v;
		par[u] = max(par[u],v);
	}

	vector<vector<int>> adj(n+1);
	for (int i=1;i<n;i++){
		adj[par[i]].push_back(i);
	}

	vector<int> sz(n+1),son(n+1,-1),depth(n+1);
	depth[n] = 1;
	function<void(int)> dfs = [&](int u){
		sz[u] = 1;
		int pos = -1;
		int mx = 0;
		for (auto& v:adj[u]){
			depth[v] = depth[u]+1;
			dfs(v);
			sz[u] += sz[v];
			if (sz[v]>mx){
				mx = sz[v];
				pos = v;
			}
		}
		son[u] = pos;
	};
	dfs(n);

	fenwick fw1(n),fw2(n);
	vector<ll> ctr(n+1);

	function<void(int,int,int)> add = [&](int u,int t,int op){
		if (op==0){
			ctr[t] += fw1.query_range(1,depth[u])-depth[t]*fw2.query_range(1,depth[u]);
			ctr[t] += (fw2.query_range(depth[u]+1,n)+fw2.query_range(depth[u],depth[u]))*(depth[u]-depth[t]);
		}
		else{
			fw1.update(depth[u],depth[u]);
			fw2.update(depth[u],1);
		}
	
		for (auto& v:adj[u]){
			add(v,t,op);
		}
	};

	function<void(int)> sub = [&](int u){
		fw1.update(depth[u],-depth[u]);
		fw2.update(depth[u],-1);
		for (auto& v:adj[u]){
			sub(v);
		}
	};

	function<void(int,int)> dfs2 = [&](int u,int keep){
		for (auto& v:adj[u]){
			if (v!=son[u]){
				dfs2(v,0);
				ctr[u] += ctr[v];
			}
		}
		if (son[u]!=-1){
			dfs2(son[u],1);
			ctr[u] += ctr[son[u]];
		}

		for (auto& v:adj[u]){
			if (v!=son[u]){
				add(v,u,0);
				add(v,u,1);
			}
		}	
		ctr[u] += fw1.query_range(1,depth[u])-depth[u]*fw2.query_range(1,depth[u]);
		fw1.update(depth[u],depth[u]);
		fw2.update(depth[u],1);

		if (keep==0){
			sub(u);
		}
	};
	dfs2(n,1);

	cout << ctr[n] << '\n';
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	int t = 1;
	cin >> t;
	while (t--) solve();

	return 0;
}
posted @ 2026-07-05 21:52  kzssCCC  阅读(8)  评论(0)    收藏  举报