1. 程式人生 > 實用技巧 >[CodeForces][貪心][樹形結構]CF980E The Number Games

[CodeForces][貪心][樹形結構]CF980E The Number Games

題面

看到樹形結構我們可能會想到樹形 \(DP\),但是仔細研究過後我們會發現這題是不需要 \(DP\) 的。
觀察每個點的貢獻為 \(2^i\) ( \(i\) 為節點標號 ),也就是說,選擇一個標號更大的點一定比選擇幾個標號小的點優

這時我們貪心的思路就明朗了:
\(n\ -\ 1\) 遍歷每個點,被選擇的點之間的路徑都需要連線,只要一個點路徑上需要新增的點加起來不超過限制,就加入該點 ( 以及路徑上的點 )。

至於路徑,我們只需要用 \(DFS\) 序維護,然後用差分樹狀陣列暴力維護一下一個點到根節點的路徑長度即可

程式碼:

// 貪心的考慮:2^n 一定保留
// DFS 暴力跳點

# include <iostream>
# include <cstdio>
# define MAXN 1000005
# define fa(x) nd[x].fa
# define dep(x) nd[x].dep
# define siz(x) nd[x].siz
# define dfn(x) nd[x].dfn

struct edge{
	int v, next;
}e[MAXN<<1];
struct node{
	int fa, dep, siz, dfn;
}nd[MAXN];
int cntS, lim;
int hd[MAXN], cntE, bit[MAXN];
bool sel[MAXN]; int cntSel;

void AddE(int u, int v);
void DFS(int now, int fa);
int Lowbit(int x);
void Update(int pos, int val);
int GetSum(int pos); 

int main(){
	int n, k;

	scanf("%d%d", &n, &k);

	k = n - k; // 刪除點數轉化為保留點數

	lim = n;

	for(int i = 1, u, v; i <= n-1; i++){
		scanf("%d%d", &u, &v);
		AddE(u, v); AddE(v, u);
	}


	DFS(n, 0);
	sel[n] = 1;

	for(int i = n; i >= 1; i--){
		if((!sel[i]) && (dep(i)-GetSum(dfn(i))+cntSel  <= k)){ // 要求選擇當前點後新增點數不超過總保留點數
			for(int now = i; now != n; now = fa(now)){
				if(sel[now]){
					break;
				}
				else{
					sel[now] = 1;
					Update(dfn(now), 1); Update(dfn(now)+siz(now), -1); // 差分
					cntSel++;
				}
			}
		}
	}

	for(int i = 1; i <= n; i++){
		if(!sel[i]){
			printf("%d ", i);
		}
	}

	return 0;
}

int GetSum(int pos){
	int ans = 0;
	while(pos){
		ans += bit[pos];
		pos -= Lowbit(pos);
	}
	return ans;
}

void Update(int pos, int val){
	while(pos <= lim){
		bit[pos] += val;
		pos += Lowbit(pos);
	}
}

int Lowbit(int x){
	return x & (-x);
}

void DFS(int now, int fa){
	fa(now) = fa, dep(now) = dep(fa) + 1, siz(now) = 1, dfn(now) = ++cntS;

	for(int i = hd[now]; i; i = e[i].next){
		if(e[i].v == fa){
			continue;
		}

		DFS(e[i].v, now);
		siz(now) += siz(e[i].v);
	}
}

void AddE(int u, int v){
	e[++cntE] = (edge){v, hd[u]};
	hd[u] = cntE;
}