【这是一个可能永远填不完的坑】网络流24题

  看到那么多大佬都开坑刷题,那我也随波逐流一下。。。虽然保不准什么时候就弃掉了。。

  进度:

8/24


 

  1、餐巾计划问题(费用流)

  题目传送门:https://www.luogu.org/problemnew/show/P1251

  这道题还是比较思维的。。。(然而说白了都是套路)

  显然我们可以把餐巾使用量看作网络的流量,把花费看作网络的费用。

  这道题主要的难点就在于如何保证每天一定有ai条餐巾用,以及处理干净餐巾的来源与脏餐巾的去向。因为只能通过源汇来控制整个网络的流量(即餐巾的使用量),所以我们可以把使用一条干净餐巾的过程拆成两个部分:(1)把一条干净的餐巾流进汇点;(2)让一条脏餐巾从源点流出。

  于是把一天拆成两个点,一个用来接受脏餐巾,一个用来使用干净的餐巾,然后按题意连边(把脏餐巾洗干净),跑最小费用最大流就行了。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<ctime>
#include<algorithm>
#include<queue>
#include<vector>
#define ll long long
#define max(a,b) (a>b?a:b)
#define min(a,b) (a<b?a:b)
#define inf 0x3f3f3f3f
#define mod 1000000007
#define eps 1e-18
inline ll read()
{
    ll tmp=0; char c=getchar(),f=1;
    for(;c<'0'||'9'<c;c=getchar())if(c=='-')f=-1;
    for(;'0'<=c&&c<='9';c=getchar())tmp=(tmp<<3)+(tmp<<1)+c-'0';
    return tmp*f;
}
using namespace std;
struct edge{
    int from,to,nxt;
    ll w,flow;
}e[1600010];
int fir[4010],inq[4010],last[4010];
ll dist[4010];
int q[1600010];
int a[2010];
int n,S,T,tot=0;
void add(int x,int y,ll w,ll f)
{
    e[tot].from=x; e[tot].to=y; e[tot].w=w; e[tot].flow=f; e[tot].nxt=fir[x]; fir[x]=tot++;
    e[tot].from=y; e[tot].to=x; e[tot].w=-w; e[tot].flow=0; e[tot].nxt=fir[y]; fir[y]=tot++;
}
int spfa()
{
    int h=1,t=1,i;
    for(i=0;i<=T;i++)dist[i]=inf,inq[i]=0,last[i]=-1;
    q[1]=S; dist[S]=0; inq[S]=1;
    while(h<=t){
        int now=q[h++]; inq[now]=0;
        for(i=fir[now];~i;i=e[i].nxt)
            if(e[i].flow&&dist[e[i].to]>dist[now]+e[i].w){
                dist[e[i].to]=dist[now]+e[i].w; last[e[i].to]=i;
                if(!inq[e[i].to]){
                    q[++t]=e[i].to; inq[e[i].to]=1;
                }
        }
    }
    if(dist[T]==inf)return 0;else return 1;
}
ll flow()
{
    ll ans=inf;
    for(int i=T;i!=S;i=e[last[i]].from)ans=min(ans,e[last[i]].flow);
    for(int i=T;i!=S;i=e[last[i]].from)
        e[last[i]].flow-=ans,e[last[i]^1].flow+=ans;
    return ans;
}
int main()
{
    int i;
    n=read(); S=0; T=2*n+1;
    for(i=1;i<=n;i++)a[i]=read();
    memset(fir,255,sizeof(fir));
    ll p0=read(),t1=read(),p1=read(),t2=read(),p2=read();
    for(i=1;i<=n;i++){
        add(S,i,0,a[i]); add(i+n,T,0,a[i]);
        if(i+t1<=n)add(i,i+t1+n,p1,inf);
        if(i+t2<=n)add(i,i+t2+n,p2,inf);
        add(S,i+n,p0,inf);
        if(i<n)add(i,i+1,0,inf),add(i+n,i+n+1,0,inf);
    }
    ll ans=0;
    while(spfa())ans+=flow()*dist[T];
    printf("%lld\n",ans);
}
洛谷P1251

 

======================================== UPD:2018.4.28 ==================================

  

  2、家园[CTSC1999](最大流)

  题目传送门:https://www.luogu.org/problemnew/show/P2754

  我们首先可以二分转化为判定性问题:过了t时后,k人是否都能到达月球。

  然后考虑拆点,把地球/月球/每个太空站按时间拆成t个点,对于每一时刻,从第i-1时刻的点往这第i时刻的对应点连一条流量为无穷大的边(停在地球/月球/太空站),然后按照每条航线在i-1时刻到第i时刻的航行路线连一条长度为太空产容量的边(坐太空船转移)。

  最后跑最大流就能得出在前t时刻能转移旅客的最大值。

  但是我们还有一种更优秀的写法。

  我们会发现,比较第t时刻与第t+1时刻时的网络,网络的不同之处仅在于第t时刻到第t+1时刻新连的边,即前t时刻的网络的最大流不变。

  于是我们可以从小到大枚举当前时刻,在上一时刻的剩余网络中连上新边,当前时刻的最大流就等于上一时刻的最大流+当前剩余网络的最大流,这样的写法效率更佳。

  至于如何判断无解。。。我们可以发现对于每个人,地球/每个太空站最多经过一次,且在地球/太空站上停留的时间最多不超过(n+2)个时刻(等太空船转一圈最多只用(n+2)个时刻),所以如果有解,那么过了第(n+1)*(n+2)时刻,必有人到达月球。所以当t>(n+1)*(n+2)且最大流==0就无解了。

  代码:

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<ctime>
#include<algorithm>
#include<queue>
#include<vector>
#define ll long long
#define max(a,b) (a>b?a:b)
#define min(a,b) (a<b?a:b)
#define lowbit(x) (x& -x)
#define inf 0x3f3f3f3f
#define mod 1000000007
#define eps 1e-18
inline ll read(){ll tmp=0; char c=getchar(),f=1; for(;c<'0'||'9'<c;c=getchar())if(c=='-')f=-1; for(;'0'<=c&&c<='9';c=getchar())tmp=tmp*10+c-'0'; return tmp*f;}
inline ll power(ll a,ll b){ll tmp=1; for(;b;b>>=1){if(b&1)tmp=tmp*a%mod; a=a*a%mod;} return tmp;}
using namespace std;
struct edge{
    int to,nxt,flow;
}e[1000010];
int n,m,k,S,T,tot=0;
int a[30],r[30],s[30][30];
int fir[250010],lv[250010],q[250010];
void add(int x,int y,int flow)
{
    e[tot].to=y; e[tot].flow=flow; e[tot].nxt=fir[x]; fir[x]=tot++;
    e[tot].to=x; e[tot].flow=0; e[tot].nxt=fir[y]; fir[y]=tot++;
}
int dfs(int now,int flow)
{
    if(now==T)return flow;
    for(int i=fir[now];~i;i=e[i].nxt)
        if(e[i].flow&&lv[e[i].to]==lv[now]+1){
            int tmp=dfs(e[i].to,min(flow,e[i].flow));
            e[i].flow-=tmp; e[i^1].flow+=tmp;
            if(tmp)return tmp;
        }
    return 0;
}
int dinic()
{
    int i,ans=0;
    while(1){
        for(i=0;i<=T;i++)lv[i]=0;
        int h=1,t=1; q[1]=S; lv[S]=1;
        while(h<=t){
            for(i=fir[q[h]];~i;i=e[i].nxt)
                if(e[i].flow&&!lv[e[i].to]){
                    q[++t]=e[i].to; lv[e[i].to]=lv[q[h]]+1;
                }
            ++h;
        }
        if(!lv[T])return ans;
        int k=dfs(S,inf);
        while(k)ans+=k,k=dfs(S,inf);
    }
}
int main()
{
    int i,j;
    n=read(); m=read(); k=read();
    memset(fir,255,sizeof(fir));
    for(i=1;i<=m;i++){
        a[i]=read(); r[i]=read();
        for(j=1;j<=r[i];j++)s[i][j]=read();
    }
    int t=1,ans=0;
    for(;;t++){
        S=0; T=(t+1)*(n+2)-1;
        for(i=0;i<n+2;i++)add((t-1)*(n+2)+i,t*(n+2)+i,inf);
        for(i=1;i<=m;i++){
            int now=t%r[i],nxt=now+1;
            if(!now)now=r[i];
            if(s[i][now]==-1)now=n+1;else now=s[i][now];
            if(s[i][nxt]==-1)nxt=n+1;else nxt=s[i][nxt];
            add((t-1)*(n+2)+now,t*(n+2)+nxt,a[i]);
        }
        ans+=dinic();
        if(ans>=k)break;
        if(t>(n+2)*(n+1)&&!ans){
            printf("0\n"); return 0;
        }
    }
    printf("%d\n",t);
}
洛谷P2754


 ========================================UPD:2018.4.29==================================

  

  3、飞行员配对方案问题(二分图匹配)

  题目传送门:https://www.luogu.org/problemnew/show/P2756

  这道题没什么技术含量,就是这道题(花店橱窗布置)去除了匹配权值的版本。

  对了,还要求一个匹配方案……只要看看哪几条边的流量为0(即有流流过)即可。

  代码:

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<ctime>
#include<algorithm>
#include<queue>
#include<vector>
#define ll long long
#define max(a,b) (a>b?a:b)
#define min(a,b) (a<b?a:b)
#define lowbit(x) (x& -x)
#define inf 0x3f3f3f3f
#define mod 1000000007
#define eps 1e-18
inline ll read(){ll tmp=0; char c=getchar(),f=1; for(;c<'0'||'9'<c;c=getchar())if(c=='-')f=-1; for(;'0'<=c&&c<='9';c=getchar())tmp=tmp*10+c-'0'; return tmp*f;}
inline ll power(ll a,ll b){ll tmp=1; for(;b;b>>=1){if(b&1)tmp=tmp*a%mod; a=a*a%mod;} return tmp;}
using namespace std;
struct edge{
    int to,nxt,flow;
}e[100010];
int fir[110],lv[110],q[110];
int n,m,S,T,tot=0;
void add(int x,int y,int flow)
{
    e[tot].to=y; e[tot].flow=flow; e[tot].nxt=fir[x]; fir[x]=tot++;
    e[tot].to=x; e[tot].flow=0; e[tot].nxt=fir[y]; fir[y]=tot++;
}
int dfs(int now,int flow)
{
    if(now==T)return flow;
    for(int i=fir[now];~i;i=e[i].nxt)
        if(e[i].flow&&lv[e[i].to]==lv[now]+1){
            int tmp=dfs(e[i].to,min(flow,e[i].flow));
            e[i].flow-=tmp; e[i^1].flow+=tmp;
            if(tmp)return tmp;
        }
    return 0;
}
int dinic()
{
    int i,ans=0;
    while(1){
        for(i=0;i<=T;i++)lv[i]=0;
        int h=1,t=1; q[1]=S; lv[S]=1;
        while(h<=t){
            for(i=fir[q[h]];~i;i=e[i].nxt)
                if(e[i].flow&&!lv[e[i].to]){
                    q[++t]=e[i].to; lv[e[i].to]=lv[q[h]]+1;
                }
            ++h;
        }
        if(!lv[T])return ans;
        int k=dfs(S,inf);
        while(k)ans+=k,k=dfs(S,inf);
    }
}
int main()
{
    int i;
    memset(fir,255,sizeof(fir));
    n=read(); m=read(); S=0; T=m+1;
    int x=read(),y=read();
    while(x>0&&y>0){
        add(x,y,1); x=read(); y=read();
    }
    for(i=1;i<=n;i++)add(S,i,1);
    for(i=n+1;i<=m;i++)add(i,T,1);
    printf("%d\n",dinic());
    for(i=1;i<=n;i++)
        for(int j=fir[i];~j;j=e[j].nxt)
            if(e[j].to!=S&&!e[j].flow){
                printf("%d %d\n",i,e[j].to); break;
            }
}
洛谷P2756

 


 ========================================UPD:2018.5.6==================================

 

  4、软件补丁问题(最短路)

  题目传送门:https://www.luogu.org/problemnew/show/P2761

   这道题因为n很小(n<=20),所以我们可以把每个补丁是否被修复,这些状态压缩起来(代码中第i位=0:未修复;1:已修复),作为图中的每一个点。这样,就可以把每一个补丁看作图中的带权边,要求的就是从点0到点2n-1的最短路。

  然后我们也可以把B1、B2、F1、F2这些集合也用二进制压缩,在跑spfa时直接用位运算判断在当前状态是否能使用这个补丁,以及使用后会到达哪一个状态。

  于是就这样解决了。(不过这个数据范围真的太极限了,220*100甚至已经大于108了。。。)

  代码:

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<ctime>
#include<algorithm>
#include<queue>
#include<vector>
#include<map>
#define ll long long
#define max(a,b) (a>b?a:b)
#define min(a,b) (a<b?a:b)
#define lowbit(x) (x& -x)
#define inf 0x3f3f3f3f
#define mod 1000000007
#define eps 1e-18
inline ll read(){ll tmp=0; char c=getchar(),f=1; for(;c<'0'||'9'<c;c=getchar())if(c=='-')f=-1; for(;'0'<=c&&c<='9';c=getchar())tmp=tmp*10+c-'0'; return tmp*f;}
inline ll power(ll a,ll b){ll tmp=1; for(;b;b>>=1){if(b&1)tmp=tmp*a%mod; a=a*a%mod;} return tmp;}
using namespace std;
struct data{
    int dist,id;
};
int que[(1<<20)+10],inq[(1<<20)+10],dist[(1<<20)+10];
int ti[110],p[110],q[110],x[110],y[110];
int n,m;
char s[30];
void spfa()
{
    memset(dist,0x3f,sizeof(dist));
    int h=0,t=1,i;
    que[0]=0; inq[0]=1; dist[0]=0;
    while(h!=t){
        int now=que[h++]; inq[now]=0;
        if(h>1<<n)h=0;
        for(i=1;i<=m;i++)
            if(!(now&p[i])&&(now&q[i])==q[i]){
                int nxt=(now|x[i])&(~y[i]);
                if(dist[now]+ti[i]>=dist[nxt])continue;
                dist[nxt]=dist[now]+ti[i];
                if(!inq[nxt]){
                    que[t++]=nxt; inq[nxt]=1;
                    if(t>1<<n)t=0;
                }
            }
    }
}
int main()
{
    int i,j;
    n=read(); m=read();
    for(i=1;i<=m;i++){
        ti[i]=read(); p[i]=q[i]=0;
        scanf("%s",s);
        for(j=0;j<n;j++)
            if(s[j]=='+')p[i]^=1<<j;
            else if(s[j]=='-')q[i]^=1<<j;
        scanf("%s",s); x[i]=y[i]=0;
        for(j=0;j<n;j++)
            if(s[j]=='-')x[i]|=1<<j;
            else if(s[j]=='+')y[i]|=1<<j;
    }
    spfa();
    if(dist[(1<<n)-1]==inf)printf("0\n");
    else printf("%d\n",dist[(1<<n)-1]);
    return 0;
}
luoguP2761

 

 ========================================UPD:2018.7.1==================================

  5、太空飞行计划问题(最大权闭合子图)

  题目传送门:https://www.luogu.org/problemnew/show/P2762

  这道题是道赤裸裸的最大权闭合子图(虽然我一开始不会)。最大权闭合子图就是指在一个图的子图中,其所有结点在原图中的所有出边都只连向子图中的结点,且这个子图的点权最大。在这道题中,就是把每一个实验连若干条出边到它所需要的仪器,实验的收入赋正权,仪器的花费赋负权,这样的图的最大权闭合子图就是所求的解。

  这种问题的做法就是把原图化为二分图,源点连正权点,负权点连向汇点,流量为权值的绝对值;正权点向负权点按原图的方式连边,流量无限大。这样求出来的最大流就是最大权闭合子图的权值和。具体证明可以参考论文:胡伯涛《最小割模型在信息学竞赛中的应用》

  不过这道题还有一个难点是要输出方案。我们可以发现,源点与某正权点之间边的流量,就是这个正权点的价值被它所附带的若干个负权点消耗的值,如果它的价值被完全消耗(甚至还欠着一笔价值),那么选取这个结点是没有意义的。于是我们就可以知道,方案中要做的实验就是那些与源点的连边未满流的实验。即,若用dinic算法求最大流,在最后一次把图分层时,与源点连通的那些实验和仪器就是所需要的。

  代码:

#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<algorithm>
#include<iostream>
#include<queue>
#include<vector>
#include<map>
#define ll long long
#define ull unsigned long long
#define max(a,b) (a>b?a:b)
#define min(a,b) (a<b?a:b)
#define lowbit(x) (x& -x)
#define mod 1000000007
#define inf 0x3f3f3f3f
#define eps 1e-18
inline ll read(){ll tmp=0; char c=getchar(),f=1; for(;c<'0'||'9'<c;c=getchar())if(c=='-')f=-1; for(;'0'<=c&&c<='9';c=getchar())tmp=(tmp<<3)+(tmp<<1)+c-'0'; return tmp*f;}
inline ll power(ll a,ll b){ll ans=0; for(;b;b>>=1){if(b&1)ans=ans*a%mod; a=a*a%mod;} return ans;}
inline ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
using namespace std;
struct edge{
    int to,nxt,flow;
}e[20010];
int fir[110],lv[110],q[110];
int val[60],p[60];
int n,m,S,T,tot=0;
char tools[10000];
void add(int x,int y,int flow)
{
    e[tot].to=y; e[tot].flow=flow; e[tot].nxt=fir[x]; fir[x]=tot++;
    e[tot].to=x; e[tot].flow=0; e[tot].nxt=fir[y]; fir[y]=tot++;
}
int dfs(int now,int flow)
{
    if(now==T)return flow;
    for(int i=fir[now];~i;i=e[i].nxt)
        if(e[i].flow&&lv[e[i].to]==lv[now]+1){
            int tmp=dfs(e[i].to,min(flow,e[i].flow));
            e[i].flow-=tmp; e[i^1].flow+=tmp;
            if(tmp)return tmp;
        }
    return 0;
}
int dinic()
{
    int i,ans=0;
    while(1){
        for(i=0;i<=T;i++)lv[i]=0;
        int h=1,t=1; q[1]=S; lv[S]=1;
        while(h<=t){
            for(i=fir[q[h]];~i;i=e[i].nxt)
                if(e[i].flow&&!lv[e[i].to]){
                    q[++t]=e[i].to; lv[e[i].to]=lv[q[h]]+1;
                }
            ++h;
        }
        if(!lv[T])return ans;
        int k=dfs(S,inf);
        while(k)ans+=k,k=dfs(S,inf);
    }
}
int main()
{
    int i;
    memset(fir,255,sizeof(fir));
    n=read(); m=read(); S=0; T=n+m+1;
    for(i=1;i<=n;i++){
        val[i]=read();
        memset(tools,0,sizeof(tools));
        cin.getline(tools,10000);
        int ulen=0,tool;
        while (sscanf(tools+ulen,"%d",&tool)==1){
            add(i,tool+n,inf);
            if(tool==0)ulen++;
            else{
                while(tool)tool/=10,ulen++;
            }
        }
        ulen++;
    }
    for(i=1;i<=m;i++)p[i]=read();
    for(i=1;i<=n;i++)add(S,i,val[i]);
    for(i=1;i<=m;i++)add(n+i,T,p[i]);
    int ans=0;
    for(i=1;i<=n;i++)ans+=val[i];
    ans-=dinic();
    for(i=1;i<=n;i++)if(lv[i])printf("%d ",i); printf("\n");
    for(i=1;i<=m;i++)if(lv[i+n])printf("%d ",i); printf("\n");
    printf("%d\n",ans);
    return 0;
}
luogu2762

 

 ========================================UPD:2018.7.3==================================

  6、试题库问题(最大流)

  题目传送门:https://www.luogu.org/problemnew/show/P2763

   这题太傻逼了……直接看成一个有一边每个节点可以匹配k次的二分图匹配就好了。

#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<algorithm>
#include<queue>
#include<vector>
#include<map>
#define ll long long
#define ull unsigned long long
#define max(a,b) (a>b?a:b)
#define min(a,b) (a<b?a:b)
#define lowbit(x) (x& -x)
#define mod 1000000007
#define inf 0x3f3f3f3f
#define eps 1e-18
#define maxn 1010
inline ll read(){ll tmp=0; char c=getchar(),f=1; for(;c<'0'||'9'<c;c=getchar())if(c=='-')f=-1; for(;'0'<=c&&c<='9';c=getchar())tmp=(tmp<<3)+(tmp<<1)+c-'0'; return tmp*f;}
inline ll power(ll a,ll b){ll ans=0; for(;b;b>>=1){if(b&1)ans=ans*a%mod; a=a*a%mod;} return ans;}
inline ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
inline void swap(int &a,int &b){int tmp=a; a=b; b=tmp;}
using namespace std;
struct edge{
    int to,nxt,flow;
}e[maxn*maxn*2+4*maxn];
int fir[2*maxn],lv[2*maxn],q[2*maxn];
int n,m,S,T,tot=0;
void add(int x,int y,int flow)
{
    e[tot].to=y; e[tot].flow=flow; e[tot].nxt=fir[x]; fir[x]=tot++;
    e[tot].to=x; e[tot].flow=0; e[tot].nxt=fir[y]; fir[y]=tot++;
}
int dfs(int now,int flow)
{
    if(now==T)return flow;
    for(int i=fir[now];~i;i=e[i].nxt)
        if(e[i].flow&&lv[e[i].to]==lv[now]+1){
            int tmp=dfs(e[i].to,min(flow,e[i].flow));
            e[i].flow-=tmp; e[i^1].flow+=tmp;
            if(tmp)return tmp;
        }
    return 0;
}
int dinic()
{
    int i,ans=0;
    while(1){
        for(i=0;i<=T;i++)lv[i]=0;
        int h=1,t=1; q[1]=S; lv[S]=1;
        while(h<=t){
            for(i=fir[q[h]];~i;i=e[i].nxt)
                if(e[i].flow&&!lv[e[i].to]){
                    q[++t]=e[i].to; lv[e[i].to]=lv[q[h]]+1;
                }
            ++h;
        }
        if(!lv[T])return ans;
        int k=dfs(S,inf);
        while(k)ans+=k,k=dfs(S,inf);
    }
}
int main()
{
    memset(fir,255,sizeof(fir));
    n=read(); m=read(); S=0; T=n+m+1;
    int cnt=0;
    for(int i=1;i<=n;i++){
        int k=read(); add(S,i,k); cnt+=k;
    }
    for(int i=1;i<=m;i++){
        int p=read();
        while(p--)add(read(),n+i,1);
        add(n+i,T,1);
    }
    int ans=dinic();
    if(ans<cnt)printf("No Solution!\n");
    else{
        for(int i=1;i<=n;i++){
            printf("%d:",i); 
            for(int j=fir[i];~j;j=e[j].nxt)
                if(!e[j].flow&&e[j].to!=S)
                    printf("%d ",e[j].to-n);
            printf("\n");
        } 
    }
}
View Code

 

 ========================================UPD:2018.6.1==================================

  一年之后,继续填坑。。。 

  7、最小路径覆盖问题

  题目传送门:https://www.luogu.org/problemnew/show/P2764

  有证明一个结论:DAG(有向无环图)最小路径覆盖数=结点总数-对应二分图最大匹配数。这里的对应二分图指把每个点拆成两个点,记为$B_i,W_i$,若原图中$i \to j$有边,则在二分图中连一条边$B_i \to W_j$。证明略。

  于是就能解决这个问题了。

#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<algorithm>
#include<queue>
#include<vector>
#include<map>
#define ll long long
#define ull unsigned long long
#define max(a,b) (a>b?a:b)
#define min(a,b) (a<b?a:b)
#define lowbit(x) (x& -x)
#define mod 1000000007
#define inf 0x3f3f3f3f
#define eps 1e-18
inline ll read(){ll tmp=0; char c=getchar(),f=1; for(;c<'0'||'9'<c;c=getchar())if(c=='-')f=-1; for(;'0'<=c&&c<='9';c=getchar())tmp=(tmp<<3)+(tmp<<1)+c-'0'; return tmp*f;}
inline ll power(ll a,ll b){ll ans=0; for(;b;b>>=1){if(b&1)ans=ans*a%mod; a=a*a%mod;} return ans;}
inline ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
inline void swap(int &a,int &b){int tmp=a; a=b; b=tmp;}
using namespace std;
struct edge{
    int to,nxt,flow;
}e[15010];
int fir[310],lv[310],q[310];
int tmp[310];
int n,m,S,T,tot=0;
void add(int x,int y,int flow)
{
    e[tot].to=y; e[tot].flow=flow; e[tot].nxt=fir[x]; fir[x]=tot++;
    e[tot].to=x; e[tot].flow=0; e[tot].nxt=fir[y]; fir[y]=tot++;
}
int dfs(int now,int flow)
{
    if(now==T)return flow;
    for(int i=fir[now];~i;i=e[i].nxt)
        if(e[i].flow&&lv[e[i].to]==lv[now]+1){
            int tmp=dfs(e[i].to,min(flow,e[i].flow));
            e[i].flow-=tmp; e[i^1].flow+=tmp;
            if(tmp)return tmp;
        }
    return 0;
}
int dinic()
{
    int i,ans=0;
    while(1){
        for(i=0;i<=T;i++)lv[i]=0;
        int h=1,t=1; q[1]=S; lv[S]=1;
        while(h<=t){
            for(i=fir[q[h]];~i;i=e[i].nxt)
                if(e[i].flow&&!lv[e[i].to]){
                    q[++t]=e[i].to; lv[e[i].to]=lv[q[h]]+1;
                }
            ++h;
        }
        if(!lv[T])return ans;
        int k=dfs(S,inf);
        while(k)ans+=k,k=dfs(S,inf);
    }
}
int main()
{
    memset(fir,255,sizeof(fir));
    n=read(); m=read(); S=0; T=2*n+1;
    for(int i=1;i<=m;i++){
        int x=read(),y=read();
        add(x,y+n,1);
    }
    for(int i=1;i<=n;i++)
        add(S,i,1),add(i+n,T,1);
    int ans=dinic();
    for(int i=1;i<=n;i++)
        if(lv[i]){
            int flag=0;
            for(int j=fir[i];~j;j=e[j].nxt)
                if(e[j].to==S&&!e[j].flow)flag=1;
            if(flag){
                int cnt=0;
                for(int k=i;;){
                    tmp[++cnt]=k;
                    int j=fir[k+n];
                    for(;~j;j=e[j].nxt)if(e[j].to!=T&&e[j].flow)break;
                    if(~j)k=e[j].to;
                    else break;
                }
                for(int j=cnt;j;j--)printf("%d ",tmp[j]);
                printf("\n");
            }
        }
    printf("%d\n",n-ans);
}
luoguP2763

 

  8、魔术球问题

  题目传送门:https://www.luogu.org/problemnew/show/P2765

  我们考虑怎样的两个球可以相邻地串在同一根柱子上,显然对于这样的两个球$i,j$,满足$i<j$且$i+j$是完全平方数。那么我们考虑如何判定能否放下$x$颗球,那么只需对$[1,x]$的数按照上面的方式连边,所需的最小柱数就是该图的最小路径覆盖。显然该图是一个DAG,那么按照上面的方法求出最小路径覆盖就可以解决问题了。关于实现方法,依然可以从小到大枚举球数,然后在残量网络上新增边累加答案。

 


 To be continued……

posted @ 2018-04-10 21:06  QuartZ_Z  阅读(1088)  评论(2编辑  收藏  举报