2020.02.21.小测验
A.Nicole的生物期末考试
问题描述
少壮不努力,长大写程序。当年Nicole就是因为努力不够,现在正坐在期末考试的考场里做生物试卷。
某生态系统的物种之间发生了,这些物种分成了两派。“正派”有n1个物种,这些物种编号依次是1,2,…,n1;“反派”有n2个物种,这些物种编号依次是n1+1,n1+2,…,n1+n2。“正派”的一些物种和“反派”的一些物种有是有矛盾的,准确的说,有m对物种(i,j),其中i是“正派”的,j是“反派”的,它们相互之间有矛盾。
Nicole为了阻止的再次发生,决定赶走一些物种,使剩下的任意两个物种之间没有矛盾关系。Nicole当然希望保留下来的物种尽量多,Nicole很快计算出了最多能够保留下来多少个物种。这时,Nicole突然发现,他漏掉了一条矛盾关系——编号分别为1和2的两个“正派”的物种(保证n1≥2)之间也有矛盾关系。那么问题来了,这样的情况下最多能保留多少个物种下来呢?Nicole当然知道怎么算了,但是他还在考试呢,就把计算的任务交给你了。
输入格式
第一行两个整数n1,n2,m,表示“正派”物种数量,“反派”物种数量,矛盾关系的数量。
接下来m行,每行两个数i,j,表示第i个物种和第j个物种有矛盾。保证第i个物种使是“正派”的,第j个物种使“反派”的。保证没有重复描述的关系。
输出格式
输出两行,每行一个整数。第一行表示假如第1,2个物种没有矛盾时最多能够保留下来的物种数量,第二行表示假如第1,2个物种有矛盾时最多能够保留下来的物种数量。
样例输入
4 3 5 1 5 2 5 3 6 3 7 4 6
样例输出
4 3
样例解释
当第1,2个物种没有矛盾时,赶走第3,5,6个物种,保留第1,2,4,7个物种,剩下的物种之间没有矛盾,这是能使留下来的物种数量最多的方案之一。
当第1,2个物种有矛盾时,赶走第2,3,5,6个物种,保留第1,4,7个物种,剩下的物种之间没有矛盾,这是能使留下来的物种数量最多的方案之一。
数据范围
对于30%的数据,, 10;
对于60%的数据,, 100;
对于100%的数据,,,1 { }
首先拿到这道题本以为很难,去看B了,B想出了点眉头后又回来看A。
然后发现A是一道网络流/二分图板题
题目中正派和反派的矛盾关系实际上就是一个二分图,最后让我们求的是最多有多少物种(节点)能够保留下来。然后矛盾关系的两个点之间没有边,所以就是求二分图的最大独立集。
于是就有了两种做法:
- 网络流(最大流)
- 匈牙利跑最大匹配
第一问:
二分图最大匹配
第二问:
不再是二分图了!
生物1和生物2不能同时选为独立集了
独立集:求最大匹配,先选没有被匹配的点,在从每条匹配的边上选一个点
正解:
枚举从二分图中删掉生物1/2,求最大独立集
但是第二问将1,2连边后跑匈牙利,虽然不是匈牙利跑的不是二分图,但是能过 /xyx
: (非正解)
#include<bits/stdc++.h> using namespace std; const int N=50005; const int M=1000005; int n1,n2,m,ans,vis[N],_link[N]; int Last[N],Next[M],End[M],tot; void cb(int x,int y){End[++tot]=y,Next[tot]=Last[x],Last[x]=tot;} int solve(int x,int f){ for(int i=Last[x];i;i=Next[i]){ int y=End[i]; if(vis[y]!=f){ vis[y]=f; if(!_link[y] || solve(_link[y],f)){ _link[y]=x; return 1; } } } return 0; } int main(){ scanf("%d%d%d",&n1,&n2,&m); for(int i=1;i<=m;i++){ int x,y; scanf("%d%d",&x,&y); cb(x,y); } for(int i=1;i<=n1;i++) ans+=solve(i,i); printf("%d\n",n1+n2-ans); ans=0; cb(1,2); memset(vis,0,sizeof(vis)); memset(_link,0,sizeof(_link)); for(int i=1;i<=n1;i++) ans+=solve(i,i); printf("%d\n",n1+n2-ans); return 0; }
:(正解)
#include<bits/stdc++.h> using namespace std; const int N=50005; const int M=1000005; int n1,n2,m,ans,vis[N],_link[N]; int Last[N],Next[M],End[M],tot; void cb(int x,int y){End[++tot]=y,Next[tot]=Last[x],Last[x]=tot;} int solve(int x,int f){ for(int i=Last[x];i;i=Next[i]){ int y=End[i]; if(vis[y]!=f){ vis[y]=f; if(!_link[y] || solve(_link[y],f)){ _link[y]=x; return 1; } } } return 0; } int main(){ scanf("%d%d%d",&n1,&n2,&m); for(int i=1;i<=m;i++){ int x,y; scanf("%d%d",&x,&y); cb(x,y); } for(int i=1;i<=n1;i++) ans+=solve(i,i); printf("%d\n",n1+n2-ans); memset(_link,0,sizeof(_link)); memset(vis,0,sizeof(vis)); ans=0; for(int i=2;i<=n1;i++) ans+=solve(i,i); int ans2=0; memset(_link,0,sizeof(_link)); memset(vis,0,sizeof(vis)); for(int i=1;i<=n1;i++) if(i!=2) ans2+=solve(i,i); printf("%d\n",n1+n2-min(ans,ans2)-1); return 0; }
B.最小边数最小割
问题描述
给一个N个节点的有向图。 求1号节点到N号节点的最小割,以及最小割的最小边数。
输入格式
第一行两个整数N,M。
接下来M行,每行三个整数s,t,c,表示s到t有一条容量为c的边。
输出格式
第一行输出一个整数,表示最小割。
第二行输出一个整数,表示最小割的最小边数。
样例输入
6 7 1 2 5 1 3 5 2 4 2 2 5 2 3 5 3 4 6 5 5 6 5
样例输出
7 2
样例解释
样例最小割等于7,容易发现只有两个最小割,其中边数少的是2条。
数据范围
,
,,1 { }
众所周知,网络流的最大流=最小割
所以第一个问跑一个最大流就出来了。
那么如何求最小割割的最少边数呢?
方法1:
建图,跑一遍最大流,这时有些边满流(流量==0)
满流的边说明一定某个最小割中
在残量网络上重新搞搞:
将满流的边(非反边)的流量改为1
将未满流的边(非反边)的流量改为
再跑一遍最大流,就能得到最小割的最少边数
方法2:(只跑一次最大流)
让边原来的流量,边的数量 合并到一起 => 边新的容量
新图边流量 = 原来流量 * 很大的一个数(至少大于最小割的边数 , m+1) + 1
新图的最小割:边集就是原来的最小割之一
= 原来最小割 (图的总边数 + 1)+ 最小割的边数
原图最小割=新图最小割 / (M+1)
最小割边数 = 新图最小割 % (M+1)
#include<bits/stdc++.h> #define ll long long using namespace std; const int N=1005; const int M=100005; const ll inf=1e9; int n,m,k,S,T; int Last[N],End[M],Next[M],tot; ll len[M]; int dis[N],gap[N],_last[N]; ll ans; inline void cb(int x,int y,ll z){ End[tot]=y,Next[tot]=Last[x],len[tot]=z,Last[x]=tot++; } void bfs(){ for(int i=1;i<=T;i++) dis[i]=T,gap[i]=0; gap[0]=0; dis[T]=0; queue<int> q; q.push(T); while(q.size()){ int x=q.front(); q.pop(); for(int i=Last[x];i;i=Next[i]){ int y=End[i]; if(dis[y]>dis[x]+1){ dis[y]=dis[x]+1; q.push(y); } } } for(int i=1;i<=T;i++){ gap[dis[i]]++; _last[i]=Last[i]; } } ll isap(int x,ll f){ if(x==T) return f; ll flow=0; for(int &i=_last[x];i;i=Next[i]){ int y=End[i]; if(len[i] && dis[x]==dis[y]+1){ ll p=isap(y,min(f-flow,len[i])); flow+=p; len[i]-=p; len[i^1]+=p; if(f==flow || dis[S]==T) return flow; } } gap[dis[x]]--; if(!gap[dis[x]]) dis[S]=T; dis[x]++; gap[dis[x]]++; _last[x]=Last[x]; return flow; } int main(){ scanf("%d%d",&n,&m); S=1,T=n,tot=2; for(int i=1;i<=m;i++){ int x,y,z; scanf("%d%d%d",&x,&y,&z); cb(x,y,z),cb(y,x,0); } bfs(); while(dis[S]<T) ans+=isap(S,inf); printf("%lld\n",ans); ans=0; for(int i=2,j=1;j<=m;i+=2,j++){ if(len[i]==0) len[i]=1,len[i^1]=0; else len[i]=inf,len[i^1]=0; } bfs(); while(dis[S]<T) ans+=isap(S,inf); printf("%lld",ans); return 0; }
C.不走寻常路
问题描述
不喜欢走寻常路。
沙坪坝的地形可以看做一个无向图,包含个节点和条无向边,节点编号为 ~ 。其中节点是的家,节点是中学。
经过调查,认为一些节点和一些边很“寻常”,于是规定了这些节点和边的行走次数上限。为了避免走寻常路,往返家到学校时经常更换线路,避免超过这些次数上限。
那么问题来了,在不超过次数上限的情况下,最多可以往返家到学校多少次呢?数据保证次数有限。
输入格式
第一行两个整数,表示节点数、边数。
第二行个整数,表示每个节点的次数上限,如果则表示没有上限。保证。
接下来行,每行三个整数,表示一条连接的无向边,次数上限为,如果表示没有上限。
输出格式
输出一个整数,表示可以往返家到学校的次数。
样例输入 1
5 8 0 0 8 0 0 1 2 6 1 3 7 1 4 5 2 3 2 2 5 4 3 4 0 3 5 0 4 5 6
样例输出 1
8
样例输入 2
3 3 0 5 0 1 2 0 1 3 7 2 3 0
样例输出 2
6
样例说明
样例数据1如图所示,在不超过次数上限的情况下,可以沿着走次,沿着走次,沿着走次,沿着 走次。一共可以往返次。
所有测试数据,,,,保证没有重边和自环。
看到这道题的第一眼就开始慌了,心里想着不会,硬是想了也没有想出什么....可能是我太菜了 此处%%% 巨佬
然后仔细的分析了一下,发现此题的恶心之处在于有点的限制次数和边的限制次数。
然后我们可以通过样例的图发现:
一个点如果有限制的次数,那么会影响到它所连向的所有边的限制次数
于是我们就可以考虑<stron>,但此处是下放到所有连接的边,不难发现,一个下放点权后,一个边的权值={x的点权,y的点权,边的权值}</stron>
好,现在点权问题解决了,那么是不是就可以多次从1号点开始搜索,也不难发现,一条合法的从1到n的路径上的经过次数=当前路径上所有边权的最小值。直到1号节点所连出的所有边的权值都为0就停止搜索。(**输入进来的0改为**)
但是凭借我的代码力,这样的搜索多半是写不出来的....(写出来也肯定会T...)
所以我们要思考一个更好的方法!!!
样例说明中已经告诉了我们
往返次数=从1到n的所有路径
所以这个问题又被转化成求从1到n的路径个数。
然后每一条边又限制了次数(很容易联想到网络流的流量)
然后就会发现这道题可以用网络流的最大流来做!!!!
边权等于流量,则最大流就是路径数。
然后将点权下方权就能写出程序。
但是你以为这样就完了吗?
我们发现样例2过了,样例1跑出来的答案是9。
然后我们观察样例1的那张图,可以发现之前我们总结出来的结论有误
有点权限制的点,其影响到所有连接与它的边且影响公共存在
公共存在是什么意思?
比如说下图中的3号节点,影响到了(反向边)这些边,而且经过3号节点时,所有被它影响到的边的次数应该同时减少。即这个点的限制次数(点权)是被所连接的边公共享用的
所以我们在或 找增广路时,添加一个条件(当前要去的点的点权不等于0),当然点权也要减去当前增广的流量!!!
于是这道题才真的被做出来!!!
#include<bits/stdc++.h> using namespace std; namespace Reader{//fread快读 const int BUF_SIZE=1048576; char rr[BUF_SIZE],*csy1,*csy2; #define GC (csy1==csy2&&(csy2=(csy1=rr)+fread(rr,1,BUF_SIZE,stdin),csy1==csy2)?EOF:*csy1++) template <class T> inline void RI(T &t){ int u=0;char c=GC; for(t=0;c<48 || c>57;c=GC){ if(c==EOF) return; u=c=='-'?1:0; } for(;c>47 && c<58;c=GC) t=(t<<1)+(t<<3)+c-48; t=u?-t:t; } inline void RC(char &c,int Rangemin=0,int Rangemax=127){ for(c=GC;c<Rangemin || c>Rangemax;c=GC); } inline void RS(char *s){ char c=GC; for(*s=0;c<33;c=GC) if(c==EOF) return; for(;c>32;c=GC) *s++=c; *s=0; } char pbuf[BUF_SIZE],*csy=pbuf; inline void WC(const char c){ if(csy-pbuf==BUF_SIZE) fwrite(pbuf,1,BUF_SIZE,stdout),csy=pbuf; *csy++=c; } template <class T> inline void WI(T x){ static int sta[38]; int top=0; if(x<0) { WC('-'); do{ sta[top++]=-(x%10); x/=10; }while(x); } else do{ sta[top++]=x%10; x/=10; }while (x); while(top) WC(sta[--top]+'0'); } inline void flush(){ fwrite(pbuf,1,csy-pbuf,stdout); csy=pbuf; } } const int N=50005; const int M=5000005; const int inf=1e9; int n,m,ans,w[N],S,T; int Last[N],Next[M],End[M],len[M],tot; int dis[N],gap[N],_last[N]; void cb(int x,int y,int z,int z0){ End[++tot]=y,Next[tot]=Last[x],len[tot]=z,Last[x]=tot; End[++tot]=x,Next[tot]=Last[y],len[tot]=z0,Last[y]=tot; } void bfs(){ for(int i=1;i<=T;i++) dis[i]=T; dis[T]=0; queue<int> q; q.push(T); while(q.size()){ int x=q.front(); q.pop(); for(int i=Last[x];i;i=Next[i]){ int y=End[i]; if(dis[y]>dis[x]+1){ dis[y]=dis[x]+1; q.push(y); } } } for(int i=1;i<=T;i++){ gap[dis[i]]++; _last[i]=Last[i]; } } int isap(int x,int f){ if(w[x]==0) return 0; if(x==T) return f; int flow=0; for(int &i=_last[x];i;i=Next[i]){ int y=End[i]; if(len[i] && dis[x]==dis[y]+1 && w[y]>0){//此题核心 w[y]>0 int p=isap(y,min(f-flow,min(len[i],w[y]))); flow+=p; len[i]-=p; len[i^1]+=p; w[y]-=p;// 减去增广代价 if(f==flow || dis[S]==T) return flow; } } gap[dis[x]]--; if(!gap[dis[x]]) dis[S]=T; dis[x]++; gap[dis[x]]++; _last[x]=Last[x]; return flow; } int main(){ Reader::RI(n); Reader::RI(m); S=1,T=n,tot=1; for(int i=1;i<=n;i++){ Reader::RI(w[i]); if(w[i]==0) w[i]=inf; } for(int i=1;i<=m;i++){ int x,y,z; Reader::RI(x); Reader::RI(y); Reader::RI(z); if(z==0) z=inf; cb(x,y,z,0); cb(y,x,z,0); } bfs(); while(dis[S]<T) ans+=isap(S,inf); ans/=2; // Reader::WI(ans); // Reader::flush(); printf("%d",ans); return 0; }
总体来说,题目正如说的那样不难(逃