Codeforces444E DZY Loves Planting

来源:互联网 发布:java零基础看什么书 编辑:程序博客网 时间:2024/06/10 12:19

题目链接

题目大意

给定一棵n个节点的带边权树,节点编号从1开始.设f(i,j)ij路径上的最大边权(特别地,当i=j时,f(i,j)=0).
对于一个下标从1n且满足pi[1,n]的数列p,定义g(p)=mini=1n{f(i,pi)},求在满足ip中最多只能出现limi次的情况下,g(p)的最大值.
n3,000.

题解

这题意好绕…而且信息量略大….
自己一开始的想法是先二分答案,然后判能否使每个f(i,pi)都大于等于mid.
可以先O(n2)预处理树上任意两点间的f(i,j),然后构图用网络流跑…图中节点数是O(n)的,然而边数可以达到O(n2)
说起来都很虚对吧,而且标签里还写着dsu并查集…于是我瞟了一眼tutorial,似乎看到了网络流?还说什么常数很小?本着相信CF服务器十分强大的心理敲了一发…果断T了…
题解里说的正解是树分治+网络流可以把边数控制在O(nlgn),但是没有想过菊花图么??就算这样不也是卡常数么?有点看醉了…
然而神犇subscriber在比赛时想出了一个O(nα(n))并查集神解…完虐什么二分答案网络流啊…

真·题解

嗯嗯说到并查集那么肯定要先将边按权值排序然后一条一条并起来啦.
考虑当前这条边怎样才能成为答案.即可以使当前边的权值cost成为f(i,j)的最小值.
先不考虑边权有相同的情况,那么当前(并上这条边之前)任意的节点i都必须找一个与它不在同一个连通块内的节点作为pi,否则ipi的路径上的最大边权就会小于当前cost了.
关键是怎么判定在限制条件下每个节点i都能分配到一个可行的pi呢?
我们换一个角度思考,在合并时考虑这个操作是否会导致不可行.
就是说合并之前是满足分配条件的,那么当前合并只会影响合并的两个集合之间的分配情况.合并两个集合UV时,如果对于uU,vVpu=v,那么合并之后pu必须在别的连通块中取,而对于其他的连通块,它们的pi丝毫不会受到影响!所以只需要在合并时考虑合并得到的连通块中的点能否在其他连通块中得到足够的pi即可.一旦发现当前合并会导致不合法,就可以停止合并了.
当然这题还有一些坑爹的细节…首先如果有权值相同的边必须把它们一次性全都并起来才能更新答案.然后对于最初每个点独立成一个连通块的情况,因为输入保证了limi1,所以它们都可以从其他连通块找到配对的pi,即初始状态是合法的.但是如果n=1,初始状态就不合法了!!可以直接特判掉.
这题真的是非常神.一开始我很困惑为什么每个连通块都满足能从其他连通块得到足够的分配就可以推出整个状态可以完成分配,后来才发现是在一步一步合并的过程中实时保证了状态的合法性,这样就大大化简了问题.
于是达到了神奇的复杂度O(nα(n))

代码

#include<cstdio>#include<algorithm>using namespace std;const int N=3e3+5;int sum,par[N],cnt[N],lim[N];bool valid;struct Edge{    int u,v,cost;    inline bool operator <(const Edge &tmp)const{        return cost<tmp.cost;    }    void Rd(){        scanf("%d%d%d",&u,&v,&cost);    }}edge[N];int get_root(int x){    return par[x]==x?x:par[x]=get_root(par[x]);}void unite(int u,int v){    u=get_root(u);    v=get_root(v);    cnt[v]+=cnt[u];    lim[v]+=lim[u];    if(cnt[v]>sum-lim[v])valid=false;    par[u]=v;}int main(){    int n;    scanf("%d",&n);    if(n==1){        puts("0");        return 0;    }    int m=n-1,ans;    for(int i=0;i<m;++i)        edge[i].Rd();    sort(edge,edge+m);    valid=true;    sum=0;    for(int i=1;i<=n;++i){        cnt[par[i]=i]=1;        scanf("%d",&lim[i]);        sum+=lim[i];    }    for(int i=0;i<m&&valid;++i){        if(!i||edge[i].cost!=edge[i-1].cost)ans=edge[i].cost;        unite(edge[i].u,edge[i].v);    }    printf("%d\n",ans);    return 0;}/*    Jun.04.16    Tags:dsu    Submissions:4    Time 31ms    Memory 2100KB*/
0 0
原创粉丝点击