2010-1115
从 Trac 迁移的文章
这是从旧校内 Wiki 迁移的文章,可能存在一些样式问题,您可以向 memset0 反馈。
原文章内容如下:
题目意思就是要在方格纸中建围墙,使X和E无法联通,然后X每能和一个A联通收入就会增加这个A对应的权值,画圈的花费是所经过的边权之和(经过两次就算两次)。
做法:
1、枚举圈上的一点作为起点,状态dist[i][j][k]表示从这个起点出发到达i,j点,且经过各A点向右的射线为奇数次的mask为k(有点拗口,等等详细解释)时的最小画圈花费,那么,回到起点的dist[i][j][k]值就表示画圈包含在内的点的mask为k时的最小花费。
2、这样就可以得到单独的一个圈包含的点为mask时所需要的最小花费A[mask],但是最终建好的围墙不一定只是一个圈,有可能是好几个圈,还需要对mask再行枚举得到结果。
对1中k的意义的解释:
当我们定好起点后,设想每个点都向右射出一条射线,那么,当我们从起点画圈再返回起点时,如果穿过了某条射线奇数次的话,说明我们把对应的这个点包在了圈中(射线法判点在多边形内),于是,我们在这个圈尚未画好,还是一条路径的时候,就可以记录它穿越某条射线的次数的奇偶次,把这些状态压缩成mask,就得到了k
{{{
#!cpp
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <algorithm>
#define MP make_pair
using namespace std;
const int MAXN = 31;
const int INF = 0x3f3f3f3f;
int N, M, K;
int di[4] = {0, 0, -1, 1};
int dj[4] = {-1, 1, 0, 0};
int bi[4] = {0, 0, -1, 1};
int bj[4] = {-1, 0, 0, 0};
int cost[MAXN * 2][MAXN], mat[MAXN * 2][MAXN];
int dist[MAXN][MAXN][1 << 6];
bool in[MAXN][MAXN][1 << 6];
int add[10], num[10];
inline bool ok(int i, int j){
return (0 <= i && i <= N && 0 <= j && j <= M);
}
inline void border(int i, int j, int dir, int & ret1, int & ret2){
switch(dir){
case 0:
case 1:
i = i * 2;
j -= (1 - dir);
break;
case 2:
case 3:
i = (i + dir - 2) * 2 - 1;
break;
}
ret1 = mat[i][j];
ret2 = cost[i][j];
}
int tot;
void bfs(int i, int j){
int k, pi, pj, nk, co, dir;
memset(dist, 0x3f, sizeof(dist));
memset(in, 0, sizeof(in));
dist[i][j][0] = 0;
priority_queue <pair <int, int> > Q;
Q.push(MP(0, ((i << 4) | j) << 6));
while (!Q.empty()){
++tot;
i = (Q.top().second >> 10);
j = ((Q.top().second >> 6) & 15);
k = (Q.top().second & 63);
int tmp = dist[i][j][k];
if (tmp != -Q.top().first){
Q.pop();
continue;
}
Q.pop();
for (dir = 0; dir < 4; ++dir){
pi = i + di[dir];
pj = j + dj[dir];
if (ok(pi, pj)){
nk = k ^ mat[i * 2 + bi[dir]][j + bj[dir]];
co = cost[i * 2 + bi[dir]][j + bj[dir]];
if (dist[pi][pj][nk] > tmp + co){
dist[pi][pj][nk] = tmp + co;
Q.push(MP(-dist[pi][pj][nk], (((pi << 4) | pj) << 6) | nk));
}
}
}
}
}
int main(){
int i, j, k, x;
int mask;
int A[1 << 6], out[1 << 6];
while (scanf("%d%d", &N, &M) != EOF){
tot = 0;
for (i = 0; i < 2 * N + 1; ++i){
for (j = 0; j < M + (i & 1); ++j){
scanf("%d", &cost[i][j]);
}
}
memset(mat, 0, sizeof(mat));
scanf("%d", &K);
for (mask = k = 0; k < K; ++k){
scanf("%d%d%d", &add[k], &i, &j);
if (add[k] == 0)
x = k;
for (i = i * 2 + 1, ++j; j <= M; ++j)
mat[i][j] |= (1 << k);
}
memset(A, 0x3f, sizeof(A));
for (i = 0; i < N; ++i){
for (j = 0; j < M; ++j){
bfs(i, j);
for (k = 0; k < (1 << K); ++k){
A[k] = min(A[k], dist[i][j][k]);
}
}
}
memset(out, 0x3f, sizeof(out));
for (k = 0; k < (1 << K); ++k){
out[k] = A[k];
}
for (k = 0; k < (1 << K); ++k){
for (i = k; i > 0; i = (i - 1) & k)
out[k] = min(out[k], A[i] + out[k ^ i]);
}
int ans = INF, now;
for (k = 0; k < (1 << K); ++k){
if (k & (1 << x)){
for (mask = now = i = 0; i < K; ++i)
if ((k & (1 << i))){
if (add[i] >= 0){
now -= add[i];
} else {
mask |= (1 << i);
}
}
ans = min(ans, now + out[mask] + A[k]);
}
}
printf("%d\n", ans);
}
}
}}}
题目意思就是要在方格纸中建围墙,使X和E无法联通,然后X每能和一个A联通收入就会增加这个A对应的权值,画圈的花费是所经过的边权之和(经过两次就算两次)。
做法:
1、枚举圈上的一点作为起点,状态dist[i][j][k]表示从这个起点出发到达i,j点,且经过各A点向右的射线为奇数次的mask为k(有点拗口,等等详细解释)时的最小画圈花费,那么,回到起点的dist[i][j][k]值就表示画圈包含在内的点的mask为k时的最小花费。
2、这样就可以得到单独的一个圈包含的点为mask时所需要的最小花费A[mask],但是最终建好的围墙不一定只是一个圈,有可能是好几个圈,还需要对mask再行枚举得到结果。
对1中k的意义的解释:
当我们定好起点后,设想每个点都向右射出一条射线,那么,当我们从起点画圈再返回起点时,如果穿过了某条射线奇数次的话,说明我们把对应的这个点包在了圈中(射线法判点在多边形内),于是,我们在这个圈尚未画好,还是一条路径的时候,就可以记录它穿越某条射线的次数的奇偶次,把这些状态压缩成mask,就得到了k
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <algorithm>
#define MP make_pair
using namespace std;
const int MAXN = 31;
const int INF = 0x3f3f3f3f;
int N, M, K;
int di[4] = {0, 0, -1, 1};
int dj[4] = {-1, 1, 0, 0};
int bi[4] = {0, 0, -1, 1};
int bj[4] = {-1, 0, 0, 0};
int cost[MAXN * 2][MAXN], mat[MAXN * 2][MAXN];
int dist[MAXN][MAXN][1 << 6];
bool in[MAXN][MAXN][1 << 6];
int add[10], num[10];
inline bool ok(int i, int j){
return (0 <= i && i <= N && 0 <= j && j <= M);
}
inline void border(int i, int j, int dir, int & ret1, int & ret2){
switch(dir){
case 0:
case 1:
i = i * 2;
j -= (1 - dir);
break;
case 2:
case 3:
i = (i + dir - 2) * 2 - 1;
break;
}
ret1 = mat[i][j];
ret2 = cost[i][j];
}
int tot;
void bfs(int i, int j){
int k, pi, pj, nk, co, dir;
memset(dist, 0x3f, sizeof(dist));
memset(in, 0, sizeof(in));
dist[i][j][0] = 0;
priority_queue <pair <int, int> > Q;
Q.push(MP(0, ((i << 4) | j) << 6));
while (!Q.empty()){
++tot;
i = (Q.top().second >> 10);
j = ((Q.top().second >> 6) & 15);
k = (Q.top().second & 63);
int tmp = dist[i][j][k];
if (tmp != -Q.top().first){
Q.pop();
continue;
}
Q.pop();
for (dir = 0; dir < 4; ++dir){
pi = i + di[dir];
pj = j + dj[dir];
if (ok(pi, pj)){
nk = k ^ mat[i * 2 + bi[dir]][j + bj[dir]];
co = cost[i * 2 + bi[dir]][j + bj[dir]];
if (dist[pi][pj][nk] > tmp + co){
dist[pi][pj][nk] = tmp + co;
Q.push(MP(-dist[pi][pj][nk], (((pi << 4) | pj) << 6) | nk));
}
}
}
}
}
int main(){
int i, j, k, x;
int mask;
int A[1 << 6], out[1 << 6];
while (scanf("%d%d", &N, &M) != EOF){
tot = 0;
for (i = 0; i < 2 * N + 1; ++i){
for (j = 0; j < M + (i & 1); ++j){
scanf("%d", &cost[i][j]);
}
}
memset(mat, 0, sizeof(mat));
scanf("%d", &K);
for (mask = k = 0; k < K; ++k){
scanf("%d%d%d", &add[k], &i, &j);
if (add[k] == 0)
x = k;
for (i = i * 2 + 1, ++j; j <= M; ++j)
mat[i][j] |= (1 << k);
}
memset(A, 0x3f, sizeof(A));
for (i = 0; i < N; ++i){
for (j = 0; j < M; ++j){
bfs(i, j);
for (k = 0; k < (1 << K); ++k){
A[k] = min(A[k], dist[i][j][k]);
}
}
}
memset(out, 0x3f, sizeof(out));
for (k = 0; k < (1 << K); ++k){
out[k] = A[k];
}
for (k = 0; k < (1 << K); ++k){
for (i = k; i > 0; i = (i - 1) & k)
out[k] = min(out[k], A[i] + out[k ^ i]);
}
int ans = INF, now;
for (k = 0; k < (1 << K); ++k){
if (k & (1 << x)){
for (mask = now = i = 0; i < K; ++i)
if ((k & (1 << i))){
if (add[i] >= 0){
now -= add[i];
} else {
mask |= (1 << i);
}
}
ans = min(ans, now + out[mask] + A[k]);
}
}
printf("%d\n", ans);
}
}