bzoj2001: [Hnoi2010]City 城市建设 wikioi2332

来源:互联网 发布:java工厂模式优缺点 编辑:程序博客网 时间:2024/06/06 00:58
两个关键的操作:
  Reduction(删除无用边):
  把待修改的边标为INF,做一遍MST,把做完后不在MST中的非INF边删去(因为这些边在原图的情况下肯定更不可能选进MST的边集,即无用边);
  Contraction(缩必须边):
  把待修改的边标为-INF,做一遍MST,在MST中的非-INF边为必须边(因为这些边在原图的情况下也一定会被选进MST),缩点。
/**************************************************************    Problem: 2001    User: xujiahe    Language: C++    Result: Accepted    Time:4084 ms    Memory:24656 kb****************************************************************/ #include <iostream>#include <algorithm>#include <cstdio>#include <cstring>#include <cmath>using namespace std;#define maxn 51000struct quest{    int x,y;}q[60000];struct node{    int pos,x,y,w;}e[25][maxn],d[maxn],t[maxn];//e[][]用来记录分层图,d[]记录当前图,t[]可以当做中间变量。int n,m,Q;int father[maxn],val[maxn],c[maxn],size[maxn],sum[maxn];long long ans[maxn];inline bool cmp(node aa,node bb) { return aa.w<bb.w; }inline int getfather(int x){return x==father[x] ? x:father[x]=getfather(father[x]);}inline void clear(int tot)//初始化,把图还原成点{    for(int i=1;i<=tot;i++)    {        father[d[i].x]=d[i].x;        father[d[i].y]=d[i].y;        size[d[i].x]=1;        size[d[i].y]=1;    }}inline void merge(int x,int y){    if (size[x]<=size[y]) size[y]+=size[x],father[x]=y;//size[]用来加速(再次查询时只查个数少的一组)    else size[x] += size[y],father[y] = x;}void contraction(int &tot,long long &cnt)//缩必须边{    int tmp=0;//记录边的个数    clear(tot);    sort(d+1,d+1+tot,cmp);    //把要改动的边加进MST,如果这时还要边i说明i是必须边,无论如何都要存到下一个图里    for(int i=1;i<=tot;i++)    {        int r1=getfather(d[i].x);        int r2=getfather(d[i].y);        if(r1!=r2)        {            merge(r1,r2);            tmp++;            t[tmp]=d[i];        }    }    //初始化,再次做MST    for(int i=1;i<=tmp;i++)    {        father[t[i].x]=t[i].x;        father[t[i].y]=t[i].y;        size[t[i].x]=1;        size[t[i].y]=1;    }    for(int i=1;i<=tmp;i++)    {        int r1=getfather(t[i].x);        int r2=getfather(t[i].y);        if(r1!=r2&&t[i].w!=-0x3f3f3f3f)//再次做MST时把边值不为最小的边(必须边)找出来,加上边的值        {            merge(r1,r2);            cnt+=t[i].w;        }    }    tmp=0;    //注意此时必须边的权值已经记录,点也都放入了一个集合,所以可以把这些端点都看成一个点。    for ( int i=1 ; i<=tot ; i++)    {        int r1 = getfather(d[i].x);        int r2 = getfather(d[i].y);        if( r1 != r2)        {            t[++tmp]=d[i];            c[d[i].pos] = tmp;            t[tmp].x = father[d[i].x];            t[tmp].y = father[d[i].y];        }    }    tot=tmp;    for ( int i=1 ; i<=tot ; i++ )    {        d[i]=t[i];    }}inline void reduction(int &tot)//删除无用边{    int tmp=0;    clear(tot);    sort(d+1,d+1+tot,cmp);    //如果把要改变的边都设为最大值,也用不到边i,那么边i为废边    for(int i=1;i<=tot;i++)    {        int r1=getfather(d[i].x);        int r2=getfather(d[i].y);        if(r1!=r2)        {            merge(r1,r2);            tmp++;//记录有用的边            t[tmp]=d[i];            c[d[i].pos]=tmp;        }        else if(d[i].w==0x3f3f3f3f)//加入要改变的边        {            tmp++;            t[tmp]=d[i];            c[d[i].pos]=tmp;        }    }    for(int i=1;i<=tmp;i++)        d[i]=t[i];    tot=tmp;}inline void solve(int l,int r,int now,long long cnt)//now为图的序号{    int tot=sum[now];    if(l==r)//递归到底了    {        val[q[l].x]=q[l].y;    }    for(int i=1;i<=tot;i++)//存图    {        e[now][i].w=val[e[now][i].pos];    }    for(int i=1;i<=tot;i++)    {        d[i]=e[now][i];        c[d[i].pos]=i;//记录d[]中边的序号。    }    if(l==r)//只改变1条边所以直接进行排序。    {        clear(tot);        ans[l]=cnt;        sort(d+1,d+1+tot,cmp);        for(int i=1;i<=tot;i++)        {            int r1=getfather(d[i].x);            int r2=getfather(d[i].y);            if(r1!=r2)            {                merge(r1,r2);                ans[l]+=d[i].w;            }        }        return;    }    for(int i=l;i<=r;i++) d[c[q[i].x]].w=-0x3f3f3f3f;//改变了边得值,val[]和c[]就体现了他们的价值。    contraction(tot,cnt);//分析见函数    for(int i=l;i<=r;i++) d[c[q[i].x]].w=0x3f3f3f3f;    reduction(tot);//分析见函数    //经过上面两个操作,缩边和删废边 图应该变得越来越小。    for(int i=1;i<=tot;i++)        e[now+1][i]=d[i];//存下一个序号的图    int mid=(l+r)>>1;//对讯问进行分治    sum[now+1]=tot;    solve(l,mid,now+1,cnt);    solve(mid+1,r,now+1,cnt);}int main(){    int x,y,z;    scanf("%d%d%d",&n,&m,&Q);    for(int i=1;i<=m;i++)    {        scanf("%d%d%d",&x,&y,&val[i]);        e[0][i].x=x;//初始图的序号为0        e[0][i].y=y;        e[0][i].w=val[i];        e[0][i].pos=i;//记录编号。    }    for(int i=1;i<=Q;i++)    {        scanf("%d%d",&q[i].x,&q[i].y);    }    sum[0]=m;//sum记录每个图有几条有用边    solve(1,Q,0,0);//对讯问进行分治    for(int i=1;i<=Q;i++)    {        printf("%lld\n",ans[i]);    }    return 0;}
0 0
原创粉丝点击