网络流 总结
写了一周网络流,今天校内小测了一发,就会两道题第二题还写GG了,真是惨2333.......
1.最大流求最大匹配
A.bzoj 1711 Dining吃饭
显然每头牛要和一种食物还有一种饮料匹配,但因为要匹配两种东西,所以直接用二分图最大匹配是做不了的,那么我们可以建三层,S向食物连边,食物向牛连边,牛拆点中间连边,然后在向饮料连边,饮料向T连边。边的容量都为一,这样每条S到T的路径都是一个三种东西的匹配,容量都为一保证了不会重。
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<queue> 5 #include<cstring> 6 #define inf 0x3f3f3f3f 7 #define N 505 8 #define M 1000005 9 using namespace std; 10 int head[N],nxt[M],ver[M],tot,f[M]; 11 void add(int a,int b,int c) 12 { 13 tot++;nxt[tot]=head[a];head[a]=tot;ver[tot]=b;f[tot]=c; 14 tot++;nxt[tot]=head[b];head[b]=tot;ver[tot]=a;f[tot]=0; 15 } 16 int S,T; 17 int ch[N]; 18 int zeng(int a,int b) 19 { 20 if(a==T)return b; 21 int r=0; 22 for(int i=head[a];i&&b>r;i=nxt[i]) 23 { 24 if(ch[ver[i]]==ch[a]+1&&f[i]) 25 { 26 int t=zeng(ver[i],min(b-r,f[i])); 27 r+=t;f[i]-=t;f[i^1]+=t; 28 } 29 } 30 if(!r)ch[a]=-1; 31 return r; 32 } 33 bool tell() 34 { 35 memset(ch,-1,sizeof(ch)); 36 ch[S]=0; 37 queue<int>q; 38 q.push(S); 39 while(!q.empty()) 40 { 41 int tmp=q.front();q.pop(); 42 for(int i=head[tmp];i;i=nxt[i]) 43 { 44 if(f[i]&&ch[ver[i]]==-1) 45 { 46 ch[ver[i]]=ch[tmp]+1; 47 q.push(ver[i]); 48 } 49 } 50 } 51 return ch[T]!=-1; 52 } 53 int dinic() 54 { 55 int r=0,t; 56 while(tell())while(t=zeng(S,inf))r+=t; 57 return r; 58 } 59 int n,ff,d; 60 int main() 61 { 62 tot=1; 63 scanf("%d%d%d",&n,&ff,&d); 64 for(int i=1;i<=ff;i++)add(S,i,1); 65 for(int i=1;i<=n;i++)add(i+ff,i+ff+n,1); 66 S=0;T=2*n+ff+d+1; 67 int t1,t2; 68 for(int i=1;i<=n;i++) 69 { 70 scanf("%d%d",&t1,&t2); 71 int tmp; 72 for(int j=1;j<=t1;j++) 73 { 74 scanf("%d",&tmp); 75 add(tmp,i+ff,1); 76 } 77 for(int j=1;j<=t2;j++) 78 { 79 scanf("%d",&tmp); 80 add(i+ff+n,tmp+2*n+ff,1); 81 } 82 } 83 for(int i=1;i<=d;i++)add(i+ff+2*n,T,1); 84 printf("%d\n",dinic()); 85 return 0; 86 }
B.小测第一题
Seal
【题目背景】 NOI2030 前夜,作为出题人的 Z 君正在颓隔膜。这个隔膜是这样的:在上古 秘境中,勇者们发现了封印古代恶魔的地方。不幸的是,这里的封印已经濒临崩 溃,恶魔们即将苏醒。勇者们决定使用魔法水晶加固封印,让恶魔们再次沉睡。
【题目描述】 封印恶魔的地方可以看作是一个 n*m 的矩形,包含了 n*m 个祭坛,并且其 中有一些祭坛已经损坏了。如果 i+j 为偶数,那么第 i 行第 j 列的祭坛只要没有损 坏,就一定会封印有一个恶魔。 其他的没有损坏的祭坛可以用来放置魔法水晶,但是一个祭坛上只能放置一 个魔法水晶,并且一个魔法水晶只能向一个与它相邻的祭坛输送魔力,从而加固 封印。 对于一个恶魔来说,如果与它相邻的两个成直角的水晶同时向它所在的祭坛 输送魔力的话,它就会被再次封印。 现在 Z 君想知道他最多可以封印多少恶魔?
这道题最重要的是发现它二分图的性质,显然每一次封印魔鬼用的肯定是一个奇数列和一个偶数列的祭坛,那么就变成了和上一题一样的三元匹配。
2.最小割解决二元关系
一般二元关系的题有两种解法,一是在实际意义中寻找限制关系建边,二是无脑解方程组。
A. bzoj 3996 [TJOI2015]线性代数
Description
给出一个N*N的矩阵B和一个1*N的矩阵C。求出一个1*N的01矩阵A.使得
按提议中的式子列出矩阵最后的表达式,发现b[i][j]对答案有贡献当且仅当啊a[i]=1&&a[j]=1,而如果a[i]=1的话那么答案就一定会加上一个-c[i],所以从S向B中每一个点连边流量为b[i][j],b(i,j)再向新一排n个点中的i和j连边,容量inf,然后n个点向T连边权为c[i],这样Σb(i,j)-最小割即为答案。割左边代表不选i,割右边代表选i,对应付出相应代价。
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<queue> 5 #include<iostream> 6 #define N 300005 7 #define inf 0x3f3f3f3f 8 #define M 2000005 9 using namespace std; 10 int head[N],ver[M],nxt[M],f[M],tot; 11 void add(int a,int b,int c) 12 { 13 tot++;nxt[tot]=head[a];head[a]=tot;ver[tot]=b;f[tot]=c; 14 tot++;nxt[tot]=head[b];head[b]=tot;ver[tot]=a;f[tot]=0; 15 } 16 int S,T; 17 int ch[N]; 18 int zeng(int a,int b) 19 { 20 if(a==T)return b; 21 int r=0; 22 for(int i=head[a];i&&b>r;i=nxt[i]) 23 { 24 if(ch[ver[i]]==ch[a]+1&&f[i]) 25 { 26 int t=zeng(ver[i],min(f[i],b-r)); 27 f[i]-=t;f[i^1]+=t;r+=t; 28 } 29 } 30 if(!r)ch[a]=-1; 31 return r; 32 } 33 bool tell() 34 { 35 memset(ch,-1,sizeof(ch)); 36 queue<int>q;q.push(S);ch[S]=0; 37 while(!q.empty()) 38 { 39 int tmp=q.front();q.pop(); 40 for(int i=head[tmp];i;i=nxt[i]) 41 { 42 if(f[i]&&ch[ver[i]]==-1) 43 { 44 ch[ver[i]]=ch[tmp]+1; 45 q.push(ver[i]); 46 } 47 } 48 } 49 return ch[T]!=-1; 50 } 51 int dinic() 52 { 53 int t,r=0; 54 while(tell())while(t=zeng(S,inf))r+=t; 55 return r; 56 } 57 int n; 58 int main() 59 { 60 tot=1; 61 scanf("%d",&n); 62 int cnt=n;int tmp; 63 S=0;int ans=0; 64 for(int i=1;i<=n;i++) 65 { 66 for(int j=1;j<=n;j++) 67 { 68 scanf("%d",&tmp); 69 cnt++; 70 add(S,cnt,tmp); 71 add(cnt,i,inf); 72 add(cnt,j,tmp); 73 ans+=tmp; 74 } 75 } 76 T=cnt+1; 77 for(int i=1;i<=n;i++) 78 { 79 scanf("%d",&tmp); 80 add(i,T,tmp); 81 } 82 printf("%d\n",ans-dinic()); 83 return 0; 84 }
还有就是解方程连边,这样只需要n个点,但快不了多少。。。。。
B.bzoj 2039: [2009国家集训队]employ人员雇佣
Description
作为一个富有经营头脑的富翁,小L决定从本国最优秀的经理中雇佣一些来经营自己的公司。这些经理相互之间合作有一个贡献指数,(我们用Ei,j表示i经理对j经理的了解程度),即当经理i和经理j同时被雇佣时,经理i会对经理j做出贡献,使得所赚得的利润增加Ei,j。当然,雇佣每一个经理都需要花费一定的金钱Ai,对于一些经理可能他做出的贡献不值得他的花费,那么作为一个聪明的人,小L当然不会雇佣他。 然而,那些没有被雇佣的人会被竞争对手所雇佣,这个时候那些人会对你雇佣的经理的工作造成影响,使得所赚得的利润减少Ei,j(注意:这里的Ei,j与上面的Ei,j 是同一个)。 作为一个效率优先的人,小L想雇佣一些人使得净利润最大。你可以帮助小L解决这个问题吗?
这题就是很裸的二元关系了,但因为我实在想不出什么NB的方法所以只好去解方程了。。。
S向每个人连容量为Ai的边,每一对人之间连2*e[i][j]容量的边,每个人i再向T连Σe(i,j)。跑就好了。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<queue> 5 #include<algorithm> 6 #define ll long long 7 #define inf 1233211234567LL 8 #define N 1005 9 #define M 15000005 10 using namespace std; 11 int n; 12 int head[N],nxt[M],ver[M]; 13 ll f[M]; 14 int tot; 15 void add(int a,int b,ll c) 16 { 17 tot++;nxt[tot]=head[a];head[a]=tot;ver[tot]=b;f[tot]=c; 18 tot++;nxt[tot]=head[b];head[b]=tot;ver[tot]=a;f[tot]=0; 19 } 20 int S,T; 21 int ch[N]; 22 ll zeng(int a,ll b) 23 { 24 if(a==T)return b; 25 ll r=0; 26 for(int i=head[a];i&&b>r;i=nxt[i]) 27 { 28 if(ch[ver[i]]==ch[a]+1&&f[i]) 29 { 30 ll t=zeng(ver[i],min(b-r,f[i])); 31 r+=t;f[i]-=t;f[i^1]+=t; 32 } 33 } 34 if(!r)ch[a]=-1; 35 return r; 36 } 37 bool tell() 38 { 39 memset(ch,-1,sizeof(ch)); 40 ch[S]=0; 41 queue<int>q; 42 q.push(S); 43 while(!q.empty()) 44 { 45 int tmp=q.front();q.pop(); 46 for(int i=head[tmp];i;i=nxt[i]) 47 { 48 if(f[i]&&ch[ver[i]]==-1) 49 { 50 ch[ver[i]]=ch[tmp]+1; 51 q.push(ver[i]); 52 } 53 } 54 } 55 return ch[T]!=-1; 56 } 57 ll dinic() 58 { 59 ll r=0,t; 60 while(tell())while(t=zeng(S,inf))r+=t; 61 return r; 62 } 63 ll a[N]; 64 ll zong[N]; 65 signed main() 66 { 67 scanf("%lld",&n); 68 tot=1; 69 S=0;T=n+1; 70 for(int i=1;i<=n;i++)scanf("%lld",&a[i]); 71 for(int i=1;i<=n;i++)add(S,i,a[i]); 72 ll tmp;ll ans=0; 73 for(int i=1;i<=n;i++) 74 { 75 for(int j=1;j<=n;j++) 76 { 77 scanf("%lld",&tmp); 78 ans+=tmp; 79 if(i>j) 80 { 81 add(i,j,2*tmp); 82 add(j,i,2*tmp); 83 zong[i]+=tmp; 84 zong[j]+=tmp; 85 } 86 } 87 } 88 for(int i=1;i<=n;i++) 89 { 90 add(i,T,zong[i]); 91 } 92 printf("%lld\n",ans-dinic()); 93 return 0; 94 }
C.bzoj 3158 千钧一发
n<=1000.
显然暴力两两枚举是否能共存。
但如果图不是二分图(发现二分图真的好有用)的话网络流是做不了的。
那就强行找它的性质啊,然后发现两个偶数一定满足2,两个奇数一定满足1( (2a+1)^2+(2b+1)^2,最后是2乘一个奇数的形式),所以吧数分两边中间连inf的边,向源汇连自己的值,ans=Σai-最小割。
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cmath> 5 #include<cstring> 6 #include<queue> 7 #define ll long long 8 #define inf 0x3f3f3f3f 9 #define N 1005 10 using namespace std; 11 int tot; 12 int head[N],nxt[N*N+N],ver[N*N+N],f[N*N+N]; 13 void add(int a,int b,int c) 14 { 15 tot++;nxt[tot]=head[a];head[a]=tot;ver[tot]=b;f[tot]=c; 16 tot++;nxt[tot]=head[b];head[b]=tot;ver[tot]=a;f[tot]=0; 17 } 18 int S,T; 19 int ch[N]; 20 int zeng(int a,int b) 21 { 22 if(a==T)return b; 23 int r=0; 24 for(int i=head[a];i&&b>r;i=nxt[i]) 25 { 26 if(ch[ver[i]]==ch[a]+1&&f[i]) 27 { 28 int t=zeng(ver[i],min(b-r,f[i])); 29 r+=t;f[i]-=t;f[i^1]+=t; 30 } 31 } 32 if(!r)ch[a]=-1; 33 return r; 34 } 35 bool tell() 36 { 37 memset(ch,-1,sizeof(ch)); 38 ch[S]=0; 39 queue<int>q; 40 q.push(S); 41 while(!q.empty()) 42 { 43 int tmp=q.front();q.pop(); 44 for(int i=head[tmp];i;i=nxt[i]) 45 { 46 if(f[i]&&ch[ver[i]]==-1) 47 { 48 ch[ver[i]]=ch[tmp]+1; 49 q.push(ver[i]); 50 } 51 } 52 } 53 return ch[T]!=-1; 54 } 55 int dinic() 56 { 57 int r=0,t; 58 while(tell())while(t=zeng(S,inf))r+=t; 59 return r; 60 } 61 int n; 62 int aa[N],bb[N]; 63 int gcd(int a,int b) 64 { 65 if(b==0)return a; 66 return gcd(b,a%b); 67 } 68 const int pp=1000003; 69 int ha[1000005],nx[2000005],to; 70 ll key[2000005]; 71 void insert(ll p) 72 { 73 ll u=p%pp; 74 to++;ha[u]=to;nx[to]=ha[u];key[to]=u; 75 } 76 bool find(ll p) 77 { 78 ll u=p%pp; 79 for(int i=ha[u];i;i=nx[i]) 80 { 81 if(key[i]==p)return 1; 82 } 83 return 0; 84 } 85 int main() 86 { 87 tot=1; 88 int ans=0; 89 scanf("%d",&n); 90 S=0;T=n+1; 91 for(int i=1;i<=n;i++) 92 { 93 scanf("%d",&aa[i]); 94 } 95 for(int i=1;i<=n;i++) 96 { 97 scanf("%d",&bb[i]); 98 ans+=bb[i]; 99 } 100 for(int i=1;i<=n;i++) 101 { 102 if(aa[i]&1) 103 { 104 add(S,i,bb[i]); 105 } 106 else add(i,T,bb[i]); 107 } 108 for(int i=1;i<=n;i++) 109 { 110 if(aa[i]&1) 111 { 112 for(int j=1;j<=n;j++) 113 { 114 if(aa[j]%2==0) 115 { 116 ll p=(ll)aa[i]*aa[i]+(ll)aa[j]*aa[j]; 117 if(gcd(aa[i],aa[j])==1) 118 { 119 ll t=sqrt(p); 120 if(t*t==p)add(i,j,inf); 121 } 122 } 123 } 124 } 125 } 126 printf("%d\n",ans-dinic()); 127 return 0; 128 }
未完待续。。。。