【貪心】AcWing 115. 給樹染色
阿新 • • 發佈:2022-01-06
傳送門:https://www.acwing.com/solution/content/82014/
分析
假如沒有染色順序的約束,那麼最佳決策當然是先染權值大的點(本質上就是排序不等式)。
然而現在它有約束,但我們可以保證的一點是:當樹上最大的點 \(u\) 的父節點 \(p\) 被染色的時候,立刻將 \(u\) 進行染色是最優的:這意味著,我們可以將其合併成一個新的點,等價於染色步驟中連續對這兩個點進行染色。
那麼這個新點的權值應該是多少呢?考慮現在 \(X,Y\) 點組成一個合併的點(對應上述的 \(p, u\))\(X\) 可以被染色,然後 \(Z\) 點也是當前可以被染色的點。(記它們點權為自己的小寫字母)
假設現在已經染了 \(k\) 個點
- 如果先染 \((X, Y)\),那麼貢獻是 \((k+1)x + (k+2)y + (k+3)z\)。
- 如果先染 \(Z\),貢獻為 \((k+1)z + (k+2)x + (k+3)u\)
也就是合併的 \((X, Y)\) 和 \(Z\) 染色的優先順序取決於兩個貢獻的大小。
\((k+1)x + (k+2)y + (k+3)z < (k+1)z + (k+2)x + (k+3)y\) 等價於 \((x + y)/2 > z\),即 \((x + y)/2 > z\) 時選擇先染 \(Z\) 否則先染 \((X,Y)\)。
據此,將一個新點
實現
y總
的做法是利用偏移量進行統計,感覺不太好想 qwq,所以我採取的是將合併的過程進行模擬,並記錄相關資訊,最後對剩下那個點的資訊進行展開得到決策序列(有點像解壓縮)。
寫這題的時候心很慌,感覺自己的實現挺亂的(感覺就我是這樣亂搞的),沒想到能 1A。如果是因為資料水了可以來試試 hack qwq。
看程式碼:
// Problem: 給樹染色
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/117/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
#define debug(x) cerr << #x << ": " << (x) << endl
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define dwn(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
#define all(x) (x).begin(), (x).end()
using pii = pair<int, int>;
using ll = long long;
inline void read(int &x){
int s=0; x=1;
char ch=getchar();
while(ch<'0' || ch>'9') {if(ch=='-')x=-1;ch=getchar();}
while(ch>='0' && ch<='9') s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
x*=s;
}
const int N=2022;
int n, rt;
int w[N];
struct Node{
int fa, sz, sum;
double avg;
}e[N];
int tot;
int h[N], ne[N]; // 記 cur=(p, u), 那麼 h[cur]=p, ne[cur]=u
bool vis[N], inr[N]; // 分別代表:是否已經被併入新點,是否在根節點所在的新點中
int find(){
int u; double val=0;
rep(i,1,tot) if(!vis[i] && !inr[i] && e[i].avg>val) val=e[i].avg, u=i;
return u;
}
vector<int> get(int cur){ // 解壓縮
int p=h[cur], u=ne[cur];
vector<int> v1, v2;
if(p>n) v1=get(p);
else v1={p};
if(u>n) v2=get(u);
else v2={u};
v1.insert(end(v1), all(v2));
return v1;
}
vector<int> g[N];
void getFa(int u, int fa){
e[u].fa=fa;
for(auto go: g[u]) if(go!=fa) getFa(go, u);
}
int main(){
cin>>n>>rt;
rep(i,1,n) read(w[i]), e[i].sum=e[i].avg=w[i], e[i].sz=1;
inr[rt]=1;
rep(i,1,n-1){
int u, v; read(u), read(v);
g[u].pb(v), g[v].pb(u);
}
getFa(rt, 0); // 將每個點的父親處理出來
tot=n;
rep(_,1,n-1){
int u=find(), p=e[u].fa; // 找到 u 以及 p
vis[u]=vis[p]=1; // 標記為已合併
tot++;
e[tot]={e[p].fa, e[u].sz+e[p].sz, e[u].sum+e[p].sum, (double)(e[u].sum+e[p].sum)/(e[u].sz+e[p].sz)};
h[tot]=p, ne[tot]=u;
if(inr[p]) inr[u]=inr[tot]=1;
rep(i,1,tot-1) if(e[i].fa==u || e[i].fa==p) e[i].fa=tot; // 維護髮生修改的每個點父節點資訊
}
auto vec=get(tot); // 從合併得到的最後的新點(此時樹上只剩一個點)開始解壓縮得到合併的操作序列。
int res=0;
rep(i,1,n) res+=i*w[vec[i-1]];
cout<<res<<endl;
return 0;
}