网络流24题(持续更新
Posted 吃花椒的妙酱
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络流24题(持续更新相关的知识,希望对你有一定的参考价值。
记录下网络流的学习,以及自己的一些理解
1.DAG最小路径覆盖
题目大意:DAG求最小路径覆盖,输出方案
思路:将n个点看成单独n条路径,接下来拆点,将每个点放在入点出点两个集合中,如果有边x->y,则x出指向y入,初始化时候所有点出点连源点,入点连汇点,最小路径数 = 总路径n - 最大流,最大流就是我们去匹配出入点的最大边数,实质就是匈牙利算法啊
建图示意 ,图中只有1->3一条路径可以从原始n条路径中拼接起来,即3-1=2,最小路径数,我们发现,连向汇点的点如果不和源点联通(点1,2),说明它会作为一条新路径的起点,即要新开一条路给它,因为输出方案时候要用,也就是说它连向汇点的边的流量此时为1(因为它未用过啊),edge[T^1].flow==1
注意点:(可能对于我来说),链式前向星从0或者2开始存图,因为要存反边,平时都从1开始存的,反边变成0了,调了两天才看出来
———————————————————————————————————————————————————————
回头又思烤了虾,说是拆成出点和入点有问题,就是进行边的匹配,尽可能的选多的边,才能使得覆盖路径最少,说拆点反而有误导倾向了 = = ||
如果拆点做的话,按照正常逻辑建图,点i的出入点连边,而每个点都可以作为路径的起点和终点,那最大流肯定为n了
#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <map>
#include <vector>
#include <queue>
using namespace std;
#define _for(i,a,b) for(int i=(a) ;i<=(b) ;i++)
#define _rep(i,a,b) for(int i=(a) ;i>=(b) ;i--)
#define mst(v,s) memset(v,s,sizeof(v))
#define pb push_back
#define IOS ios::sync_with_stdio(false)
//#define int long long
#define inf 0x3f3f3f3f
typedef long long ll;
const int N=1e4+10;
int n,np,nc,m;
int s,t;
int d[N];
int pre[N];
int nxt[N];
struct ty
{
int t,next,flow;
}edge[5010];
int tot=1,head[N];
void add(int x ,int y, int flow)
{
edge[++tot]={y,head[x],flow};
head[x]=tot;
edge[++tot]={x,head[y],0};
head[y]=tot;
}
void ini()
{
tot=1;
mst(head,-1);
}
bool bfs(int t)
{
mst(d,0);
queue <int> q;
q.push(0);
d[0]=1;
while( !q.empty() )
{
int x = q.front();
q.pop();
if( x==t ) return true;
for(int i=head[x] ;i!=-1;i=edge[i].next)
{
int y = edge[i].t;
if( edge[i].flow > 0 && !d[y] )
{
d[y] = d[x] + 1;
q.push(y);
}
}
}
return false;
}
int dfs(int x ,int flow ,int t)
{
if( x==t ) return flow;
int sum=0;
for(int i=head[x] ;i!=-1;i=edge[i].next)
{
int y = edge[i].t;
if( d[y] == d[x] +1 && edge[i].flow>0 )
{
int temp = dfs(y,min(edge[i].flow , flow-sum),t);
if( temp )//有流就记录路径
{
nxt[x] =y-n;//记录下一步
}
edge[i].flow -= temp;
edge[i^1].flow += temp;
sum += temp;
if( sum == flow ) return flow;
}
}
return sum;
}
signed main()
{
///!!!
// freopen("data.txt","r",stdin);
//!!!=
IOS;
ini();
cin>>n>>m;
s=0,t=2*n+1;
//实质是二分图匹配
_for(i,1,n)
{
add(0,i,1);add(i+n,2*n+1,1);
}
_for(i,1,m)
{
int x,y;
cin>>x>>y;
add(x,y+n,1);
}
int ans=0;
while( bfs(t) ) ans += dfs(s,inf,t);
for(int i=head[t] ;i!=-1 ;i=edge[i].next)
{
if( edge[i^1].flow ==1) //如果此点连到汇点
//说明是所在路径的起点,注意对于汇点来说它是反边
{
int y = edge[i].t - n;
while( y )
{
cout<<y<<" ";
y = nxt[y];
}
cout<<endl;
}
}
cout<<n-ans<<endl;
}
2 魔术球
题目大意:n个柱子,每个柱子上放数字,相邻数字的和必须为平方数,求n个柱子最多放几个数字
思路:既然数字的接龙有限制,若x+y为平方数,我们就对x,y连边,每个柱子肯定放的数字越多越好,柱子数也要尽可能的少,那不就转化为上一题dag最小路径覆盖了吗?
柱子数就是路径数量,建图和上面几乎一样
不过这里路径条数规定好了,所以我们不断往图里加点,如果路径条数仍小于n就继续加点,否则break。简单说就加一个点,跑一下网络流,直到路径数多于n。
注意:要顺序输出柱子的方案,而且卡格式,不能输出多余空格 = = ||
#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <map>
#include <vector>
#include <queue>
using namespace std;
#define _for(i,a,b) for(int i=(a) ;i<=(b) ;i++)
#define _rep(i,a,b) for(int i=(a) ;i>=(b) ;i--)
#define mst(v,s) memset(v,s,sizeof(v))
#define pb push_back
#define IOS ios::sync_with_stdio(false)
#define inf 0x3f3f3f3f
typedef long long ll;
const int N=2e5+10;
const int p = 100000;
int n,np,nc,m;
int s,t;
int d[N];
int pre[N];
int nxt[N];
int num[N],cnt;
struct ty
{
int t,next,flow;
}edge[N];
int tot=1,head[N];
void add(int x ,int y, int flow)
{
edge[++tot]={y,head[x],flow};
head[x]=tot;
edge[++tot]={x,head[y],0};
head[y]=tot;
}
void ini()
{
tot=1;
mst(head,-1);
}
bool bfs(int t)
{
mst(d,0);
queue <int> q;
q.push(s);
d[s]=1;
while( !q.empty() )
{
int x = q.front();
q.pop();
if( x==t ) return true;
for(int i=head[x] ;i!=-1;i=edge[i].next)
{
int y = edge[i].t;
if( edge[i].flow > 0 && !d[y] )
{
d[y] = d[x] + 1;
q.push(y);
}
}
}
return false;
}
int dfs(int x ,int flow ,int t)
{
if( x==t ) return flow;
int sum=0;
for(int i=head[x] ;i!=-1;i=edge[i].next)
{
int y = edge[i].t;
if( d[y] == d[x] +1 && edge[i].flow>0 )
{
int temp = dfs(y,min(edge[i].flow , flow-sum),t);
if( temp )//有流就记录路径
{
nxt[x] =y-p;//记录下一步
}
edge[i].flow -= temp;
edge[i^1].flow += temp;
sum += temp;
if( sum == flow ) return flow;
}
}
return sum;
}
bool check(int x ,int y)
{
int temp=x+y;
// cout<<" temp "<<temp<<" "<<(int) sqrt(temp)*(int) sqrt(temp)<<endl;
return (int) sqrt(temp)*(int) sqrt(temp) == temp;
}
signed main()
{
///!!!
// freopen("data.txt","r",stdin);
//!!!=
// IOS;
ini();
cin>>n;
s=0,t=2e5+1;
int ans=0;
for(int i=1;;i++)//枚举加入的数
{
add(s,i,1);add(i+p,t,1);
_rep(j,i-1,1)
{
// cout<<" i j "<<i<<" "<<j<<endl;
if( check(i,j) )
{
// cout<<" ok "<<j<<"-> "<<i<<endl;
add(j,i+p,1);
continue;
}
}
while( bfs(t) ) ans += dfs(s,inf,t);
if( i - ans > n )//柱子放不下了
{
cout<<i-1<<endl;
for(int j=head[t] ;j!=-1 ;j=edge[j].next)
{
if( edge[j^1].flow==1 && edge[j].t-p!=i )//如果没到汇点
//说明是路径起点
{
int y = edge[j].t-p;
num[++cnt] = y;
}
}
break;
}
}
sort(num+1,num+1+cnt);//对柱子起点排序
_for(i,1,cnt)
{
int y = num[i];
while(y)
{
if(nxt[y]!=0 )cout<<y<<" ";
else cout<<y;
y = nxt[y];
}
cout<<endl;
}
}
3最长递增子序列问题
n<=500
思路:(1)入门dp略了
(2)拆点——出点入点,跑完问题1的dp后,我们获得dp[i],以第i个数为结尾的最长上升子序列的长度。
如果求多条的话,就可以用网络流了。容量为1——每个数字只能用一次。两个数字a[i],a[j]要连边的话要符合dp[i] == dp[j] + 1&&a[i]>=a[j],因为要组成最多数的最长上升子序列的话,至少要遵照问题1跑出来的dp值相邻才能进行连边,然后dp值为1的连源点,dp值最大的连汇点,起点终点的选择应该挺好理解的。
(3)x1xn次数无限制,那就给1和n的出入点之间加容量为无穷的边,1和源点连inf的边,n和汇点的连边需要判断一下dp[n]是否为最大值(注意点),dp[1]因为肯定最小所以肯定为起点,dp[n]则不一定
#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <map>
#include <vector>
#include <queue>
using namespace std;
#define _for(i,a,b) for(int i=(a) ;i<=(b) ;i++)
#define _rep(i,a,b) for(int i=(a) ;i>=(b) ;i--)
#define mst(v,s) memset(v,s,sizeof(v))
#define pb push_back
#define IOS ios::sync_with_stdio(false)
//#define int long long
#define inf 0x3f3f3f3f
typedef long long ll;
const int N=1e4+10;
const int p = 100000;
int s,t,n;
int d[N];
int a[N],dp[N];
int nxt[N];
int ans1;
struct ty
{
int t,next,flow;
}edge[N];
int tot=1,head[N];
void add(int x ,int y, int flow)
{
edge[++tot]={y,head[x],flow};
head[x]=tot;
edge[++tot]={x,head[y],0};
head[y]=tot;
}
void ini()
{
tot=1;
mst(head,-1);
}
bool bfs(int t)
{
mst(d,0);
queue <int> q;
q.push(s);
d[s]=1;
while( !q.empty() )
{
int x = q.front();
q.pop();
if( x==t ) return true;
for(int i=head[x] ;i!=-1;i=edge[i].next)
{
int y = edge[i].t;
if( edge[i].flow > 0 && !d[y] )
{
d[y] = d[x] + 1;
q.push(y);
}
}
}
return false;
}
int dfs(int x ,int flow ,int t)
{
if( x==t ) return flow;
int sum=0;
for(int i=head[x] ;i!=-1;i=edge[i].next)
{
int y = edge[i].t;
if( d[y] == d[x] +1 && edge[i].flow>0 )
{
int temp = dfs(y,min(edge[i].flow , flow-sum),t);
edge[i].flow -= temp;
edge[i^1].flow += temp;
sum += temp;
if( sum == flow ) return flow;
}
}
return sum;
}
void solve()
{
//拆点
s=0,t=2*n+1;
_for(i,1,n)
{
if( dp[i] == 1 ) add(s,i,1);//dp值为1,向起点连边
if( dp[i] == ans1 ) add(i+n,t,1);//dp值为max,要作为路径终点,向汇点连边
_for(j,i+1,n)
{
if( dp[i] +1 == dp[j] && a[j]>=a[i] )//不仅要dp值相邻且a[i]值也要满足不下降
{
add(i+n,j,1);
}
}
add(i,i+n,1);//入点向出点连边
}
int ans=0;
while( bfs(t) ) ans += dfs(s,inf,t);-----
cout<<ans<<endl;
add(1,1+n,inf);add(s,1,inf);
add(n,n+n,inf);
if( dp[n] == ans1)
add(n+n,t,inf);
while( bfs(t) ) ans += dfs(s,inf,t);
cout<<ans<<endl;
}
signed main()
{
///!!!
// freopen("data.txt","r",stdin);
//!!!=
IOS;
ini();
cin>>n;_for(i,1,n) cin>>a[i];
_for(i,1,n) dp[i]=1;
_for(i,2,n)
{
_for(j,1,i-1)
{
if( a[i] >= a[j] ) dp[i] = max(dp[j]+1,dp[i]);
}
ans1 = max(ans1,dp[i]);
}
cout<<ans1<<endl;
solve();
}
以上是关于网络流24题(持续更新的主要内容,如果未能解决你的问题,请参考以下文章