[ USACO 2001 OPEN ] 地震

\(\\\)

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;
}

posted @ 2018-11-06 21:32  SGCollin  阅读(205)  评论(0编辑  收藏  举报