好像还能用isap和矩阵dp的,不过我没咋闹明白isap,分层能看懂后面就完全看不懂了,等今天晚上看懂了在写一个用isap做这道题的方法。嗯,立flag;
题意:给出 行数m和列数n,和各行各列的和,求这个矩阵可能的排列,不同的结果不同的输出,我都写到注释上面了。
方法:dinic,就是ek算法的增强版,先以源点为0,然后对整个网络进行分层,一层一层的分,分完层在进行dfs,跑一个while,不断的对残余网络分层,搜索,知道分层分不了了,也就是说到不了终点了就完事。
思路是不难理解的,见图的思路很值得学习,
注意点1 有一个弧优化,就是在进行dfs的时候,这样dfs就可以用while了,试想dfs的对树的搜索,如果用这个方法,就可以在达到目的之前对 左,中,右进行遍历。不然只会返回一个结果,
2 在对 残残残残残余网络之后见图 在判断 路径的大小,这样就会很省时间
#include <queue>
#include <vector>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <iostream>
#include <algorithm>
#include <functional>
const int maxn=2000;
using namespace std;
typedef long long ll;
struct edge{
int to,cap,rev;
edge(int a,int b,int c){to=a,cap=b,rev=c;}
//分别是目的地,记录反向边的坐标,和容量;
};
vector<edge>G[maxn];
vector<int>G1[maxn];
bool vis[maxn];
int cengci[maxn];
int n,m;
int itr[maxn];
void addedge(int from,int to,int value)
{ G[from].push_back((edge){to,value,G[to].size()});
G[to].push_back((edge){from,0,G[from].size()-1});
}
void add_edge(int from,int to)
{ G1[from].push_back(to);
}
void bfs(int s)
{ queue<int>q;
q.push(s);
memset(cengci,-1,sizeof(cengci));
cengci[s]=0;//这和上面一行都是分层必要的初始化;
while(!q.empty())
{ int u=q.front();
q.pop();
for(int i=0;i<G[u].size();i++)
{ edge &s=G[u][i];
if(cengci[s.to]<0&&s.cap>0)
{cengci[s.to]=cengci[u]+1;//分层;
q.push(s.to);}
}
}
}
int dfs(int s,int t,int f)
{ //int flow;
if(s==t) return f;//dfs结束的条件
for(int &i=itr[s];i<G[s].size();i++)
{ edge &w=G[s][i];
if(w.cap>0&&cengci[w.to]>cengci[s])
{ int d=dfs(w.to,t,min(w.cap,f));
if(d>0)
{ w.cap-=d;
G[w.to][w.rev].cap+=d;//反向边,自己画一个二维数组就理解了。g就是二位数组。
return d;
}
}
}
return 0;
}
int max_flow(int s,int t)
{ int INF=0x3f3f3f3f;
int flow=0;
int cas=1;
while(1)
{ bfs(s);
memset(itr,0,sizeof(itr));
if(cengci[t]<0)
{ //printf("%d!!! %d\n",t,cengci[t]);
//printf("%d!!!!\n",cas++);
return flow;}
int l;//=dfs(s,t,INF);
while(l=dfs(s,t,INF))//多次进行dfs,而不是一次分层然后一次dfs,
flow+=l;
}
}
bool judge_dfs(int x,int val)
{ vis[x]=1;
for(int i=0;i<G1[x].size();i++)
{ int t=G1[x][i];
if(t==val) continue;//不找反向边。反向边一定能成为环所以你在逗我??
if(vis[t]) return 1;//确定已经找到环了,因为vis被预定就是说明是环了
if(judge_dfs(t,x)) return 1;//继续dfs,把后继节点的t变成左边的x,这样在下一个dfs中如果t==val就是说明
//他的后继节点是前驱结点,所以就是环咯。
}
vis[x]=0;
return 0;
}
bool judge()
{ memset(vis,0,sizeof(vis));
for(int i=0;i<=m+n+1;i++)
{ for(int j=0;j<G[i].size();j++)
{edge &e=G[i][j];
if(e.cap>0)//你看你看,如果在残余网络中边还存在,那么就见图,新建的图已经找不到一条增广路径了555;
add_edge(i,e.to);}
}
for(int i=0;i<=n;i++)
if(judge_dfs(i,-1)) return 1;//第二个参数设定成多少都行,只要不在 点的范围里。。就是钦定的边,就是说,如果判断某个具体的点能不能
//构成环。
return 0;
}
int main(){
int T;
int a,b,t=1;
scanf("%d",&T);
while(T--)
{ scanf("%d%d",&n,&m);
for(int i=0;i<maxn;i++)
{ G[i].clear();
G1[i].clear();
}
int sum1=0,sum2=0,sum=0;
for(int i=1;i<=n;i++)
{scanf("%d",&a);
addedge(0,i,a);
sum+=a,sum1+=a;//把所有杭上的点给加了
//也就是左边的原点到左边一排的最大流。
}
for(int j=n+1;j<=m+n;j++)
{ scanf("%d",&a);
addedge(j,m+n+1,a);
sum2+=a;//右边的最大流;
}
if(sum1!=sum2)
{printf("Case #%d: So naive!\n",t++);//不能成立,数据有问题啊哈哈
continue;
}
for(int i=1;i<=n;i++)
for(int j=1+n;j<=m+n;j++)
{ addedge(i,j,9);
}
int ans=max_flow(0,m+n+1);
//printf("%d %d*******\n",ans,sum);
if(ans!=sum)
printf("Case #%d: So naive!\n",t++);//最大流跑不到和,不能成立。
else
{ if(judge())
printf("Case #%d: So young!\n",t++);//好多种类方法。
//在这个图中
//这个图表示的意思 就是各个行和和列和相互制约关系的成立。
//而这个图中有环,就说明了把每个边权加一个少一个都是没关系的。这部就有多种情况了嘛。
else
printf("Case #%d: So simple!\n",t++);//只有一种情况,费了好大劲做出来。-。-好简单啊。
}
}
return 0;
}