题解 P2472 [SCOI2007]蜥蜴
Posted colazcy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了题解 P2472 [SCOI2007]蜥蜴相关的知识,希望对你有一定的参考价值。
Solution [SCOI2007]蜥蜴
题目大意:给定一个\(n\)行\(m\)列的地图,每个点有一个经过次数限制,可以从一个点跳到与它距离不超过\(d\)的另一个点.问有多少只蜥蜴不能从地图中出去
题目分析:有多少只蜥蜴不能从地图中出去,可以转化成最多有多少只蜥蜴可以从地图中出去.然后从一个点跳到另一个点我们自然而然想到连有向边,每个点的经过次数限制可以看做是流量上限.最多有多少只蜥蜴可以出去,就是求最大流量.这不就是一个最大流么.然后我们来看核心——建图
首先,普通的最大流问题是每条边有流量限制,但是这个题是每个点有流量限制 .对于这个问题,拆点成边是基本操作.对于每个流量限制为\(d\)的点\(A\),连一条流量限制为\(d\)的边\((a,a')\)
然后对于从一个点跳到另一个点,如果它们间的距离小于题中所给的\(d\),设这两个点为\(A\),\(B\).拆点后它们对应的两条边应该是\((a,a')\),\((b,b')\).我们连一条边\((a',b)\),权值为\(INF\)(一个点经过次数由拆点后的边的流量限制来限制)
然后如果一个点\(A\)可以跳出地图,我们就建一个虚拟汇点\(T\),连边\((a',T)\),边权为\(INF\)
我们再建一个虚拟节点\(S\),从\(S\)向每一个有蜥蜴的点连一条边权为\(1\)的边.因为题目中限制同一个点上只能有一个蜥蜴(之前不读题调了好久)
拆点之后,原图中两个点之间有双向边是不影响答案的(并不会超流量),具体画画图很容易想,这里不再赘述
然后我们跑一遍\(S\),\(T\)最大流即可得出答案
献上码风丑陋的代码:
#include <cstdio>
#include <cstring>
#include <queue>
#include <cmath>
using namespace std;
const int maxn = 4040;
const int INF = 0x7fffffff;
const int maxm = 444444;
struct Point//存点的结构体,x,y为坐标,z为石柱可以经过的次数(高度),mark为该点是否有蜥蜴
int x,y,z,mark;
point[maxn];
inline double calc(const Point &a,const Point &b)
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
struct Edge//存图
int from,to,cap,flow;
Edge()
Edge(int a,int b,int c,int d):from(a),to(b),cap(c),flow(d)
Edges[maxm];
int head[maxn],nxt[maxm],tot = 1;
inline void addedge(int from,int to,int cap)
Edges[++tot] = Edge(from,to,cap,0);
nxt[tot] = head[from];
head[from] = tot;
Edges[++tot] = Edge(to,from,0,0);
nxt[tot] = head[to];
head[to] = tot;
int a[maxn],pre[maxn];
inline int maxflow(int s,int t)//蒟蒻为了省事直接上EK了,紫书的模板
int ret = 0;
while(true)
memset(a,0,sizeof(a));
queue<int> Q;
Q.push(s);
a[s] = 0x7fffffff;
while(!Q.empty())
int u = Q.front();Q.pop();
for(int i = head[u];i;i = nxt[i])
Edge &e = Edges[i];
int v = Edges[i].to;
if(!a[v] && e.flow < e.cap)
a[v] = min(a[u],e.cap - e.flow);
pre[v] = i;
Q.push(v);
if(a[t])break;
if(!a[t])break;
for(int i = pre[t];i;i = pre[Edges[i].from])
Edges[i].flow += a[t],Edges[i ^ 1].flow -= a[t];
ret += a[t];
return ret;
char str[maxn];
int n,m,d,cnt;//cnt表示一共有多少只蜥蜴
inline int get(int x,int y)return (x - 1) * m + y;//根据每个点的坐标计算出它的编号
int main()
scanf("%d %d %d",&n,&m,&d);
for(int i = 1;i <= n;i++)//读入
scanf("%s",str + 1);
for(int j = 1;j <= m;j++)
int p = get(i,j);
point[p].x = i;
point[p].y = j;
point[p].z = str[j] - '0';
for(int i = 1;i <= n;i++)
scanf("%s",str + 1);
for(int j = 1;j <= m;j++)
point[get(i,j)].mark = (str[j] == 'L') ? 1 : 0;
if(str[j] == 'L')cnt++;
for(int i = 1;i <= n;i++)//拆点,所以新的点的编号就是原来点的编号乘2与乘2加1
for(int j = 1;j <= m;j++)
if(point[get(i,j)].z)addedge(2 * get(i,j),2 * get(i,j) + 1,point[get(i,j)].z);
for(int i = 1;i <= n;i++)
for(int j = 1;j <= m;j++)
if(!point[get(i,j)].z)continue;
for(int k = 1;k <= n;k++)
for(int w = 1;w <= m;w++)//处理从一个点调到另一个点的情况
if(point[get(k,w)].z && calc(point[get(i,j)],point[get(k,w)]) <= d && !(i == k && j == w))addedge(2 * get(i,j) + 1,2 * get(k,w),INF);
for(int i = 1;i <= n * m;i++)//虚拟源点,向每个有蜥蜴的点连边
if(point[i].mark)addedge(2 * n * m + 2,i * 2,1);
for(int i = 1;i <= n * m;i++)//虚拟汇点
if(min(min(point[i].x,n - point[i].x + 1),min(point[i].y,m - point[i].y + 1)) <= d && point[i].z)addedge(i * 2 + 1,2 * n * m + 3,INF);
printf("%d\n\n",cnt - maxflow(2 * n * m + 2,2 * n * m + 3));//跑S-T最大流即为答案
return 0;
以上是关于题解 P2472 [SCOI2007]蜥蜴的主要内容,如果未能解决你的问题,请参考以下文章