bzoj3258: 秘密任务 (+一百题感言?)

bzoj3258: 秘密任务
题意:n<=400个点,m<=4000条边,可以在每条边的起点或终点截断这条边。求截断所有1到n最短路径的最小花费,以及方案是否唯一。


题解

显然是个最小割的模型。
按照题意跑一遍最短路,把不在最短路里的边删掉,其他的拆成两条边,流量分别赋起点和终点的费用,跑最大流,第一问结束。
有关最小割是否有唯一方案,参考了这个blog↓
[BZOJ 1797][BZOJ 3258]最小割的唯一性判定
跑一遍tarjan对于一条满流边。如果起点与终点分属两个scc,那么它可以作为原图最小割的一条边。而当起点与S所在scc相同,终点与T所在scc相同时,这条边的选择是唯一的。


代码

说起来是时隔半年后第一次上3000B呢。
交完去吃饭,回来发现1A了,还是很开心的。

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 5000
#define M 24006
#define ll long long
using namespace std;
int T,n,m,n_,a[N];
int to[M],hd[M],val[M],lk[N],cur[N];
char ch;
int q[N],h,t,dfn[N],low[N],tms;
bool inq[N];
ll dis[N],ans;
void add(int u,int v,int w,int loc)
{to[loc]=v,hd[loc]=lk[u],val[loc]=w,lk[u]=loc;}
void read(int &x)
{
    x=0;
    do ch=getchar();
    while(ch<'0'||ch>'9');
    do x=x*10+ch-'0',ch=getchar();
    while(ch>='0'&&ch<='9');
}
int u,v,w;
bool bfs()
{
    for(int i=1;i<=n_;i++)
    dis[i]=0,cur[i]=lk[i];
    h=0,t=dis[1]=1,q[0]=1;
    while(h<t)
    {
        u=q[h++];
        for(int i=lk[u];i;i=hd[i])
        if(val[i]&&!dis[to[i]])
        dis[q[t++]=to[i]]=dis[u]+1;
    }
    return dis[n];
}
ll dfs(int x,ll mx)
{
    if(x==n)return mx;
    ll ret=0,cst;
    for(int &i=cur[x];i;i=hd[i])
    if(val[i]&&dis[to[i]]==dis[x]+1)
    {
        cst=dfs(to[i],mx<val[i]?mx:val[i]);
        if(!cst)dis[to[i]]=0;
        else ret+=cst,mx-=cst,val[i]-=cst,val[i^1]+=cst; 
        if(!mx)break;
    }
    return ret;
}
void tarjan(int x)
{
    dfn[x]=low[x]=++tms;
    inq[q[t++]=x]=1;
    for(int i=lk[x];i;i=hd[i])
    if(val[i])
    {
        if(!dfn[to[i]])
        {
            tarjan(to[i]);
            if(low[x]>low[to[i]])
            low[x]=low[to[i]];
        }
        else if(inq[to[i]]&&low[x]>dfn[to[i]])
        low[x]=dfn[to[i]];
    }
    if(dfn[x]==low[x])
    {
        while(q[--t]!=x)
        inq[q[t]]=0,cur[q[t]]=x;
        inq[x]=0,cur[x]=x;
    }
}
bool f;
int main()
{
    read(T);
    while(T--)
    {
        read(n),read(m);
        n_=n;
        for(int i=1;i<n;i++)
        read(a[i]);
        memset(lk,0,sizeof lk);
        for(int i=2;i<(m<<2);i+=2)
        {
            read(u),read(v),read(w);
            add(u,v,w,i);
            i+=2,add(v,u,w,i); 
        }
        memset(dis,0x3f,sizeof dis);
        h=dis[1]=0,t=q[0]=1;
        while(h!=t)
        {
            u=q[h++],inq[u]=0;
            if(h==N)h=0;
            for(int i=lk[u];i;i=hd[i])
            if(dis[to[i]]>dis[u]+val[i])
            {
                dis[to[i]]=dis[u]+val[i];
                if(!inq[to[i]])
                {
                    inq[q[t++]=to[i]]=1;
                    if(t==N)t=0;
                }
            }
        }
        memset(lk,0,sizeof lk);
        for(int i=2;i<(m<<2);i+=4)
        {
            u=to[i],v=to[i+2],w=val[i];
            if(dis[v]+w==dis[u])swap(u,v);
            if(dis[u]+w>dis[v])continue;
            n_++;
            if(u==n)continue;
            if(v==n)
            add(u,v,a[u],i),add(v,u,0,i|1);
            else
            add(u,n_,a[u],i),add(n_,u,0,i|1),
            add(n_,v,a[v],i+2),add(v,n_,0,i+3);
        }
        ans=0;
        while(bfs())ans+=dfs(1,0x3f3f3f3f);
        memset(dfn,0,sizeof dfn);t=0;
        for(int i=1;i<=n_;i++)
        if(!dfn[i])tarjan(i);
        f=1;
        for(int i=1;i<=n_;i++)
        for(int j=lk[i];j;j=hd[j])
        {
            u=cur[to[j]],v=cur[to[j^1]];
            if(val[j]||u==v||(j&1))
            continue;
            if(u!=cur[1])
            swap(u,v);
            if(u!=cur[1]||v!=cur[n])
            {f=0;break;}
        }
        if(f)printf("Yes ");
        else printf("No ");
        printf("%d\n",ans);
    }
}

题目无关

离在bzoj上交A+B过了13个月,离开权限也有九个半月了吧。现在才刷到100是因为中间有半年纯文化课,没碰OI。不过中考结束,终于还是回来被碾压了呢……
周围oier的人员组成果然有变化,也意识到这个团队/这种生活可能和自己一直想象的不太一样。
以及,发现自己的代码力倒退至一年前的暑假,与此同时,机房里的其他人都在屠day榜……简直可怕……
不过并没有退路。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章