BZOJ5228: [Lydsy2017省队十连测]Bounce
? 解题记录 ? ? BZOJ ? ? 费用流 ? ? 网络流 ?    2018-10-01 10:03:10    499    0    0

很套路的一道题,做过无限之环来看这道题还是十分好想的

因为我比较菜,考试的时候就没能想出来。

按照套路,把格子黑白染色,然后考虑图的特点,到一个地方一定拐弯。

并且,对于一条回路来说,正向和反向的贡献都是一样的。

接下来,我们指定黑、白格子的流向,考虑一个白格子的流出一定是某个黑格子的流入。那么就好办了,我们规定黑格子横进竖出,白格子横出竖进。这样我们把问题抽象成一张图,对于每一个格子可以选择从进向自己连边,自己向出连边,相当于每种进的方案都可以选择两种出去的方案。但是题目还有一个隐含条件,一个点只能经过一条流量,也就是出入度都是1,想到限制出入度便想到了最小路劲覆盖的模型。

于是,初步的构图便完成了。

1、套用最小路径覆盖模型把每一个格子拆两个点u1, u2。S->u1->u2->T费用都是0,流量都是1,考虑到一个格子不经过的情况,相当于一个自环,所以u1->u2有连边。

2、格子X的连边分为:1、黑色,左右两边是A, B,A1->X2, B1->X2,上下两边是C, D,X1->C2,X1->D2。2、白色,把上面的连边反过来X1->A2, X1->B2, C1->X2, D1->X2

最后,对于必须走的格子,也就是不能有自环,我们去掉中间u1->u2的边即可。

 

辣鸡题目卡我常数……

把TLE的点删了下面的程序就能过了

#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstring>
#include<iostream>
#define LL long long
using namespace std;
const int maxm = 5e3 + 5;
const int maxn = 2e3 + 5;
const int N = 35;
const int inf = 0x3f3f3f3f;
 
inline char gc(){
    static char buf[5000000], *p1 = buf, *p2 = buf;
    return (p1 == p2) && (p2 = (p1 = buf) + fread(buf, 1, 5000000, stdin), p1 == p2) ? EOF : *p1++;
}
int f = 1;
inline void read(int & x) {
    x = 0, f = 1; static char c = gc();
    while(!isdigit(c)) {
    	if(c == '-') f = -1;
    	c = gc();
    }
    while(isdigit(c)) x = x * 10 + c - '0', c = gc();
    x *= f;
}

struct edge {
    int v, w, cost, next;
} e[maxm << 2];
int head[maxn], cnt;
void adde(const int &u, const int &v, const int &w, const int &cost) {
    //cerr << u << "---" << w << "/" << cost << "-->" << v << endl;
    e[cnt] = (edge) {v, w, cost, head[u]};
    head[u] = cnt++;
    e[cnt] = (edge) {u, 0, -cost, head[v]};
    head[v] = cnt++;
}
 
int q[maxn * maxn], frt, bk;
int vis[maxn], fa[maxn], fw[maxn], id[maxn], dis[maxn];
LL dw;
const int Lf = 0, Rt = 1;
int C[N][N], R[N][N], ban[N][N];
int n, m, K, u, v, ND[N][N][2], dcnt, S, T;
 
 
int SPFA(int s, int t) {
	frt = bk = 1;
    q[bk++] = s, vis[s] = 1;
    for(register int i = 1; i <= dcnt; ++i)
    	dis[i] = -inf;
    dis[s] = 0;
    while(frt < bk) {
        int u = q[frt++];
        vis[u] = 0;
        for(register int i = head[u]; ~i; i = e[i].next) {
            int v = e[i].v;
            if(!e[i].w) continue;
            if(dis[v] < dis[u] + e[i].cost) {
                dis[v] = dis[u] + e[i].cost;
                fa[v] = u, fw[v] = e[i].w, id[v] = i;
                if(vis[v]) continue;
                q[bk++] = v;
                vis[v] = 1;
            }
        }
    }
    return dis[t];
}
 
int Flow(int u, int t, int mn) {
    if(u == t) return mn;
    mn = Flow(fa[u], t, min(mn, fw[u]));
    e[id[u]].w -= mn, e[id[u] ^ 1].w += mn;
    return mn;
}
 
int MaxFlow(int s, int t) {
    int ans = 0, f = 0, tmp;
    while(SPFA(s, t) != -inf) {
        tmp = Flow(t, s, inf), f += tmp;
        ans += 1ll * dis[t] * tmp;
    }
    return f == dw ? ans : -1;
}

int spc[N * N][2];

int main() {
    //freopen("bounce.in", "r", stdin);
    //freopen("bounce.out", "w", stdout);
    int t;
    read(t);
    while(t--) {
	    memset(head, -1, sizeof(head));
	    memset(ban, 0, sizeof(ban));
	    cnt = 0, dcnt = 0;
	    read(n), read(m), dw = n * m;
	    for(register int i = 1; i <= n; ++i)
	    	for(register int j = 1; j < m; ++j)
	    		read(C[i][j]);
	    for(register int i = 1; i < n; ++i)
	    	for(register int j = 1; j <= m; ++j)
	    		read(R[i][j]);
		read(K);
		for(register int i = 1; i <= K; ++i) {
			read(u), read(v);
			ban[u][v] = 1, spc[i][0] = u, spc[i][1] = v;
		} 
		S = ++dcnt, T = ++dcnt;
	    for(register int i = 1; i <= n; ++i)
	    	for(register int j = 1; j <= m; ++j) {
	    		ND[i][j][Lf] = ++dcnt;
	    		ND[i][j][Rt] = ++dcnt;
	    		adde(S, ND[i][j][Lf], 1, 0);
	    		if(!ban[i][j]) adde(ND[i][j][Lf], ND[i][j][Rt], 1, 0);
	    		adde(ND[i][j][Rt], T, 1, 0);
	    	}
	    for(register int i = 1; i <= n; ++i)
	    	for(register int j = 1; j <= m; ++j) {
	    		if((i + j) & 1) { //横进竖出 
	    			if(j < m) adde(ND[i][j + 1][Lf], ND[i][j][Rt], 1, C[i][j]);
	    			if(j > 1) adde(ND[i][j - 1][Lf], ND[i][j][Rt], 1, C[i][j - 1]);
	    			if(i < n) adde(ND[i][j][Lf], ND[i + 1][j][Rt], 1, R[i][j]);
	    			if(i > 1) adde(ND[i][j][Lf], ND[i - 1][j][Rt], 1, R[i - 1][j]);
				} else {		  //竖进横出 
	    			if(j < m) adde(ND[i][j][Lf], ND[i][j + 1][Rt], 1, C[i][j]);
	    			if(j > 1) adde(ND[i][j][Lf], ND[i][j - 1][Rt], 1, C[i][j - 1]);
	    			if(i < n) adde(ND[i + 1][j][Lf], ND[i][j][Rt], 1, R[i][j]);
	    			if(i > 1) adde(ND[i - 1][j][Lf], ND[i][j][Rt], 1, R[i - 1][j]);
				}
	    	}
		int ans = MaxFlow(S, T);
	    if(ans == -1) printf("Impossible\n");
	    else printf("%d\n", ans);
	}
    return 0;
}

甚至,我贴了份ZKW也没有过,mmp

#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstring>
#include<iostream>
#define LL long long
using namespace std;
const int maxm = 1e4 + 5;
const int maxn = 5e3 + 5;
const int N = 40;
const int inf = 0x3f3f3f3f;
  
inline char gc(){
    static char buf[5000000], *p1 = buf, *p2 = buf;
    return (p1 == p2) && (p2 = (p1 = buf) + fread(buf, 1, 5000000, stdin), p1 == p2) ? EOF : *p1++;
}
int f = 1;
inline void read(int & x) {
    x = 0, f = 1; static char c = gc();
    while(!isdigit(c)) {
    	if(c == '-') f = -1;
    	c = gc();
    }
    while(isdigit(c)) x = x * 10 + c - '0', c = gc();
    x *= f;
} 

LL dw;
const int Lf = 0, Rt = 1;
int C[N][N], R[N][N], ban[N][N];
int n, m, K, u, v, ND[N][N][2], dcnt;

namespace ZKW {
	bool vis[maxn];int dist[maxn];
	//解释一下各数组的含义:vis两个用处:spfa里的访问标记,増广时候的访问标记,dist是每个点的距离标号
	int s,t,ans=0;
	//s是起点,t是终点,ans是费用答案
	int nedge=-1,p[maxm << 2],c[maxm << 2],cc[maxm << 2],nex[maxm << 2],head[maxn];
	//这里是边表,解释一下各数组的含义:p[i]表示以某一点出发的编号为i的边对应点,c表示编号为i的边的流量,cc表示编号为i的边的费用,nex和head不说了吧。。。
	inline void addedge(int x,int y,int z,int zz){
	    p[++nedge]=y;c[nedge]=z;cc[nedge]=zz;nex[nedge]=head[x];head[x]=nedge;
	}
	inline void adde(int x, int y, int z, int zz){
		addedge(x, y, z, zz);
		addedge(y, x, 0, -zz);
	}
	//建边(数组模拟边表倒挂)
	int q[maxn * maxn], frt, bk;
	inline bool spfa(int s,int t){
	    memset(vis,0,sizeof vis),frt=bk=1;
	    for(int i=0;i<=dcnt;i++)dist[i]=1e9;dist[t]=0;vis[t]=1;
	//首先SPFA我们维护距离标号的时候要倒着跑,这样可以维护出到终点的最短路径
	    q[bk++]=t;
	//使用了SPFA的SLF优化(SLF可以自行百度或Google)
	    while(frt<bk){
	        int now=q[frt];++frt;
	        for(int k=head[now];k>-1;k=nex[k])if(c[k^1]&&dist[p[k]]>dist[now]-cc[k]){
	//首先c[k^1]是为什么呢,因为我们要保证正流,但是SPFA是倒着跑的,所以说我们要求c[k]的对应反向边是正的,这样保证走的方向是正确的
	            dist[p[k]]=dist[now]-cc[k];
	//因为已经是倒着的了,我们也可以很清楚明白地知道建边的时候反向边的边权是负的,所以减一下就对了(负负得正)
	            if(!vis[p[k]]){
	                vis[p[k]]=1;
	                q[bk++]=p[k];
	            }
	        }
	        vis[now]=0;
	    }
	    return dist[s]<1e9;
	//判断起点终点是否连通
	}
	inline int dfs(int x,int low){
	//这里就是进行増广了
	    if(x==t){vis[t]=1;return low;}
	    int used=0,a;vis[x]=1;
	//这边是不是和dinic很像啊
	    for(int k=head[x];k>-1;k=nex[k])if(!vis[p[k]]&&c[k]&&dist[x]-cc[k]==dist[p[k]]){
	//这个条件就表示这条边可以进行増广
	        a=dfs(p[k],min(c[k],low-used));
	        if(a)ans+=a*cc[k],c[k]-=a,c[k^1]+=a,used+=a;
	//累加答案,加流等操作都在这了
	        if(used==low)break;
	    }
	    return used;
	}
	inline int costflow(){
	    int flow=0; ans=0;
	    while(spfa(s,t)){
	//判断起点终点是否连通,不连通说明满流,做完了退出
	        vis[t]=1;
	        while(vis[t]){
	            memset(vis,0,sizeof vis);
	            flow+=dfs(s,1e9);
	//一直増广直到走不到为止(这样也可以省时间哦)
	        }
	    }
	    return flow == dw ? -ans : -1;//这里返回的是最大流,费用的答案在ans里
	}

}
 
 
int main() {
	using namespace ZKW;
    freopen("bounce.in", "r", stdin);
    freopen("bounce.out", "w", stdout);
    int T;
    read(T);
    while(T--) {
        memset(nex,-1,sizeof(nex));
		memset(head,-1,sizeof(head));
        memset(ban, 0, sizeof(ban));
        nedge = -1, dcnt = 0;
        read(n), read(m), dw = n * m;
        for(register int i = 1; i <= n; ++i)
            for(register int j = 1; j < m; ++j)
                read(C[i][j]);
        for(register int i = 1; i < n; ++i)
            for(register int j = 1; j <= m; ++j)
                read(R[i][j]);
        read(K);
        for(register int i = 1; i <= K; ++i) {
            read(u), read(v);
            ban[u][v] = 1;
        } 
        s = ++dcnt, t = ++dcnt;
        for(register int i = 1; i <= n; ++i)
            for(register int j = 1; j <= m; ++j) {
                ND[i][j][Lf] = ++dcnt;
                ND[i][j][Rt] = ++dcnt;
                adde(s, ND[i][j][Lf], 1, 0);
                if(!ban[i][j]) adde(ND[i][j][Lf], ND[i][j][Rt], 1, 0);
                adde(ND[i][j][Rt], t, 1, 0);
            }
        for(register int i = 1; i <= n; ++i)
            for(register int j = 1; j <= m; ++j) {
                if((i + j) & 1) { //横进竖出 
                    if(j < m) adde(ND[i][j + 1][Lf], ND[i][j][Rt], 1, -C[i][j]);
                    if(j > 1) adde(ND[i][j - 1][Lf], ND[i][j][Rt], 1, -C[i][j - 1]);
                    if(i < n) adde(ND[i][j][Lf], ND[i + 1][j][Rt], 1, -R[i][j]);
                    if(i > 1) adde(ND[i][j][Lf], ND[i - 1][j][Rt], 1, -R[i - 1][j]);
                } else {          //竖进横出 
                    if(j < m) adde(ND[i][j][Lf], ND[i][j + 1][Rt], 1, -C[i][j]);
                    if(j > 1) adde(ND[i][j][Lf], ND[i][j - 1][Rt], 1, -C[i][j - 1]);
                    if(i < n) adde(ND[i + 1][j][Lf], ND[i][j][Rt], 1, -R[i][j]);
                    if(i > 1) adde(ND[i - 1][j][Lf], ND[i][j][Rt], 1, -R[i - 1][j]);
                }
            }
        int ans = costflow();
        if(ans == -1) printf("Impossible\n");
        else printf("%d\n", ans);
    }
    return 0;
}

 

上一篇: LOJ#2585. 「APIO2018」新家

下一篇: BZOJ5219: [Lydsy2017省队十连测]最长路径

499 人读过
立即登录, 发表评论.
没有帐号? 立即注册
0 条评论
文档导航