经过4天的折磨,终于写完了这个专题 …
点此进入专题地址
呃 感觉还是有所提升的,一直以为图论难的是建图方式,现在发现有一个优秀的板子也是十分必要的…不然TLE到自闭…
考虑到大多数都需要用到板子,所以下面只给出建图函数,不给出求最大流之类的函数了
——------------------------------------------------------------
A - ACM Computer Factory POJ - 3436
给出了
台机器的信息,对于每一个机器有两个属性和一个价值
属性为:输入与输出,每个属性共由
个小细节决定,
由键盘输入
求获得最大价值需要新建立哪些边
具体细节为
1.
:不需要接口输入 / 不能输出接口
2.
: 必须需要接口输入 / 输出一个接口
3.
: 是否有接口输入无所谓
所以我们可以发现,对于输入细节是0/2的机器 均可以放在第一个作为总输入,对于输出细节全为1的机器,均可以作为总输出
首先求出最大价值,即最大流
所以我们建立一个超级源点和超级汇点,之后对于每一个机器拆点拆为两个点,边为i到i + n,权值为w[i]的有向边,然后把所有输入符合条件的点与源点建边为inf,输出符合条件的点拆点后与汇点建边为inf,之后对于所有输出与另一个输入吻合的点,则把输出拆点后与输入建边为inf,即i + n -> j ,(至于为什么是从拆完点之后建边,就需要细细品了…)
之后就是直接跑一个最大流了
然后需要找出新建立的边
因为我们在前面手动把每一个输出与输入吻合的点建边了,所以我们只需要求出哪些边被用过,然后输出就可以了,大概有两种办法,
1. 结构体不含流量,只含容量的话,那就找出所有终点小于起点,且flow大于0的边就可以了
2. 结构体设置了流量这一属性的话,就直接求出所有流量小于0的边就可以了
建图是
虚拟源点–>电器–>拆点电器–>虚拟汇点
inf–>w[i]–>inf
电器内部为
源点–>输入电器 输出电器–>汇点
之后在电器之中暴力枚举可以互相连接的点再暴力连接起来
输出点–>输入点
inf
vector<ANS> ans;
void solve () {
int n, pp, q;
int in[55][55], out[55][55], w[55];
Dinic res;
res.clear();
//p = read(), n = read();
scanf("%d %d", &pp, &n);
int S = 0, T = 2 * n + 1;
res.S = S, res.T = T;
rep(i,1,n) {
//w[i] = read();
scanf("%d", &w[i]);
bool flag = true;
rep(j, 0, pp - 1) {
//in[i][j] = read();
scanf("%d",&in[i][j]);
if(in[i][j] == 1) {
flag = false;
}
}
if(flag) {
res.add_edge(S, i, inf);
}
flag = true;
rep(j, 0 ,pp - 1) {
//out[i][j] = read();
scanf("%d", &out[i][j]);
if(out[i][j] == 0) {
flag = false;
}
}
if(flag) {
res.add_edge(i + n, T, inf);
}
}
rep(i,1,n) {
res.add_edge(i, i + n, w[i]);
rep(j,1,n) {
if(i == j) {
continue;
}
if(judge(out[i],in[j],pp)) {
res.add_edge(i + n, j, inf);
}
}
}
int sum = res.dinic() , cnt = 0;
rep(i,0,res.num - 1) {
int u = edge[i].u, v = edge[i].v, w = edge[i].c;
if(u == T || u == S || v == T || v == S || u - n == v || v - n == u) {
continue;
}
// 找到反向弧
if((i & 1) && v > u && w > 0) {
++ cnt;
ans.pb(ANS(v - n, u, w));
}
}
printf("%d %d\n",sum, cnt);
rep(i,0,cnt - 1) {
printf("%d %d %d\n",ans[i].u, ans[i].v, ans[i].w);
}
}
B - Dining POJ - 3281
给出
头牛,F个饮料,D个面包(每个面包和饮料均不同)
每头牛有自己所喜爱的饮料与面包,且两者都要吃一个
求最大可以满足多少牛
这个就很简单了,先把每头牛拆点,建立一条
权值为1的边,然后建立一个超级源点与超级汇点,之后把所有的饮料与源点/喜爱他的牛建边,权值为1,同理把所有的面包与汇点/喜爱他的牛的拆点建边,权值为1,然后跑最大流
所以建立出来的图大致为
权值均为1
源点–>饮料–>牛–>拆点牛–>面包–>汇点
void solve () {
scanf("%d %d %d", &n, &f, &d);
N = n * 2 + f + d + 2;
int S = n * 2 + f + d + 1, E = S + 1;
rep(i,1,n) {
int nf = read(), nd = read();
while(nf --) {
int pos = read();
pos += n * 2;
int u = i * 2 - 1;
add(pos, u);
add(u, u + 1);
add(S, pos);
}
while(nd --) {
int pos = read();
pos += n * 2 + f;
int u = i * 2;
add(u, pos);
add(pos, E);
}
}
printf("%d\n",max_flow(S, E));
}
C - A Plug for UNIX POJ - 1087
首先用
把这些字符串都转换为下标
。。。。
大意是先给出
个插座,然后给出
个用电器(每种用电器有它所必须的插座),最后给出
个适配器,每行给出a,b 代表可以把b转换为a
适配器数目无限多,求问最多可以使多少用电器成功用上电
也就是求最大流,首先把各种字符串转换为下标…
然后由于这个是源点直接供给插座,所以不需要拆点了,直接源点链接插座
对于每一种适配器,我们建立一条从后者到前者权值为
的边就可以了
然后用电器连接汇点,求一个最大流就可以了
建图方式为
源点–>插座–>用电器–>汇点
关于每一种适配器我们可以放在插座部分
输出点–>输入点
map<string, int> mp;
void solve() {
Dinic res;
res.clear();
int n, m, k;
res.S = 0, res.T = 1;
int S = 0, T = 1;
int tot = 2;
string s1, s2;
cin >> n; while(n --) {
cin >> s1;
mp[s1] = tot ++;
res.add_edge(S, mp[s1], 1);
}
cin >> m; rep(i,1,m) {
cin >> s1 >> s2;
if(!mp[s1]) {mp[s1] = tot ++;}
if(!mp[s2]) {mp[s2] = tot ++;}
res.add_edge(mp[s1], T, 1);
if(mp[s1] != mp[s2]) {
res.add_edge(mp[s2], mp[s1], 1);
}
}
cin >> k; while(k --) {
cin >> s1 >> s2;
if(!mp[s1]) {mp[s1] = tot ++;}
if(!mp[s2]) {mp[s2] = tot ++;}
if(mp[s1] != mp[s2]) {
res.add_edge(mp[s2], mp[s1], inf);
}
}
cout << m - res.dinic() << endl;
}
D - Going Home POJ - 2195
给出一个地图,
代表房子,
代表人,下雨后每个人都要跑回房子,但是一个房子只能有一个人居住,每个人到房子的距离为两者的欧几里得距离,每个人回家需要花费一定路费,路费与距离成正比,系数为1,也就是说,跑多远花多少钱,求出所有人都跑到房子内所花费的路费最低为多少
··
这就是一个最小费用最大流板子题,模板自己找吧
建图大概为
源点–>每个人 权值为1,费用为0
每个人–>房子 权值为1,费用为欧几里得距离
房子–>汇点 权值为1, 费用为0
然后就是直接跑板子啦
char str[1005][1005];
vector<pii> house, kid;
void solve () {
while(~scanf("%d %d", &n, &m) && n && m) {
S = 0, T = 1;
ms(head, -1);
num = -1;
house.clear();
kid.clear();
rep(i,1,n) {
scanf("%s", str[i] + 1);
}
rep(i,1,n) {
rep(j,1,m) {
if(str[i][j] == 'H') {
house.pb(MP(i,j));
} else if(str[i][j] == 'm') {
kid.pb(MP(i,j));
}
}
}
int szhouse = house.size(), szkid = kid.size();
rep(i,0,szkid - 1) {
add_edge(0, i + 2, 1, 0);
add_edge(i + 2, 0, 0, 0);
rep(j,0,szhouse - 1) {
add_edge(i + 2, j + 2 + szkid, 1, abs(kid[i].fi - house[j].fi) + abs(kid[i].se - house[j].se));
add_edge(j + 2 + szkid, i + 2, 0, -abs(kid[i].fi - house[j].fi) + -abs(kid[i].se - house[j].se));
}
}
rep(i,0,szhouse-1) {
add_edge(1, i + 2 + szkid, 0, 0);
add_edge(i + 2 + szkid, 1, 1, 0);
}
max_flow__min_cost();
printf("%d\n",min_cost);
}
}
E - Minimum Cost POJ - 2516
个店主,
个商铺,
种货物
对于每一种货物,给出了每个店所需要的,每个商铺所拥有的,以及从n到m货物为k的单位费用
求出是否可以供应,可以的话输出最低费用
首先我们直接判断所有的货物数目与所有的需求数目,看每种货物是否都满足需求,不满足直接输出-1,满足的话求一个最小费用最大流
建图:
货物一种一种来求,算k次
对于每一种货物,把源点与商铺之间建立一条边,权值为商铺的拥有量,费用为0,然后建立每个商铺到每个店铺的边,权值为店铺所需要的,费用为对应路费,然后建立从店铺到汇点的边,权值为需要的,费用为0
然后就直接跑MCMF啦
这题用快读一直被卡超时,换成scanf就过了,,,可能数据很(du)棒(liu)
含有一堆格外的空格与空行吧…
void solve () {
while(~scanf("%d %d %d", &n, &m, &k) && (n || m || k)) {
res.init(n + m + 1);
ms(need,0);
ms(have,0);
ms(money,0);
ms(goods,0);
bool flag = true;
for(int i = 1; i <= n; ++ i) {
for(int j = 1; j <= k; ++ j) {
scanf("%d", &need[i][j]);
goods[j] += need[i][j];
}
}
for(int i = 1; i <= m; ++ i) {
for(int j = 1; j <= k; ++ j) {
scanf("%d", &have[i][j]);
goods[j] -= have[i][j];
}
}
for(int l = 1; l <= k; ++ l) {
for(int i = 1; i <= n; ++ i) {
for(int j = 1; j <= m; ++ j) {
scanf("%d", &money[i][j][l]);
}
}
}
for(int i = 1; i <= k; ++ i) {
if(goods[i] > 0) {
flag = false;
break;
}
}
if(!flag) {
puts("-1");
continue;
}
int sum = 0;
for(int l = 1; l <= k; ++ l) {
res.init(n + m + 1);
for(int i = 1; i <= m; ++ i) {
res.add_edge(0, i, have[i][l], 0);
res.add_edge(i, 0, 0, 0);
}
for(int i = 1; i <= n; ++ i) {
for(int j = 1; j <= m; ++ j) {
res.add_edge(j, i + m, have[j][l], money[i][j][l]);
res.add_edge(i + m, j, 0, -1 * money[i][j][l]);
}
res.add_edge(i + m, m + n + 1, need[i][l], 0);
res.add_edge(m + n + 1, i + m, 0, 0);
}
sum += res.MinCostMaxFlow(0, n + m + 1);
}
printf("%d\n", sum);
}
}
F - Power Network POJ - 1459
这题题意太绕了…就是给出m个边,然后a个源点,b个汇点
之后建立一个超级源点,超级汇点,都连接起来就好了,读入太难了,我用的cin。。。
void solve () {
int n, m, ns, nt;
char ch;
while(~scanf("%d %d %d %d", &n, &ns, &nt, &m)) {
res.init(n + 2);
int S = n + 1, T = n + 2;
while(m --) {
int u, v, w;
char ch;
cin >> ch >> u >> ch >> v >> ch >> w;
res.add_edge(u, v, w);
}
while(ns --) {
int v, w;
//scanf("(%d)%d", &v, &w);
cin >> ch >> v >> ch >> w;
res.add_edge(S, v, w);
}
while(nt --) {
int u, w;
//scanf("(%d)%d", &u, &w);
cin >> ch >> u >> ch >> w;
res.add_edge(u, T, w);
}
printf("%d\n", res.max_flow(S, T));
}
}
G - Island Transport HDU - 4280
给出n个岛屿的坐标,以及m条边代表点和点之间的权值,源点为x最小的点,汇点为x最大的点,求一个最大流就可以了。。
问题是这题太毒瘤了,把Dinic卡了…必须要用ISAP算法。。。。吐了
我还不会ISAP,我用的别人写法9999ms过的。。。
迷惑的是我找来一个ISAP板子也超时了,,太迷惑了
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
int Max=0,Min=inf,s,t;
init();
for(int i=0; i<n; i++)
{
int x,y;
scanf("%d%d",&x,&y);
if(Max<x) Max=x,t=i+1;
if(Min>x) Min=x,s=i+1;
}
for(int i=0; i<m; i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
add(v,u,w);
}
printf("%d\n",Dinic(s,t));
}
}
H - Food HDU - 4292
种食物,
种饮料,每人有自己喜欢的食物与饮料,求问最多满足多少人
就是个简单最大流啊,食物与饮料之间用人塞进去,然后把人拆点,就可以了啊,问题是我一直谜之mle,最后把结构体封装删了就过了,,,
建图
源点–>食物–>人–>拆点人–>饮料–>汇点
void solve () {
//int N, F, D;
while(~scanf("%d %d %d", &n, &F, &D)) {
clear();
//读入饮料与食物的个数
rep(i,1,F) {
int tmp; scanf("%d", &tmp);
add_edge(0, i, tmp);
}
rep(i,1,D) {
int tmp; scanf("%d", &tmp);
add_edge(i + F + 2 * n, T, tmp);
}
rep(i,1,n) {
add_edge(F + i, F + n + i, 1);
scanf("%s", str1[i] + 1);
}
rep(i,1,n) {
scanf("%s", str2[i] + 1);
}
rep(i,1,n) {
rep(j,1,F) {
if(str1[i][j] != 'Y') {
continue;
}
add_edge(j, i + F, 1);
}
rep(j,1,D) {
if(str2[i][j] != 'Y') {
continue;
}
add_edge(i + F + n, j + F + n * 2, 1);
}
}
printf("%d\n", dinic());
}
}
I - Control HDU - 4289
给出点数
与边数
,以及源点汇点,和边权
求问最小割(就是这个意思,堵住一些边使得整体为0)
也就是最大流问题,关于最小割就是最大流可以自己去搜一搜证明
int arr[maxn];
void solve () {
int n, m, s, t;
while(~scanf("%lld %lld", &n, &m)) {
scanf("%lld %lld", &s, &t);
res.init(n * 2);
rep(i,1,n) {
scanf("%lld", &arr[i]);
//if(i != t)
res.add_edge(i * 2 - 1, i * 2, arr[i]);
//if(i != s)
res.add_edge(i * 2, i * 2 - 1, arr[i]);
//cout << i * 2 - 1 << " " << i * 2 << " " << arr[i] << endl;
}
while(m --) {
int u, v;
scanf("%lld %lld", &u, &v);
res.add_edge(u * 2, v * 2 - 1, inf);
res.add_edge(v * 2, u * 2 - 1, inf);
}
printf("%lld\n",res.max_flow(s * 2 - 1, t * 2));
}
}
J - Sabotage UVA - 10480
这个和上面一样,也是需要求最小割
区别在于这个需要你输出被阻断的边,这个其实简单,在跑完最大流之后
从源点开始进行一次搜索,把它能到达的所有点进行染色,最后遍历所有的边,如果这个边残量为0,且两点颜色不同,则为被阻断的边,输出就可以了
void search(int now) {
pos[now] = true;
int sz = G[now].size();
for(int i = 0; i < sz; ++ i) {
Edge& e = edge[G[now][i]];
if(!pos[e.to] && e.cap > e.flow) {
pos[e.to] = true;
search(e.to);
}
}
}
void print() {
m = edge.size();
for(int i = 0; i < m; ++ i) {
int u = edge[i].fr, v = edge[i].to;
if(pos[u] != pos[v] && u > v) {
printf("%d %d\n", u, v);
}
}
puts("");
}
void solve () {
int n, m, s, t;
while(~scanf("%d %d", &n, &m) && n && m) {
res.init(n);
while(m --) {
int u , v, w;
scanf("%d %d %d", &u, &v, &w);
res.add_edge(u, v, w);
}
res.max_flow(1, 2);
res.search(1);
res.print();
}
}
K - Leapin’ Lizards HDU - 2732
有
个人要逃生,
个柱子,每个柱子只能被踩一定次数,求问有多少人跑不出去,每次跑的最大距离为d
首先把所有一步能跑出去的点拆点后与汇点相连,点与拆点之间是它的被踩次数
然后把所有的人所在的地方与源点相连为1,之后暴力合并点,把距离小于D的点都加上边,注意是起点拆点后加给终点
最后求最大流,用总数减去他就好了
char str[50], mp[25][25];
int cas = 0;
void solve () {
int n, m;
double d;
scanf("%d %lf", &n, &d);
res.init(n * 20 * 2 + 2);
int S = n * 20 * 2 + 1, T = n * 20 * 2 + 2;
rep(i,1,n) {
scanf("%s", str + 1);
int len = strlen(str + 1);
m = len;
for(int j = 1; j <= len; ++ j) {
if(str[j] != '0') {
res.add_edge((i - 1) * len + j, (i - 1) * len + j + n * len, str[j] - '0');
if(i - d < 1 || i + d > n || j - d < 1 || j + d > len) {
res.add_edge( (i - 1) * len + j + n * len, T, inf);
}
}
}
}
int tot = 0;
rep(i,1,n) {
scanf("%s", str + 1);
int len = strlen(str + 1);
for(int j = 1; j <= len; ++ j) {
if(str[j] == 'L') {
++ tot;
res.add_edge(S, (i - 1) * len + j, 1);
}
}
}
//int m = strlen(str + 1);
rep(i,1,n) {
rep(j,1,m) {
rep(x,1,n) {
rep(y,1,m) {
double dis = sqrt((i - x) * (i - x) * 1.0 + (j - y) * (j - y));
if(dis > d) continue;
res.add_edge(n * m + (i - 1) * m + j, (x - 1) * m + y, inf);
}
}
}
}
int tmp = res.max_flow(S, T);
tmp = tot - tmp;
printf("Case #%d: ", ++ cas);
if(tmp == 0) {
printf("no ");
} else {
printf("%d ",tmp);
}
if(tmp > 1) {
printf("lizards were left behind.\n");
} else {
printf("lizard was left behind.\n");
}
}
L咕咕咕 神仙题,读不懂
M - Escape HDU - 3605
给出
个人,
个星球, 之后
行,每行
个字,为1则代表i可以在j星球生存,最后一行给出每个星球最多可以住多少人,求问是否可以使得所有人都有地方住
我是用二分图多重匹配做的,m大小只有10嗷嗷嗷
直接匈牙利多重匹配啦
void solve () {
while(~scanf("%d %d", &n, &m)) {
init();
for(int i = 1; i <= n; ++ i) {
for(int j = 1; j <= m; ++ j) {
int tmp; scanf("%d", &tmp);
if(tmp) {
G[i].push_back(j);
}
}
}
for(int i = 1; i <= m; ++ i) {
scanf("%d", &sum[i]);
if(sum[i] == 0) {
sum[i] = -1;
}
}
puts(match() ? "YES" : "NO");
}
}
我用HK一直超时。。。匈牙利却过了
看来对于多重匹配来说,这个复杂度还是不太确定的嗷
N - Marriage Match II HDU - 3081
给出
个妹子和
个汉子,每个妹子有自己心仪的对象,每个妹子也有许多朋友,朋友关系可以传递(并查集),然后自己朋友心仪的对象自己也可以上(???呕吼??)
每一轮每个妹子匹配一个可以匹配的汉子,然后下一轮替换,不能匹配自己前面已经匹配到的(渣女!),求问可以玩多少轮(每一轮必须对于妹子要是完备匹配)
我本来想用最短路计数来做的,后来一直不过样例才发现用过的不能再用了,,,所以还是老老实实建图权值为1跑最大流吧
采用二分逼近答案,每次一check的mid值,我们从源点到每个妹子建立一个mid流量,妹子到汉子建立流量为1,汉子到汇点建立mid流量,最后judge的是最大流是否大于等于mid就可以啦
int dad[maxn];
bool line[maxn][maxn];
int seek(int x) {return x == dad[x] ? x : dad[x] = seek(dad[x]);}
bool judge(int n, int mid) {
//Dinic res;
res.init(n * 2 + 1);
int S = 0, T = 2 * n + 1;
for(int i = 1; i <= n; ++ i) {
res.add_edge(S, i, mid);
for(int j = n + 1; j <= n * 2; ++ j) {
//res.add_edge(j, T, mid);
if(line[i][j]) {
res.add_edge(i, j, 1);
}
}
}
for(int j = n + 1; j <= n * 2; ++ j) {
res.add_edge(j, T, mid);
}
return res.max_flow(S, T) >= mid * n;
}
void solve () {
ms(line, false);
int n, m, f;
scanf("%d %d %d", &n, &m, &f);
for(int i = 0; i <= n * 2 + 1; ++ i) {
dad[i] = i;
}
while(m --) {
int u, v;
scanf("%d %d", &u, &v);
line[u][v + n] = true;
}
while(f --) {
int u, v;
scanf("%d %d", &u, &v);
dad[seek(u)] = seek(v);
}
for(int i = 1; i <= n; ++ i) {
for(int j = i + 1; j <= n; ++ j) {
if(seek(i) == seek(j)) {
for(int k = n + 1; k <= 2 * n; ++ k) {
line[i][k] = line[j][k] = (line[i][k] || line[j][k]);
}
}
}
}
int res = 0, L = 0, R = 100;
//n = n * 2 + 1;
while(L <= R) {
int mid = L + R >> 1;
if(judge(n, mid)) {
res = mid;
L = mid + 1;
} else {
R = mid - 1;
}
}
printf("%d\n", res);
}
O - Marriage Match IV HDU - 3416
这个就是最短路计数问题了,不过由于路径不能重复走,所以也只能用最大流来做了,先跑最短路,然后对于每条边,如果dis[u]+w=dis[v],那就加入边进去,权值为1,最后跑最大流,
void solve () {
res1.read_data();
res1.dijstra(res1.S);
res2.init(res1.n);
for(int i = 1; i <= res1.n; ++ i) {
int sz = res1.G[i].size();
for(int j = 0; j < sz; ++ j) {
decltype((res1.G[i][j])) e = res1.G[i][j];
if(res1.dis[i] + e.w == res1.dis[e.v]) {
res2.add_edge(i, e.v, 1);
}
}
}
printf("%d\n", res2.max_flow(res1.S, res1.T));
}