bzoj4753[JSOI2016]最佳团体

题意:01分数规划,但可选的数字之间存在森林形的依赖关系(可以认为0号点是个虚根,因为并不能选).

虽然有森林形的依赖关系,但还是可以套分数规划的思路,二分答案k,判断是否存在一个比值大于k的方案

即是否存在一种选取方式使得sigma{fight[i],i is choosed}/sigma{cost[i],i is choosed}>=k

移项,发现只需要sigma{fight[i]-cost[i]*k,i is choosed}>=0,也就是把每个点的权值设置成”战斗力-花费*比值”,判断是否存在一种满足依赖关系的选取方案使得选择的权值之和>=0,那么让权值之和尽量大判定最大值是否大于等于0即可.定义f[i][j]表示i为根的子树中选取j个点时的最大权值,用背包暴力转移,看似是O(n^3)的,但仔细分析发现复杂度是O(n^2)的,因为每次合并一棵子树时付出的代价是”已经合并的兄弟子树的大小之和”*”正在合并的这棵子树的大小”,实质上是树上每对节点在LCA处贡献时间复杂度,这一部分相当于bzoj4033.

于是总体复杂度是O (log(ans)*N^2),n是2500,感觉很虚但是能跑过去…注意处理某棵子树如果选择那么子树的根节点必须选择,以及0号节点的处理.再有就是二分精度一定要调好.

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=2505;
struct edge{
  int to,next;
}lst[maxn];int first[maxn],len=1;
void addedge(int a,int b){
  lst[len].to=b;lst[len].next=first[a];first[a]=len++;
}
int k,n;
int cost[maxn],fight[maxn],prt[maxn],sz[maxn];
double w[maxn];
double f[maxn][maxn];
void dfs(int x){
  sz[x]=1;
  for(int pt=first[x];pt;pt=lst[pt].next){
    dfs(lst[pt].to);
    sz[x]+=sz[lst[pt].to];
  }
}
void dp(int x){
  int tot=0;
  if(x)f[x][1]=w[x],tot=1;
  else f[x][0]=0;
  for(int pt=first[x];pt;pt=lst[pt].next){
    dp(lst[pt].to);tot+=sz[lst[pt].to];
    for(int i=tot;i>=0;--i){
      for(int j=0;j<=sz[lst[pt].to]&&j<=i;++j){
    f[x][i]=max(f[x][i],f[x][i-j]+f[lst[pt].to][j]);
      }
    }
  }
  
}
bool check(double ans){
  for(int i=1;i<=n;++i){
    w[i]=fight[i]-ans*cost[i];
  }
  memset(f,0xc2,sizeof(f));
  dp(0);//printf("%.3f\n",f[2][1]);
  return f[0][k]>=0;
}
int main(){
  scanf("%d%d",&k,&n);
  for(int i=1;i<=n;++i){
    scanf("%d%d%d",&cost[i],&fight[i],&prt[i]);addedge(prt[i],i);
  }
  dfs(0);
  double l=0,r=1e4;
  while(r-l>1e-5){
    double mid=(l+r)/2;
    if(check(mid))l=mid;
    else r=mid;
  }
  printf("%.3f\n",(r+l)/2);
  return 0;
}

 

posted @ 2017-02-14 21:02  liu_runda  阅读(2490)  评论(3编辑  收藏  举报
偶然想到可以用这样的字体藏一点想说的话,可是并没有什么想说的. 现在有了:文化课好难