1 条题解

  • 0
    @ 2026-4-23 16:54:27

    这是由 AI 翻译为中文文本的官方题解

    子任务 1

    确定首都之后的操作,即为寻找带权无向图的最小生成树的算法,被称为“Prim 算法”。Prim 算法通过使用 priority queue 等数据结构,可以在单次 O(MlogM)O(M \log M) 的时间复杂度内执行。将此操作对 NN 种首都情况分别进行,计算复杂度为 O(NMlogM)O(NM \log M)

    子任务 2

    由于 Prim 算法是求解最小生成树的算法,如果存在某条边使得有新的顶点变得可达,那么这条边必定包含在最小生成树中。因此,可以忽略不包含在最小生成树中的边。首先求出最小生成树,然后在该图上改变起点为 NN 种情况执行 Prim 算法,计算复杂度变为 O(N2logN)O(N^2 \log N)

    子任务 3

    ss 出发可达的岛屿集合始终构成一个区间。 考虑 s<is < i 的情况,当从 ss 可以到达 ii 时,存在某个 jjjsj \le s),使得从 jjii 的区间都是可达的。这个 jj 的值是,设 ssii 路径上边的最大权值为 mm,从 ss 开始向编号较小的方向查看时,第一次出现权值大于 mm 的边的位置。固定 ii,当 ss 的编号逐渐减小时,jj 的编号也会同样逐渐减小。因此,利用尺取法(双指针)的要领,可以依次求出 s=i1,i2,,1s = i - 1, i - 2, \dots, 1 对应的 jj 的值。利用同样的方法也可以计算 s=i+1,i+2,,Ns = i + 1, i + 2, \dots, N 的情况,计算复杂度为 O(NQ)O(NQ)

    子任务 4, 5,满分解法

    考虑与 Prim 算法齐名的另一种求解最小生成树的算法——Kruskal 算法。

    假设在 Kruskal 算法的某一步中,连通分量 AA 和连通分量 BB 通过连接 AA 中顶点 xxBB 中顶点 yy 的边 (x,y)(x, y) 进行合并。此时,对于属于 AA 的顶点 aa 和属于 BB 的顶点 bb,可以确定 Da,b=A+Dy,bD_{a,b} = |A| + D_{y,b}。在这里,对于顶点 bb 的不便度 D1,b+D2,b++DN,bD_{1,b} + D_{2,b} + \dots + D_{N,b} 的表达式,通过反复进行前面等式所表示的代入操作,最终可以将其表示为 A1+A2+|A_1| + |A_2| + \dots 的形式。(请注意 Db,b=0D_{b,b} = 0。)可以发现,在这个表达式中 A|A| 出现的次数,等于在边 (x,y)(x, y) 处切断最小生成树时,位于 xx 一侧的顶点总数 kk。因此,只需对包含在 BB 中的所有顶点 bb 的得分统一加上 A×k|A| \times k 即可。同理,对包含在 AA 中的所有顶点 aa 的得分也要加上 B×(Nk)|B| \times (N - k)

    当图是一条路径时,这个加法操作相当于区间加法。因为只需要在最后求出结果即可,所以可以使用 imos 法(差分法)。 对于一般的图,这个加法操作对应于在“表示合并过程的树(Kruskal 重构树)”中,对子树的所有节点进行加法的操作。因为只需要在最后求出结果即可,所以只需将该值记录在子树的根节点上,之后一边将这些值累加,一边自顶向下地向子节点传递即可。

    在计算复杂度方面,求解最小生成树时的边排序过程成为了瓶颈,为 O(MlogM)O(M \log M)

    参考代码:

    #include<bits/stdc++.h>
    using namespace std;
    #define rep(i,n) for(int i = 0;i < (n);i++)
    #define eb emplace_back
    #define si(x) (ll)x.size()
    #define all(x) x.begin(),x.end()
    #define pll pair<ll,ll>
    #define vll vector<ll>
    #define inf LLONG_MAX / 3
    template<class T> bool chmin(T& a, const T& b){ if(a <= b) return 0; a = b; return 1; }
    template<class T> bool chmax(T& a, const T& b){ if(a >= b) return 0; a = b; return 1; }
    typedef long long ll;
    
    struct uf{
    	vll p;
    	void init(ll n){
    		p.resize(n);
    		rep(i,n)p[i] = i;
    	}
    	ll par(ll x){
    		return p[x] = (x == p[x] ? x : par(p[x]));
    	}
    	bool same(ll a,ll b){
    		a = par(a);
    		b = par(b);
    		return a == b;
    	}
    	void unite(ll a,ll b){
    		a = par(a);
    		b = par(b);
    		p[a] = b;
    	}
    };
    
    int main(){
    	cin.tie(0);
    	ios::sync_with_stdio(0);
    	ll n,m,q;
    	cin>>n>>m>>q;
    	vector<tuple<ll,ll,ll> >edges;
    	rep(i,m){
    		ll a,b,c;
    		cin>>a>>b>>c;
    		a--,b--;
    		edges.eb(c,a,b);
    	}
    	sort(all(edges));
    
    	vector<pll>tedges;
    	vector<vll> t(n);
    	vll szt(n);
    	auto gawa = [&](ll x,ll y){
    
    		if(szt[x] > szt[y]){
    			return n - szt[y];
    		}
    		return szt[x];
    	};
    	{
    		uf uf;
    		uf.init(n);
    		for(auto &&[c,a,b] : edges){
    			if(uf.same(a,b))continue;
    			uf.unite(a,b);
    			tedges.eb(a,b);
    			t[a].eb(b);
    			t[b].eb(a);
    		}
    		function<ll(ll,ll)> dfs = [&](ll x,ll frm){
    			szt[x] = 1;
    			for(auto &&y: t[x])if(y != frm){
    				szt[x] += dfs(y,x);
    			}
    			return szt[x];
    		};
    		dfs(0, -1);
    	}
    
    	ll id = n;
    	vll ad(n,0),p(n),sz(n, 1);
    	vll p_star(n);
    	rep(i,n)p[i] = p_star[i] = i;
    	function<ll(ll)> par = [&](ll x){
    		return p_star[x] = (p_star[x] == x ? x : par(p_star[x]));
    	};
    
    	for(auto &&[x,y] : tedges){
    		ll a = par(x);
    		ll b = par(y);
    		ad[b] += sz[a] * gawa(x,y);
    		ad[a] += sz[b] * gawa(y,x);
    		p[a] = p_star[a] = p[b] = p_star[b] = id;
    		p.eb(id);
    		p_star.eb(id);
    		ad.eb(0);
    		sz.eb(sz[a] + sz[b]);
    		id++;
    	}
    
    	for(int i = id - 1;i >= 0; i--){
    		ad[i] += ad[p[i]];
    	}
    
    	while(q--){
    		ll x;
    		cin>>x;
    		x--;
    		cout<<ad[x]<<"\n";
    	}
    }
    
    • 1

    信息

    ID
    9658
    时间
    2000ms
    内存
    1024MiB
    难度
    10
    标签
    递交数
    2
    已通过
    1
    上传者