hdu4975 A simple Gaussian elimination problem.(正确解法 最大流+删边判环)(Updated 2014-10-16)
这题标程是错的,网上很多题解也是错的。
http://acm.hdu.edu.cn/showproblem.php?pid=4975
2014 Multi-University Training Contest 10
A simple Gaussian elimination problem.Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others) Problem Description
Dragon is studying math. One day, he drew a table with several rows
and columns, randomly wrote numbers on each elements of the table. Then he counted the sum of each row and column. Since he thought the map will be useless after he got the sums, he destroyed the table after that. However Dragon's mom came back and found what he had done. She would Could you help Dragon to do that? Input
The first line of input contains only one integer, T(<=30), the
number of test cases. Following T blocks, each block describes one test case. There are three lines for each block. The first line The second line contains N integer show the sum of each row. The third line contains M integer show the sum of each column. Output
Each output should occupy one line. Each line should start with "Case
#i: ", with i implying the case number. For each case, if we cannot get the original table, just output: "So naive!", else if we can reconstruct the table by more than one ways, you should output one line contains only: "So young!", otherwise (only one way to reconstruct the table) you should output: "So simple!". Sample Input
3
1 1 5 5 2 2 0 10 0 10 2 2 2 2 2 2 Sample Output
Case #1: So simple!
Case #2: So naive! Case #3: So young! Source
Recommend
|
题意:有n行m列矩阵,给出各行的和、各列的和,矩阵元素需要为0~9,判断无解、唯一解、多解。
题解:网络流+判环。
和hdu4888几乎一模一样,hdu4888题解链接:http://www.cnblogs.com/yuiffy/p/3891639.html
这题的做法就是hdu4888的做法,点上面的链接怒看如何建图,我下面说这题的不同之处。网上很多题解都是错的,标程也是错的。
这题标程有错,但没有反例的数据,所以数据还是对的。但标程使用的错误方法使它运行时间巨短,导致这题时限开得少,直接拿超碉优化的网络流+简单dfs判环也是过不了的。
正确做法应该是用更好的判环方法,我想到的是 dfs判环回溯时删边(不是删点),代码如下:
bool dfs(const int &x,const int &prex) {///深搜判环
int biu=-;
walked[x]=true;
for (int i=head[x]; i!=-; i=e[i].next) {
if(e[i].v==prex){
biu=i;
continue;
}
if (e[i].cap>) {
if(walked[e[i].v]) return true;
if(dfs(e[i].v,x)) return true;
}
if(biu==-) head[x]=e[i].next;///删边,因为这条边为0或者走了这条边却没发现环
else e[biu].next=e[i].next;
biu=i;
}
walked[x]=false;
return false;
}
可以想到,如果选择一条边递归,然后从递归里出来了,说明没找到环,那么走这条边肯定找不到环,以后也不用走了,可以把这条边删了。或者这条边流量为0,也可以删了。
这样的话每条边最多只进一次,O(m)。
其实4888也可以用这种方法,不过4888还要输出结果,所以先记录结果再删。
然后我说一下网上常见的错误解法,因为没有反例数据,这种错误解法可以ac。
错误的dfs判环:
bool walked[maxn];
bool dfs(const int &x,const int &preE) {
int pp=preE^;
for (int &i=head[x]; i!=-; i=e[i].next) {
if(i==(pp))continue;
if (e[i].cap>) {
if(walked[e[i].v]) return true;
walked[e[i].v]=true;
if(dfs(e[i].v,i)) return true;
walked[e[i].v]=false;
}
}
return false;
}
网上很多地方都说这个int &i是个优化,加了这个优化就能过。其实这不是优化,这是乱搞,碰巧没有反例数据。
int &i=head[x]的意思是把i当成head[x]的引用,也就是i其实就是head[x]。
这样,i=e[i].next相当于head[x] = e[head[x]].next,是会改变head[x]的,也就是把上一条出边给扔了!这和我上面说的删边不一样,我上面的是只删递归过的边,而这个会把不能走的边也删掉。这题里不能走的边就是直接回头的那条边。
这样会发生什么情况呢?我来画个图:
1和3、2和3、2和4之间有双向边,而1和4之间只有单向边。当深搜1->3->2->4时,会把4的出边全扔了。本来我们应该找到的环是1->4->2->3->1,可是4的出边已经没了,找不到这个环。
由于大家建图的方法、网络流的方法、dfs的顺序不一致,所以这题不好找到对所有代码都成立的反例输入。如果谁发现好的样例的话希望能写出来,怒艹错误解法。
这个错解有人说是比赛时试出来的;还有人比赛时试验只在m,n<=200时判多解,居然正好也能A…怕了……还是希望以后出题人能认真一点,弄好标程和数据。
再说一下标程的错误解法:标程也是回溯时删东西,不过不是删边是删点……点和你无冤无仇,为什么删它?搜完一个点所有出边可不能保证这个点就没用了…就像我上面给的例子的点4。
代码:
//#pragma comment(linker, "/STACK:102400000,102400000")
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
#include<set>
#include<stack>
#include<queue>
using namespace std;
#define ll long long
#define usll unsigned ll
#define mz(array) memset(array, 0, sizeof(array))
#define minf(array) memset(array, 0x3f, sizeof(array))
#define REP(i,n) for(i=0;i<(n);i++)
#define FOR(i,x,n) for(i=(x);i<=(n);i++)
#define RD(x) scanf("%d",&x)
#define RD2(x,y) scanf("%d%d",&x,&y)
#define RD3(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define WN(x) prllf("%d\n",x);
#define RE freopen("D.in","r",stdin)
#define WE freopen("1biao.out","w",stdout)
#define mp make_pair
#define pb push_back int ans;
int r[],co[];
int k,nr,nc;
int sumr,sumc;
const int maxn=;//点数
const int maxm=;//边数
const int inf=;//MAXINT
struct vnode {
int v,next;
int cap;
};
int cnt,head[maxn];
int h[maxn],g[maxn],d[maxn];//g[i]为标号为i的结点个数,h[i]为i结点的标号,d[]当前弧优化,记录当前弧
bool found;
int n,m,st,ed;//n个点m条边
int augc,flow;//augc为增广路容量,flow为最大流
vnode e[maxm]; inline void add(const int &x,const int &y,const int &z) {
e[cnt].v=y;
e[cnt].cap=z;
e[cnt].next=head[x];
head[x]=cnt;
cnt++; e[cnt].v=x;
e[cnt].cap=;
e[cnt].next=head[y];
head[y]=cnt;
cnt++; } bool walked[maxn];
bool dfs(const int &x,const int &prex) {///深搜判环
int biu=-;
walked[x]=true;
for (int i=head[x]; i!=-; i=e[i].next) {
if(e[i].v==prex)continue;
if (e[i].cap>) {
if(walked[e[i].v]) return true;
if(dfs(e[i].v,x)) return true;
}
if(biu==-) head[x]=e[i].next;///删边,因为这条边为0或者走了这条边却没发现环
else e[biu].next=e[i].next;
biu=i;
}
walked[x]=false;
return false;
} void aug(const int &m) {
int mini,minh=n-;
int augco=augc;
int &i=d[m];///当前弧优化
if (m==ed) { //如果当前结点为汇点
found=true;
flow+=augc; //增加流量
return;
}
for (; i!=-; i=e[i].next) { //寻找容许边
//printf("m=%d,i=%d,e[i].v=%d,e[i].cap=%d,e[i].next=%d\n",m,i,e[i].v,e[i].cap,e[i].next);
//getchar();
if (e[i].cap && h[e[i].v]+==h[m]) { //如果残留容量大于0,如果是容许边
if (e[i].cap < augc) augc=e[i].cap;//如果容许边流量小于当前增广路流量 则更新增广路流量
//d[m]=i; //把i定为当前弧
aug(e[i].v); //递归
if (h[st]>=n) return; //GAP 如果源点距离标号大于n 则停止算法
if (found) break; //如果找到汇点 则退出寻找
augc=augco;//没找到就还原当前的流
}
}
if (!found) { //重标号
for (int i=head[m]; i!=-; i=e[i].next) //找那个标号,这里不能用d[m]开始,不然会蛋疼
if (e[i].cap && h[e[i].v]<minh) {
minh=h[e[i].v];
mini=i;
}
g[h[m]]--; //GAP 距离为
if (!g[h[m]]) h[st]=n; //GAP
h[m]=minh+;
d[m]=mini;
g[h[m]]++; //GAP
} else {
//修改残量
e[i].cap-=augc;
e[i^].cap+=augc;
}
} inline void farm() {
int i,j,x,y,z;
memset(head,-,sizeof(head));
cnt=;
n=nc+nr+;
st=n-;
ed=n;
for(i=; i<=nc; i++)
add(st,i,co[i]);
for(i=; i<=nr; i++)
add(nc+i,ed,r[i]);
for(i=; i<=nc; i++)
for(j=; j<=nr; j++)
add(i,j+nc,k);
memset(h,,sizeof(h));
memset(g,,sizeof(g));
g[]=n;
flow=;
for(i=; i<=n; i++)
d[i]=head[i];//当前弧初始化
while(h[st]<n) {
augc=inf;//初始化增广路容量为正无穷大
found=false;
aug(st);//从源点开始找
}
if(flow!=sumr) {
ans=;
return;
}
memset(walked,false,sizeof(walked));
for(i=; i<=nr; i++) { ///因为建图的特性,有环的话环肯定包含1~nr中的点,也就是表示行的结点
if(dfs(i,-)) {
ans=;
return;
}
}
ans=;
//printf("%d\n",flow);
} inline void read(int &a) {///读入优化
char ch;
bool flag = false;
a = ;
while(!((((ch = getchar()) >= '') && (ch <= '')) || (ch == '-')));
if(ch != '-') {
a *= ;
a += ch - '';
} else {
flag = true;
}
while(((ch = getchar()) >= '') && (ch <= '')) {
a *= ;
a += ch - '';
}
if(flag) {
a = -a;
}
} int main() {
//RE;
//WE;
int i,j,cas=,t;
scanf("%d",&t);
while(t--) {
scanf("%d%d",&nr,&nc);
k=;
sumr=;
sumc=;
for(i=; i<=nr; i++) {
//scanf("%d",&r[i]);
read(r[i]);
sumr+=r[i];
}
for(i=; i<=nc; i++) {
//scanf("%d",&co[i]);
read(co[i]);
sumc+=co[i];
}
ans=;
if(sumr==sumc)farm();
if(ans==) printf("Case #%d: So naive!\n",cas++);
else if(ans!=) {
printf("Case #%d: So young!\n",cas++);
} else {
printf("Case #%d: So simple!\n",cas++);
}
}
//cout<<"end";
return ;
}
2014-10-16 Update
srm菊苣指出这个居然过不了4888,我试了一下,居然真的wa!
然后发现了一个小错误,if(e[i].v==prex)continue,continue之前没有修改biu。加上biu=i就能过了。
之前居然挂了错的代码这么久,见谅!我逗!
hdu4975 A simple Gaussian elimination problem.(正确解法 最大流+删边判环)(Updated 2014-10-16)的更多相关文章
- hdu4975 A simple Gaussian elimination problem.(最大流+判环)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4975 题意:和hdu4888基本一样( http://www.cnblogs.com/a-clown/ ...
- A simple Gaussian elimination problem.(hdu4975)网络流+最大流
A simple Gaussian elimination problem. Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65 ...
- HDOJ 4975 A simple Gaussian elimination problem.
和HDOJ4888是一样的问题,最大流推断多解 1.把ISAP卡的根本出不来结果,仅仅能把全为0或者全为满流的给特判掉...... 2.在残量网络中找大于2的圈要用一种类似tarjian的方法从汇点開 ...
- HDU 4975 A simple Gaussian elimination problem.
A simple Gaussian elimination problem. Time Limit: 1000ms Memory Limit: 65536KB This problem will be ...
- hdu - 4975 - A simple Gaussian elimination problem.(最大流量)
意甲冠军:要在N好M行和列以及列的数字矩阵和,每个元件的尺寸不超过9,询问是否有这样的矩阵,是独一无二的N(1 ≤ N ≤ 500) , M(1 ≤ M ≤ 500). 主题链接:http://acm ...
- A simple Gaussian elimination problem.
hdu4975:http://acm.hdu.edu.cn/showproblem.php?pid=4975 题意:给你一个n*m的矩阵,矩阵中的元素都是0--9,现在给你这个矩阵的每一行和每一列的和 ...
- hdu 4975 A simple Gaussian elimination problem.(网络流,推断矩阵是否存在)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4975 Problem Description Dragon is studying math. One ...
- hdu 4975 A simple Gaussian elimination problem 最大流+找环
原题链接 http://acm.hdu.edu.cn/showproblem.php?pid=4975 这是一道很裸的最大流,将每个点(i,j)看作是从Ri向Cj的一条容量为9的边,从源点除法连接每个 ...
- hdu4975 网络流解方程组(网络流+dfs判环或矩阵DP)
http://acm.hdu.edu.cn/showproblem.php?pid=4975 A simple Gaussian elimination problem. Time Limit: 20 ...
随机推荐
- 数据结构算法C语言实现(八)--- 3.2栈的应用举例:迷宫求解与表达式求值
一.简介 迷宫求解:类似图的DFS.具体的算法思路可以参考书上的50.51页,不过书上只说了粗略的算法,实现起来还是有很多细节需要注意.大多数只是给了个抽象的名字,甚至参数类型,返回值也没说的很清楚, ...
- 加州大学伯克利分校Stat2.2x Probability 概率初步学习笔记: Section 4 The Central Limit Theorem
Stat2.2x Probability(概率)课程由加州大学伯克利分校(University of California, Berkeley)于2014年在edX平台讲授. PDF笔记下载(Acad ...
- POJ 1236 Network of Schools(强连通分量/Tarjan缩点)
传送门 Description A number of schools are connected to a computer network. Agreements have been develo ...
- 高性能JavaScript笔记一(加载和执行、数据访问、DOM编程)
写在前面 好的书,可能你第一遍并不能领会里面的精魂,当再次细细品评的时候,发现领悟的又是一层新的含义 (这段时间,工作上也不会像从前一样做起来毫不费力,开始有了新的挑战,现在的老大让我既佩服又嫉妒,但 ...
- 快捷键&小技巧&备忘录
shift+鼠标滚轮:实现左右移动 alt+鼠标左键双击:打开属性 chrome中在F12下的Element中,可以先选中某一项,可以直接按住F2进行编辑 chrome中element的右下方我们可以 ...
- JavaScript---认识JavaScipt
认识JavaScript 1.什么是JavaScript? JavaScript是属于网络的脚本语言,她被数百万计的网页用来改进设计.验证表单.检测浏览器.创建cookies以及更多的应用,她更是因特 ...
- ng-controller event data
$emit只能向parent controller传递event与data $broadcast只能向child controller传递event与data $on用于接收event与data 例子 ...
- python wmi使用
python wmi 官方开发文档https://msdn.microsoft.com/en-us/library/aa394388(v=vs.85).aspx WMI使用的WIN32_类库名 htt ...
- EmgnCv进行轮廓寻找和计算物体凸包
http://blog.csdn.net/qq_22033759/article/details/48029493 一.轮廓寻找 用的是FindContours函数,在CvInvoke中 不过需要用到 ...
- webservice理解
什么是webservice? 1.基于web的一种服务,webservice分为服务器端server和客户端client. server端会会提供一些资源供客户端的应用来访问(获取所需要的数据) 2. ...