一、POJ 2391 【拆点+二分+最大流】
【题目大意】
给定一个无向图,点 i 处有 Ai 头牛,点 i 处的牛棚能容纳 Bi 头牛,求一个最短时
间 T 使得在 T 时间内所有的牛都能进到某一牛棚里去。 (1 <= N <= 200, 1 <= M <=
1500, 0 <= Ai <= 1000, 0 <= Bi <= 1000, 1 <= Dij <= 1,000,000,000)
【建模方法】
将每个点 i 拆成两个点 i’, i’’,连边(s, i’, Ai), (i’’, t, Bi)。二分最短时间 T,若 d[i][j]<=T
(d[i][j]表示点 i, j 之间的最短距离)则加边(i’, j’’, ∞)。每次根据最大流调整二分
的上下界即可。
一种错误的建图方法,即不拆点,见下图:
其中每条无向边表示两条方向相反的有向边,容量均为∞。
当二分到 T = 70 的时候,显然我们只加入了(2, 3)和(3, 4)两条无向边,因为只有
这两对点间的最短距离小于等于 70。但是从图中也可以看出,由于没有拆点,
点 2 也可以通过这两条边到达点 4,而实际上这是不允许的。也就是说我们所加
的限制条件没有起到作用。由此可见,只有拆点才是正确的做法。
本题注意longlong
//#include<bits/stdc++.h>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <cmath>
#include <map>
#include <queue>
#include <stack>
#include <set>
#include <algorithm>
using namespace std;
#define For(i,a,b) for(int (i)=(a);(i) < (b);(i)++)
#define rof(i,a,b) for(int (i)=(a);(i) > (b);(i)--)
#define IOS ios::sync_with_stdio(false)
#define lson l,m,rt <<1
#define rson m+1,r,rt<<1|1
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long ll;
typedef unsigned long long ull;
void RI (int& x){
x = 0;
char c = getchar ();
while (c == ' '||c == '\n') c = getchar ();
bool flag = 1;
if (c == '-'){
flag = 0;
c = getchar ();
}
while (c >= '0' && c <= '9'){
x = x * 10 + c - '0';
c = getchar ();
}
if (!flag) x = -x;
}
void RII (int& x, int& y){RI (x), RI (y);}
void RIII (int& x, int& y, int& z){RI (x), RI (y), RI (z);}
/**************************************END define***************************************/
const int maxn = 510;
const int maxm = 1e5;
const int INF =0x3f3f3f3f;
int start,end,cnt;
int dis[maxn],gap[maxn];
ll floy[210][210];
int n,m;
int top,node[maxn];
int A[maxn],B[maxn];
ll total;
struct Side
{
int u,to,next;
ll c;
}side[maxm];
void add_side(int u,int v,ll c)
{
side[top]=(Side){u,v,node[u],c}; node[u]=top++;
side[top]=(Side){v,u,node[v],0}; node[v]=top++;
}
void Floyd()
{
For(k,1,n+1)For(i,1,n+1)For(j,1,n+1){
if(floy[i][j]>floy[i][k]+floy[k][j])
floy[i][j]=floy[i][k]+floy[k][j];
}
}
ll get_flow(int u,ll flow)
{
if(u==end) return flow;
ll ans=0;
for(int i=node[u];i!=-1;i=side[i].next){
int v=side[i].to;
ll c=side[i].c;
if(dis[u]>dis[v]&&c){
ll f=get_flow(v,min(flow-ans,c));
ans+=f;
side[i].c-=f;
side[i^1].c+=f;
if(ans==flow) return ans;
}
}
if(!--gap[dis[u]]) dis[start]=cnt+2;
gap[++dis[u]]++;
return ans;
}
ll solve(ll k)
{
start=0,end=n+n+1;
cnt=end+1;
top=0;
mem(node,-1);
For(i,1,n+1)
add_side(start,i,A[i]),
add_side(i+n,end,B[i]),
add_side(i,n+i,1e15);
For(i,1,n+1){
for(int j=i+1;j<=n;j++){
if(floy[i][j]<=k)
add_side(i,j+n,1e15),add_side(j,i+n,1e15);
}
}
ll ans=0;
mem(gap,0);mem(dis,0);
gap[0]=cnt;
while(dis[start]<cnt) ans+=get_flow(start,1e15);
return ans;
}
int main()
{
//freopen("input.txt","r",stdin);
while(~scanf("%d%d",&n,&m)){
total=0;
For(i,1,n+1) {RII(A[i],B[i]);total+=A[i];}
For(i,0,210)
For(j,0,210)
if(i==j)floy[i][i]=0;
else floy[i][j]=1e15;
For(i,0,m){
int u,v,w;
RIII(u,v,w);
if(floy[u][v]>w) floy[u][v]=floy[v][u]=w;
}
Floyd();
ll x=0,y=0;
For(i,1,n+1)For(j,1,n+1)
if(floy[i][j]!=1e15) y=max(y,floy[i][j]);
if(solve(y+1)<total) {
puts("-1");
continue;
}
while(x<y){
ll mid=(y+x)/2;
if(solve(mid)>=total) y=mid;
else x=mid+1;
}
printf("%lld\n",x );
}
return 0;
}
二、POJ 3057 【二分图匹配+二分】
队友题解 http://blog.csdn.net/houxinssdut/article/details/48051703
一侧是点编号,一侧是门,
且把一个门拆成1~左侧点数目个,枚举时间T,若u点到某门距离dis 小于等于T ,建边<u,d+dis>....<u,d+T>
最大匹配数即可通过的人数
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <cmath>
#include <map>
#include <queue>
#include <stack>
#include <set>
#include <algorithm>
using namespace std;
#define For(i,a,b) for(int (i)=(a);(i) < (b);(i)++)
#define rof(i,a,b) for(int (i)=(a);(i) > (b);(i)--)
#define IOS ios::sync_with_stdio(false)
#define lson l,m,rt <<1
#define rson m+1,r,rt<<1|1
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 3e4;
const int maxm = 4e6;
const int INF =0x3f3f3f3f;
int gnum[15][15];
int sdis[110][50];//i->j zuiduanlu
int px[4]={1,0,-1,0};
int py[4]={0,1,0,-1};//down right up left
char g[15][15];
int n,m;
int A,B;
int posd[maxn];
struct Side{
int from,to,next;
};
struct BPM{
int n,m; //左右n,m个点
Side side[maxm];
int top;
int node[maxn];
int left[maxn]; // left[i]为右边第i个点的匹配点编号,-1表示不存在
bool T[maxn]; // T[i]为右边第i个点是否已标记
void init(int nn,int mm)
{
n=nn,m=mm;
top=0;
mem(node,-1);
}
void add_side(int u,int v)
{
side[top]=(Side){u,v,node[u]}; node[u]=top++;
}
bool dfs(int u){
for(int i=node[u];i!=-1;i=side[i].next){
int v=side[i].to;
if(!T[v]){
T[v]=true;
if(left[v]==-1||dfs(left[v])){
left[v]=u;
return true;
}
}
}
return false;
}
int hungarian()
{
int ans=0;
mem(left,-1);
for(int u=0;u<n;u++){
mem(T,0);
if(dfs(u)) ans++;
}
return ans;
}
};
BPM solver;
int dis[maxn],vis[15][15];
int diss[15][15];
struct Point{
int x,y;
};
vector <Point> vd, vp;
queue <Point> q;
void bfs(int x,int y,int did)
{
mem(vis,0);
mem(diss,0);
while(!q.empty())q.pop();
Point p;
p.x=x,p.y=y;
q.push(p);
while(!q.empty()){
p=q.front();
q.pop();
for(int i=0;i<4;i++){
int nx=p.x+px[i],ny=p.y+py[i];
if(nx>0&&nx<n-1&&ny>0&&ny<m-1)
if(g[nx][ny]=='.'&&!vis[nx][ny]){
vis[nx][ny]=1;
int d=diss[nx][ny]=diss[p.x][p.y]+1;
q.push((Point){nx,ny});
sdis[gnum[nx][ny]][did]=d;
}
}
}
}
int solve(int k)
{
solver.init(vp.size(),vd.size()*k);
for(int i=1;i<=vp.size();i++){
for(int j=0;j<vd.size();j++){
if(sdis[i][j]<=k){
for(int w=sdis[i][j];w<=k;w++){
solver.add_side(i-1,j*k+w-1);
//cout<<i-1<<" "<<j*k+w-1<<"--\n";
}
}
}
}
/* cout<<k<<endl;
for(int i=0;i<solver.top;i++){
printf("%d %d \n",solver.side[i].from,solver.side[i].to );
}*/
return solver.hungarian();
}
int main(){
//freopen("test.txt","r",stdin);
int T;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++){
scanf("%s",g[i]);
}
mem(sdis,0x3f);
mem(gnum,-1);
vd.clear(), vp.clear();// 存 ‘D’和‘.’坐标
For(i,0,n)
For(j,0,m){
if(g[i][j]=='D')
vd.push_back((Point){i,j});
else if(g[i][j]=='.'){
vp.push_back((Point){i,j});
gnum[i][j]=vp.size();//‘.’的编号
}
}
A=vp.size();// A是'.'数目
for(int i=0; i<vd.size();i++){
bfs(vd[i].x,vd[i].y,i);
}
if(vp.size()==0){
printf("0\n");
continue;
}
int impo=1;
for(int i=1;i<=A;i++){
impo=1;
for(int j=0;j<vd.size();j++){
if(sdis[i][j]!=INF) {
impo=0;
break;
}
}
if(impo) break;
}
if(impo){
printf("impossible\n");
continue;
}
int x=0,y=n*m;
while(x<y){
int mid=(y+x)/2;
if(solve(mid)>=A) y=mid;
else x=mid+1;
}
printf("%d\n",x );
}
return 0;
}
三、POJ2699
《POJ 2699 The Maximum Number of Strong Kings 》
【题目大意】
一场联赛可以表示成一个完全图,点表示参赛选手,任意两点 u, v 之间有且仅有
一条有向边(u, v)或(v, u),表示 u 打败 v 或 v 打败 u。一个选手的得分等于被他打
败的选手总数。一个选手被称为“strong king”当且仅当他打败了所有比他分高
的选手。分数最高的选手也是 strong king。现在给出某场联赛所有选手的得分序
列,由低到高,问合理安排每场比赛的结果后最多能有几个 strong king。已知选
手总数不超过 10 个。
【思路】10个人,至多有10*9/2=45场比赛,可以把每场比赛设为一个点,设为match[u][v],
想到枚举strong king的个数,判断满足满流条件的最大值。当strong king个数为C时,如果存在满流的情况,那么一定存在
得分最高的C个是strong king的情况(贪心很好想,每个人只有赢的数目有差异,其他都是相同的,赢的场次越多,越有可能得分最高)。当对于分数最低的选手,比他的分数不足以达到C,那么C不满足条件,枚举退出。
于是乎可以建图。流量为 比赛场次=得分数
1、源点到每场比赛有一条容量为1的边,每场比赛最多贡献一个积分
2、对于后C个选手,每个人都要赢所有比他分数高的人,对应的比赛向他连一条边,容量为1,这是必须要走的边。
其他的情况,每场比赛输赢没有强制要求,分别向两个参加比赛的选手建立容量为1 的边,这是可选择的边。
(PS:因为存在必须要走的边,出于严谨性要把该选手拆成两个点:i,i',必须要走的边连在i'上,不必须走的边连在i上,i到i'有一条容量为【【选手赢的场次】-【必须赢的场次】】的边。 但是因为这个模型考虑的是是否满流,每场比赛的入流量为1,每场比赛必须走才能保证满流,而对于输赢定下来的比赛只有一条向外流的边,所以可以不进行拆点限制。【本题特殊,有强制性的流量要求时,其他题要注意拆点】)
3、每个选手向汇点连边,容量为得分。
此处应贴代码:代码木有
四、《POJ 3281 Dining 》
【题目大意】
有 F 种食物和 D 种饮料, 每种食物或饮料只能供一头牛享用, 且每头牛只享用一
种食物和一种饮料。现在有 N 头牛,每头牛都有自己喜欢的食物种类列表和饮
料种类列表,问最多能使几头牛同时享用到自己喜欢的食物和饮料 。(1 <= F <=
100, 1 <= D <= 100, 1 <= N <= 100)
最大流的正确性依赖于它的每一条 s-t 流都与一种实际方案一一对
应。那么此题也需要用 s-t 流将一头牛和它喜欢的食物和饮料“串”起来,而食
物和饮料之间没有直接的关系,自然就想到把牛放在中间,两边是食物和饮料,
由 s, t 将它们串起来构成一种分配方案。至此建模的方法也就很明显了:每种食
物 i 作为一个点并连边(s, i, 1),每种饮料 j 作为一个点并连边(j, t, 1),将每头牛 k
拆成两个点 k’, k’’并连边(k’, k’’, 1), (i, k’, 1), (k’’, j, 1),其中 i, j 均是牛 k 喜欢的食物
或饮料。求一次最大流即为结果。
五、JOJ 2453 CANDY
【题目大意】
有 N 颗糖果和 M 个小孩,老师现在要把这 N 颗糖分给这 M 个小孩。每个小孩 i
对每颗糖 j 都有一个偏爱度 Aij,如果他喜欢这颗糖,Aij = 2,否则 Aij = 1。小孩 i
觉得高兴当且仅当∑Cij×Aij >= Bi,j=1,2,…,N,若他分得了糖 j,Cij = 1,否则 Cij =
0。 问能否合理分配这 N 颗糖, 使得每个小孩都觉得高兴。 (1 <= N <= 100,000, 1 <=
M <= 10, 0 <= Bi <= 1,000,000,000)
【方法】一种最直观的想法就是每颗糖 i 作为一个点并连边(s, i, ?),每个小孩 j 作为一个
点并连边(j, t, Bj)。若小孩 j 喜欢糖果 i 则连边(i, j, 2),否则连边(i, j, 1),然后求一
次最大流看是否等于∑Bj。但是问题也很明显,我们还没有给与源点关联的边确
定容量。实际上我们无法确定它们的容量,因为最大流无法实现这样一种控制:
一个点有若干条出边,容量不尽相同,现在要求经过该点的流可以任选一条出边
流走,且一旦选定之后就只能从这条边流而不能再进入其他的出边。因此我们无
法确定与源关联的边的容量,因为经过每颗糖 i 的流无法在出边容量有 1 又有 2
的情况下作出正确的选择。
那么是否就没有办法了呢?虽然流无法在容量有1又有2的情况下作出正确的选
择, 但却可以在容量有 1 又有 0 的情况下最自然地作出正确的选择, 流过去就表
示选择了那条出边,且因为容量为 1,不会再有流进入其他的出边。那么此题的
构图方法也就出来了:每颗糖 i 作为一个点并连边(s, i, 1),每个小孩 j 作为一个
点并连边(j, t, floor(Bj/2)),若小孩 j 喜欢糖果 i 则连边(i, j, 1),否则连边(i, j, 0)或者
干脆不连边,效果一样。设最大流为 ans,若 ans+N >= ∑Bj 则可以满足要求。
为什么?因为每颗糖迟早都要分给某个小孩,它一定会为总权值贡献 1,只不过
如果它分给了喜欢它的小孩就再额外贡献 1。现在我只考虑这额外的 1 单位贡献
究竟能送出去多少,最后加上基值 N 并与∑Bj 比较即可。
六、最大权闭合子图
http://www.cnblogs.com/kane0526/archive/2013/04/05/3001557.html