1. 程式人生 > 其它 >【luogu CF1119F】【luogu P7600】Niyaz and Small Degrees / 封閉道路

【luogu CF1119F】【luogu P7600】Niyaz and Small Degrees / 封閉道路

Niyaz and Small Degrees / 封閉道路

題目連結:luogu CF1119F / luogu P7600

題目大意

給一棵樹,邊有邊權,是砍掉的費用。
對於每個 x,詢問最少要花費費用砍邊,使得每個點都不與超過 x 個點相連。

思路

首先看到題目容易想到要 DP。
那我們就想想 \(n^2logn\) 的 DP,我們列舉 \(x\),然後搞 \(O(nlogn)\) DP。

那就是設 \(f_{i,0/1}\)\(i\) 與父親連邊刪或不刪時以 \(i\) 為根的子樹的點都滿足度數不大於 \(x\) 的最小費用。
在度數沒有限制的時候,那對於 \(u\) 的兒子 \(v\),之間邊權為 \(w\)

,有 \(f_{u,0}=f_{u,1}=\min\{f_{v,0},f_{v,1}+w\}\)

那如果有了度數限制,那我們就要想,你選左邊就是割,右邊就是不割。
那對於 \(f_{u,0}\),它就一定要選 \(du_u-x\) 個左邊的。(\(du_u\)\(u\) 的度數)
那對於 \(f_{u,1}\),它就一定要選 \(du_u-x-1\) 個左邊的。
那容易想到,如果本來就是左邊優,那我們肯定選左邊。那我們可以提前選,然後記得要選的個數要減。
那如果是右邊優,那我們就看如果硬要選左邊會虧多少(\(f_{v,1}+w-f_{v,0}\)),那我們肯定優先硬改虧的最少的,那我們可以用堆來找到前 \(du_u-x\)

個的總費用。
那就可以了。

那我們考慮怎麼優化。
不難想到,如果 \(x\geq du_u\),那 \(u\) 這個點割不割都無所謂,那我們可以把這個點刪掉。
但不能把別的點入度修改,而且別的點就相當於有一個費用是 \(w\)\(w\) 是它與這個點的邊的邊權)放進了堆裡面。而且這個是一隻存在的,不像我們上面演算法的堆,那些要清空,而這些要留下。

那我們就要弄一個可刪堆,可以用兩個普通的堆來維護。
(大概就是你要刪你就不直接刪而是放進刪除堆,每次你要取之前如果你的普通堆和刪除堆堆頂相同就把它彈出不要,即我們不立刻刪掉,而是阻礙到再刪)
(具體是看看程式碼)

那我們接著繼續想,那每次列舉 \(x\)

,DP 的規模就不是 \(nlogn\),而是 \(mlogm\)\(m\) 時度數超過 \(x\) 的點數)
那這個所有 \(m\) 加起來的規模其實是 \(O(n)\) 級別的,因為你想,入度和是 \(2n\),那一個點入度是 \(x\) 就會貢獻 \(x\) 次,那總共就最多貢獻 \(2n\) 次,所以 \(m\) 就是 \(O(n)\) 級別。
複雜度也就變成了 \(O(nlogn)\)

具體的實現可以看看程式碼。

程式碼

由於兩道題是一模一樣的,而且 APIO 由於那個互動程式碼會有點亂,這裡就只在 CF 的那個上面寫註釋。

然後記得兩個的點編號一個是 \(1\sim n\),一個是 \(0\sim n- 1\)

Niyaz and Small Degrees

#include<queue>
#include<cstdio>
#include<vector>
#include<algorithm>
#define ll long long

using namespace std;

int n, x, y, z, du[250001], xx[250001];
int nd, kil, in[250001];
vector <pair<int, int> > e[250001];
ll sum, f[250001][2], re;
vector <ll> ans, tmp, del;

struct cd_heap {//可刪堆
	priority_queue <int> q1, q2;
	ll sum;
	void insert(int x) {q1.push(x); sum += x;}
	void delete_(int x) {q2.push(x); sum -= x;}
	void ck() {while (!q1.empty() && !q2.empty() && q1.top() == q2.top()) q1.pop(), q2.pop();}
	int top() {ck(); return q1.top();}
	void pop() {ck(); sum -= q1.top(); q1.pop();}
	int size() {return q1.size() - q2.size();}
}h[250001];

void add(int x, int y, int z) {
	e[x].push_back(make_pair(y, z));
	du[x]++;
}

bool cmp0(pair <int, int> x, pair <int, int> y) {
	return du[x.first] > du[y.first];
	//優化,列舉相連的點時按相連點入度從大到小列舉
	//如果列舉到已被刪去,那後面的點都被刪了,就可以直接退出了
}

bool cmp(int x, int y) {
	return du[x] < du[y];
}

void kill(int x) {//刪點
	for (int i = 0; i < e[x].size(); i++){
		int y = e[x][i].first, z = e[x][i].second;
		if (du[y] <= nd) break;
		h[y].insert(z);//相連的邊要留著,相當於單獨費用是 z 的(因為不砍不會有花費,z-0=z)
	}
}

void dfs(int now, int father) {
	in[now] = nd;
	int num = du[now] - nd;//看要砍多少邊
	while (h[now].size() > num)//維護堆只有這麼多個邊(下同)
		h[now].pop();
	
	for (int i = 0; i < e[now].size(); i++) {//dfs 遞迴 DP
		int to = e[now][i].first;
		if (du[to] <= nd) break;
		if (to == father) continue;
		dfs(to, now);
	}
	
	tmp.clear(); del.clear();//記得清空
	for (int i = 0; i < e[now].size(); i++) {
		int to = e[now][i].first, dis = e[now][i].second;
		if (du[to] <= nd) break;
		if (to == father) continue;
		ll x = f[to][1] + dis - f[to][0];
		if (x <= 0) {//明顯是不割優
			re += f[to][1] + dis;
			num--;//這個直接處理乘割了,那要割的數量就少了
		}
		else {
			re += f[to][0];
			del.push_back(x);//把這裡加進堆的記錄下來,因為只有這一次可以用,那我們搞完要刪掉它們
			h[now].insert(x);
		}
	}
	
	//tmp 是存丟出去的,由於也是隻是這一次用,那我們到時還要放回去,丟出去只是為了統計費用
	while (h[now].size() && h[now].size() > num)
		tmp.push_back(h[now].top()), h[now].pop();
	f[now][0] = h[now].sum;//不刪父親的
	while (h[now].size() && h[now].size() > num - 1)//刪父親就代表要多刪一條
		tmp.push_back(h[now].top()), h[now].pop();
	f[now][1] = h[now].sum;//刪父親的
	
	for (int i = 0; i < tmp.size(); i++)//記得把要還原的還原,把要刪的刪了
		h[now].insert(tmp[i]);
	for (int i = 0; i < del.size(); i++)
		h[now].delete_(del[i]);
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i < n; i++) {
		scanf("%d %d %d", &x, &y, &z);
		add(x, y, z);
		add(y, x, z);
		sum += z;
	}
	
	ans.push_back(sum);
	for (int i = 1; i <= n; i++) {
		sort(e[i].begin(), e[i].end(), cmp0);
		xx[i] = i;
	}
	
	sort(xx + 1, xx + n + 1, cmp);
	
	kil = 1;
	while (++nd < n) {
		while (kil <= n && nd == du[xx[kil]]) kill(xx[kil]), kil++;//有新的可以直接刪掉的點
		if (kil > n) {//所有點都被刪掉了,後面答案都是 0
			ans.push_back(0);
			continue;
		}
		
		re = 0;
		for (int i = kil; i <= n; i++) {//DP,記得要列舉每個樹
			if (in[xx[i]] == nd) continue;
			dfs(xx[i], 0);	
			re += f[xx[i]][0];
		}
		
		ans.push_back(re);
	}
	
	for (int i = 0; i < ans.size(); i++)
		printf("%lld ", ans[i]);
	
	return 0;
}

封閉道路

#include<queue>
#include<cstdio>
#include<vector>
#include<algorithm>
#define ll long long

using namespace std;

int n, x, y, z, du[250001], xx[250001];
int nd, kil, in[250001];
vector <pair<int, int> > e[250001];
ll sum, f[250001][2], re;
vector <ll> ans, tmp, del;

struct cd_heap {
	priority_queue <int> q1, q2;
	ll sum;
	void insert(int x) {q1.push(x); sum += x;}
	void delete_(int x) {q2.push(x); sum -= x;}
	void ck() {while (!q1.empty() && !q2.empty() && q1.top() == q2.top()) q1.pop(), q2.pop();}
	int top() {ck(); return q1.top();}
	void pop() {ck(); sum -= q1.top(); q1.pop();}
	int size() {return q1.size() - q2.size();}
}h[250001];

void add(int x, int y, int z) {
	e[x].push_back(make_pair(y, z));
	du[x]++;
}

bool cmp0(pair <int, int> x, pair <int, int> y) {
	return du[x.first] > du[y.first];
}

bool cmp(int x, int y) {
	return du[x] < du[y];
}

void kill(int x) {
	for (int i = 0; i < e[x].size(); i++){
		int y = e[x][i].first, z = e[x][i].second;
		if (du[y] <= nd) break;
		h[y].insert(z);
	}
}

void dfs(int now, int father) {
	in[now] = nd;
	int num = du[now] - nd;
	while (h[now].size() > num)
		h[now].pop();
	
	for (int i = 0; i < e[now].size(); i++) {
		int to = e[now][i].first;
		if (du[to] <= nd) break;
		if (to == father) continue;
		dfs(to, now);
	}
	
	tmp.clear(); del.clear();
	for (int i = 0; i < e[now].size(); i++) {
		int to = e[now][i].first, dis = e[now][i].second;
		if (du[to] <= nd) break;
		if (to == father) continue;
		ll x = f[to][1] + dis - f[to][0];
		if (x <= 0) {
			re += f[to][1] + dis;
			num--;
		}
		else {
			re += f[to][0];
			del.push_back(x);
			h[now].insert(x);
		}
	}
	
	while (h[now].size() && h[now].size() > num)
		tmp.push_back(h[now].top()), h[now].pop();
	f[now][0] = h[now].sum;
	while (h[now].size() && h[now].size() > num - 1)
		tmp.push_back(h[now].top()), h[now].pop();
	f[now][1] = h[now].sum;
	
	for (int i = 0; i < tmp.size(); i++)
		h[now].insert(tmp[i]);
	for (int i = 0; i < del.size(); i++)
		h[now].delete_(del[i]);
}

std::vector<long long> minimum_closure_costs(int N, std::vector<int> U,
                                             std::vector<int> V,
                                             std::vector<int> W) {
    n = N;
	for (int i = 1; i < n; i++) {
		x = U[i - 1] + 1;
		y = V[i - 1] + 1;
		z = W[i - 1];
		add(x, y, z);
		add(y, x, z);
		sum += z;
	}
	
	ans.push_back(sum);
	for (int i = 1; i <= n; i++) {
		sort(e[i].begin(), e[i].end(), cmp0);
		xx[i] = i;
	}
	
	sort(xx + 1, xx + n + 1, cmp);
	
	kil = 1;
	while (++nd < n) {
		while (kil <= n && nd == du[xx[kil]]) kill(xx[kil]), kil++;
		if (kil > n) {
			ans.push_back(0);
			continue;
		}
		
		re = 0;
		for (int i = kil; i <= n; i++) {
			if (in[xx[i]] == nd) continue;
			dfs(xx[i], 0);	
			re += f[xx[i]][0];
		}
		
		ans.push_back(re);
	}
	
	return ans;
}