[APIO2021] 封闭道路 T3简要题解

感觉是整场考试中思维最简单的一题

考场写了\(O(n(\sqrt n + \log n))\),在luogu和uoj都是73,然后可能是因为没打.h,在CCF上爆零了,然后喜提铜牌

此处做法的复杂度是\(O(n\log n)\)的,在luogu上跑了440ms左右,还是挺快的

\(solution\)

  • 首先我们考虑朴素\(dp\)怎么做,方便起见,以下我们都计算允许每个点保留\(k\)条边,最大的边权和,容易发现这个和答案刚好是互补的,最后用总数减以下即可

  • 我们注意到每个\(k\)之间,\(dp\)是没有关联的,考虑\(dp1_u\),为\(u\)的子树内(注意这里不考虑与父亲节点的边是否选择),所有点都选了不超过\(k\)条边,\(u\)选了\(k\)条边的最优方案,\(dp2_u\)表示\(u\)的子树内,所有点都选了不超过\(k\)条边,\(u\)选了k-1条边的最优方案,这里可以理解为剩下的那条边要连到父亲

  • 考虑如何转移,我们考虑贪心的转移,首先,假设我们一条边都不选,答案显然是\(\sum_{v \in son(u)}{dp1_v}\),接下来我们有k次反悔的机会,每次可以选一条边,那么贡献就是\(w + dp2_v - dp1_v\),我们设它为\(f_v\),那么\(dp1_u = max(\sum_{v \in son(u)}{dp1_v} + [至多k个f_v])\),\(dp2_u\)则是至多\(k-1\)个\(f_v\)

  • 容易用排序做到\(O(n^2\log)\)

  • 考虑单\(\log\)怎么做,我们不妨先分析点性质

  • 定义:假设我们当前在计算允许保留\(k\)条边,一个点被定义为无用的,当且仅当\(degree(v)(以下简称deg_v) \leq k\)

  • 首先我们注意到无用点和无用点之间的边我们无需决策,是一定可以选的,这部分贡献我们可以事先用差分来预处理

for(int u = 0; u < N; ++u)
    for(auto v:E[u]){
        int o = max(deg[u],deg[v]);
        F[o] += w; 
    }
for(int i = 1; i < N; ++i)  F[i] += F[i-1];
  • 考虑有用点\(u\)和无用点\(v\)之间的决策,我们发现这部分决策是平凡的,因为如果一个点成为了无用点,那么在之后,其贡献将一直不变,保持为\(w(u,v)\),那么我们可以考虑用一个堆来维护,那么我们每次可以只计算有用点和有用点之间的贡献,不够的再直接用无用点的堆的前缀和来补充
  • 此部分代码如下(注意此处无需考虑父子关系)
void _delete(int u){
	for(auto now:E[u]){
		int v = now.fi,w = now.se;
		q[v].push(-w);Sw[v] += w;
	}
}
  • 那么我们可以发现,对于每个\(k\),我们只需保留\(deg \ge k\)的点形成的森林来遍历,我们可以先在邻接表上按度数排序,用vector的pop_back和双指针来删除点,这样做的复杂度看起来很暴力,但是实际上我们可以分析,一个点至多只会被遍历度数次,而所有的点的度数和是线性级别的,加上堆和排序的复杂度,总复杂度就是\(O(n \log n)\)
  • 有一些细节,比如如何计算堆的前\(k\)大的和?如果嫌麻烦,可以直接暴力权值线段树,反正空间1G,假设当前计算到\(k\),我们可以让堆只保留前\(k\)大的,剩下的先弹进另一个堆里(这里可以叫做垃圾堆),待更新到\(k+1\)时,再尝试从垃圾堆中拿出最大的来更新堆,这样的话,对于每个点,都有堆大小\(\leq\)度数,而根据我们之前的分析,总度数和是线性,那么加上堆的复杂度就是单log的
  • 时间复杂度\(O(n\log n)\),空间复杂度\(O(n)\)
  • 代码如下
#include<bits/stdc++.h>
std::vector<long long> minimum_closure_costs(int N, std::vector<int> U,
                                             std::vector<int> V,
                                             std::vector<int> W);
using namespace std;
int read(){
	char c = getchar();
	int x = 0;
	while(c < '0' || c > '9')	c = getchar();
	while(c >= '0' && c <= '9')	x = x * 10 + c - 48,c = getchar();
	return x;
}
const int _ = 1e5 + 7;
int deg[_];int n;
#define ll long long
#define mp make_pair
#define fi first
#define se second
#define pb push_back 
typedef pair<int,int> pii;
vector<pii>E[_];
vector<ll>F;
vector<int>Ver;
bool cmp(pii a,pii b){
	return deg[a.fi] > deg[b.fi];
}
bool Cmp(int a,int b){
	return deg[a] < deg[b];
}
priority_queue<ll>q[_],bq[_];ll Sw[_];
int dfn[_],dfncnt;
void dfs1(int u,int fa){
	for(auto now:E[u]){
		int v = now.fi,w = now.se;
		if(v ^ fa){
			int o = max(deg[u],deg[v]);
			F[o] += w;
//			cout<<o<<" "<<w<<'\n'; 
			dfs1(v,u); 
		}
	}
}
ll dp1[_],dp2[_];
int st[_],top,bac[_],bcnt;
void _delete(int u){
	for(auto now:E[u]){
		int v = now.fi,w = now.se;
		q[v].push(-w);Sw[v] += w;
	}
}
void work(int u,int par,const int D){
	dfn[u] = D;
	dp1[u] = dp2[u] = 0;/*dp1[u]取D个,dp2[u]取D-1个*/
	for(auto now:E[u]){
		int v = now.fi;
		if(v ^ par){
//			cout<<u<<' '<<v<<"E"<<'\n';
			work(v,u,D);
		}
	}
	ll ans = 0;
	top = 0;
	for(auto now:E[u]){
		int v = now.fi,w = now.se;
		if(v ^ par){
			ans += dp1[v];
			if(w > (dp1[v] - dp2[v])){
				st[++top] = w - (dp1[v] - dp2[v]);
			}
		}
	}
	sort(st + 1,st + top + 1);
	reverse(st + 1,st + top + 1);
	ll S1 = 0,S2 = Sw[u];
	dp1[u] = Sw[u];dp2[u] = Sw[u];
//	if(D == 2)	cout<<Sw[u]<<'\n';
	assert(q[u].size() <= (unsigned int)D);
	if((int)q[u].size() == D)	dp2[u] += q[u].top();
//	cout<<u<<'\n';
	bcnt = 0;
//	cout<<u<<'\n';
	for(int i = 1; i <= min(top,D); ++i){/*选i个有用点,D-i个无用点*/
		S1 += st[i];
		if((int)q[u].size() > D - i){
			bac[++bcnt] = q[u].top();
			S2 += bac[bcnt];
			q[u].pop();
		}
		dp1[u] = max(dp1[u],S2 + S1);
		if(i == D)		continue;
		ll V = S2 + S1;if((int)q[u].size() >= D - i){
			V += q[u].top(); 
		}
		dp2[u] = max(dp2[u],V);
	}
//	cout<<u<<'\n';
	while(bcnt)	q[u].push(bac[bcnt]),bcnt--;
//	if(D == 2 && u == 1)	cout<<dp1[u]<<'\n';
	dp1[u] += ans;dp2[u] += ans;
	if(!par)	F[D] += dp1[u]; 
}
//vector<int> U;vector<int> V;vector<int> W;
vector<ll> minimum_closure_costs(int N,vector<int> U,vector<int> V,vector<int> W) {
	ll S = 0;
//	int N = read();
	n = N;
	/*for(int i = 0; i < N - 1; ++i){
		U.pb(0),V.pb(0),W.pb(0);
		U[i] = read(),V[i] = read(),W[i] = read();
	}*/
	for(int i = 1; i < N; ++i){
		int u = U[i-1] + 1,v = V[i-1] + 1,w = W[i-1];
//		u--,v--;
		deg[u]++,deg[v]++;
		S += w;
		E[u].pb(mp(v,w));E[v].pb(mp(u,w));
	}
	for(int i = 1; i <= N; ++i)	F.pb(0);
	for(int i = 1; i <= N; ++i)	Ver.pb(i);
	sort(Ver.begin(),Ver.end(),Cmp);Ver.pb(0);
	dfs1(1,0);
	for(int i = 1; i <= N; ++i)	sort(E[i].begin(),E[i].end(),cmp);
	for(int i = 1; i < N; ++i)	F[i] += F[i-1];
	int l = 0;
//	for(int i = 0; i < N; ++i)	cout<<Ver[i]<<' ';
//	cout<<'\n';
	for(int i = 1; i < N; ++i){
		while(l <= N && deg[Ver[l]] <= i){
			_delete(Ver[l]);
			l++;
		}
//		cout<<i<<' '<<l<<'\n';
		for(int ver = l; ver < N; ++ver){
			int u = Ver[ver];
			int k = E[u].size() - 1;
			while(k >= 0 && deg[E[u][k].fi] <= i){
				E[u].pop_back();
				k--;
			}
		}
//		puts("hxsb");
		for(int ver = l; ver < N; ++ver){
			int u = Ver[ver];
			while((int)q[u].size() > i){
				ll x = -q[u].top();
				q[u].pop();Sw[u] -= x;
				bq[u].push(x);
			}
			if((int)q[u].size() < i){
				if((int)bq[u].size()){
					ll x = bq[u].top();
					bq[u].pop();q[u].push(-x);Sw[u] += x;
				}
			}
			else{
				if((int)bq[u].size()) {
					ll x = bq[u].top();ll y = -q[u].top();
					if(x > y){
						q[u].pop();bq[u].pop();
						Sw[u] -= y;Sw[u] += x;
						q[u].push(-x);bq[u].push(y);
					}
				}
			}
		} 
		for(int ver = l; ver < N; ++ver){
			int u = Ver[ver];
			if(dfn[u] != i)	work(u,0,i);
		}
	}
	for(int i = 0; i < N; ++i)	F[i] = S - F[i];
//	for(int i = 0; i < N; ++i)	cout<<F[i]<<' ';
	return F;
}
/*int main(){
	freopen("data.in","r",stdin);
	freopen("data.out","w",stdout);
	minimum_closure_costs();
	return 0;
}*/
上一篇:「NOI2019」退役记???


下一篇:查询某软件所连接的外网IP地址