[ USACO 2001 OPEN ] 地震
阿新 • • 發佈:2018-11-07
define clas 最小 iostream 無向圖 ace sum add usaco
簡單化簡(註意把 \(x\) 放進求和號這一步)
\[ f\ge \sum_{e_i\in E}(w_i+t_ix) \]
這個顯然就是一個最小生成樹的形式了。
\(\\\)
Description?
給出一張 \(n\) 個點 \(m\) 條邊的無向圖,現在要建一棵生成樹。
每條邊都有消耗的時間 \(t_i\),也有建造的代價 \(w_i\) 。
最後總金給了 \(f\) 元,求單位時間的利潤最大能是多大。
總時間 \(=\) 建造每一條邊的時間之和
總利潤 \(=f-\) 建造每一條邊的代價之和。
- \(n\le 400,m\le 10^4,f,w_i,t_i\le 2\times 10^9\)
\(\\\)
Solution
\(01\) 分數規劃。二分單位利潤可能的值。
假設 \(x\) 為當前二分的利潤,\(E\) 為一種合法的生成樹的邊集。
\[
\frac{f-\sum_{e_i\in E}w_i}{\sum_{e_i\in E}t_i}\ge x
\]
簡單化簡(註意把 \(x\) 放進求和號這一步)
\[ f\ge \sum_{e_i\in E}(w_i+t_ix) \]
這個顯然就是一個最小生成樹的形式了。
每次重新計算每一條邊的邊權,然後 \(MST\) 驗證一下和是否小於 \(f\) 。
千萬不要忘了更新的時候是重新排過序的,如果跑 \(MST\) 用的邊集與原邊是獨立的,記得更新每條邊的端點。
\(\\\)
Code?
#include<cmath> #include<cstdio> #include<cctype> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 100010 #define R register #define gc getchar using namespace std; inline int rd(){ int x=0; bool f=0; char c=gc(); while(!isdigit(c)){if(c=='-')f=1;c=gc();} while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();} return f?-x:x; } int n,m,tot,hd[N],p[N],ans[N]; struct edge{int to,nxt;}e[N<<1]; inline void add(int u,int v){ e[++tot].to=v; e[tot].nxt=hd[u]; hd[u]=tot; } struct BIT{ int c[N]; inline int lowbit(int x){return x&-x;} inline void add(int p,int x){for(;p<=n;p+=lowbit(p))c[p]+=x;} inline int sum(int p){ int res=0; for(;p;p-=lowbit(p)) res+=c[p]; return res; } }bit; inline void dfs(int u,int fa){ ans[p[u]]=bit.sum(p[u]); bit.add(p[u],1); for(R int i=hd[u],v;i;i=e[i].nxt) if((v=e[i].to)!=fa) dfs(v,u); bit.add(p[u],-1); } int main(){ n=rd(); for(R int i=1,u,v;i<n;++i){ u=rd(); v=rd(); add(u,v); add(v,u); } for(R int i=1;i<=n;++i) p[rd()]=i; dfs(1,0); for(R int i=1;i<=n;++i) printf("%d\n",ans[i]); return 0; }
[ USACO 2001 OPEN ] 地震