【这是一个可能永远填不完的坑】网络流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); }
======================================== 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); }
========================================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; } }
========================================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; }
========================================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; }
========================================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"); } } }
========================================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); }
8、魔术球问题
题目传送门:https://www.luogu.org/problemnew/show/P2765
我们考虑怎样的两个球可以相邻地串在同一根柱子上,显然对于这样的两个球$i,j$,满足$i<j$且$i+j$是完全平方数。那么我们考虑如何判定能否放下$x$颗球,那么只需对$[1,x]$的数按照上面的方式连边,所需的最小柱数就是该图的最小路径覆盖。显然该图是一个DAG,那么按照上面的方法求出最小路径覆盖就可以解决问题了。关于实现方法,依然可以从小到大枚举球数,然后在残量网络上新增边累加答案。
To be continued……