HDU - 6749 Mosquito (2020 年百度之星·程序设计大赛 - 初赛一 1007) Apare_xzc

HDU - 6749 Mosquito (2020 年百度之星·程序设计大赛 - 初赛一 1007)

2020.7.21

在这里插入图片描述


Sample Input

2
5 5 4
1 1 10
1 5 10
5 1 10
5 5 10
3 3 1
1 1 8

Sample Output

4
-1

题目链接:

HDU-6749 <–
2020百度之星初赛一1007 <–


分析:

        一共m*n的格点,窗户都在边上。蚊子每秒钟可以移动一格。问蚊子最少花多长时间可以让每个格点都有一只。
        首先我们可以先得到答案是否存在。如果所有窗户的蚊子总数比格点总数少,那么一定不可能完成任务。反之答案一定存在。 某个窗户上的一只蚊子飞到某个目标格点最少需要花的时间为窗户和目标格点的曼哈顿距离。问题满足二分性质,即时间越长,越可能满足条件。答案的下限是0,因为可能在开始就已经满足条件。答案的上限是m+n-2, 因为这是格点中最远两个点的曼哈顿距离。
        现在考虑如何check,即为给定了限定时间t,我们检验是否可以满足题目条件。因为蚊子的数量是不变的,所以,如果一只蚊子去了格点A就不能去格点B,这满足网络流的性质。我们可以用网络流来判断,我们思考如何构图。
        首先我们可以建立源点S和汇点T,因为窗口的个数不大于6,所以我们每个窗口都建立一个结点。因为第i个窗户有a[i]只蚊子,所以我们建立源点S到窗口i的权值为a[i]的边,这样就限制了输入的总流量为蚊子之和。
        显然我们可以把每个格点都建立一个结点,那么一共就要建立n*m个结点。我们可以让每个窗户到每个t时间内蚊子可以到达格点都连一条权值为无穷大(这里为a[i]即可,因为这个窗户最多这么多只蚊子)的边。然后每个格点连一条到汇点T权值为1的边,这样我们从S到T的最大流就是t时间内蚊子最多可以覆盖的格点数。如果最大流等于总格点数n*m,则可以,否则不可以。 但是我们发现这样图的结点太多,我们考虑是否可以合并结点。因为对于每个窗户的蚊子是否可达,很多格点的情况都是一样的。 我们可以用窗户的蚊子是否可达进行分类,只有组多6个窗户,每个窗户是否可达,用01串表示,那么用二进制压缩状态,最多2^6=64种状态。那么就可以把n*m个格子按照t时间内所有窗户蚊子的可达情况分成2^k 类,每类所有格子合并为一个结点。结点的状态为x, 若x的二进制的第i位为1,那么我们就连一条从第i个窗户到状态为x的结点的一条权值为无穷大的边。当然,我们还要统计每个状态分类有多少个结点,即为cnt[x], 然后每个结点建立一条权值为cnt[x]连到汇点T的边。至此,直接跑最大流判断最大流是否可以达到m*n即可。


做法:

  1. 统计蚊子总数,如果蚊子总数小于格点总数,直接输出-1,结束。
  2. 如果蚊子总数 >= 格点总数,一定有答案。我们可以二分答案。答案范围为[0,m+n-1)
  3. check答案的时候我们用网络流。首先,认为增加源点S和汇点T。每个窗户为一个结点,从源点S向第i个窗户连一条权值为a[i]的单向边。我们把格点分为2^w类(w为窗户的个数)。每类的状态为x(0<x<2^w)。状态x二进制的第i位为1代表当前时间t之内窗户i的蚊子可以到达该类格点。统计cnt[x]为满足状态x的格点数。每个状态建立一个结点。每个状态x向汇点T连一条权值为cnt[x]的边。每个状态x如果第i位为1,就从第i个窗户向该状态x连一条权值为无穷大的边(a[i]即可)。构图后跑S到T的最大流,最大流能到达m*n即可。
  4. 结点编号可以这样分配(合理即可),令M=2^w。状态0到M-1的编号即为本身。代表第i个窗户的结点编号为M+i(0<=i<w), S编号为M+w,T编号为M+w+1
  5. 最大流可用最常见的dinic算法。构图的时候建立权值为0的反向边便于"反悔"。每次dfs求增广路相加。dfs之前可以bfs将图分层dep,增广路的深度路径的dep只能递增。

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 1001;
int x[6],y[6],a[6],dis[N][N][6],m,n,w;
int cnt[1<<6]; //cnt[i]表示state[i]的格子的个数 
struct Node{
	int to,next,d;
}node[2000]; //链式前向星构图 
int head[100],tot,cur[100],dep[100],S,T;
void addedge(int u,int v,int d) {
	node[tot].to = v;
	node[tot].d = d;
	node[tot].next = head[u];
	head[u] = tot++;
}
void initEdge() {
	tot = 0;
	memset(head,-1,sizeof(head));
}
void AddEdge(int u,int v,int d) {
	addedge(u,v,d);
	addedge(v,u,0); //反向边 
}
bool bfs() {
	memset(dep,-1,sizeof(dep));
	dep[S] = 0; //将图按照到源点的步数进行分层 
	queue<int> Q;
	Q.push(S);
	while(!Q.empty()) {
		int u = Q.front(); Q.pop();
		for(int i=head[u];i!=-1;i=node[i].next) {
			int to = node[i].to;
			if(dep[to]==-1&&node[i].d>0) //流量分配完的结点不分配深度 
				dep[to] = dep[u]+1,Q.push(to);
		}
	}
	return dep[T]!=-1;
}
int dfs(int u,int flow) {
	if(u==T) return flow;
	int sum = 0;
	for(int& i=cur[u];i!=-1;i=node[i].next) { //当前弧优化 
		int to = node[i].to;
		if(dep[to]==dep[u]+1&&node[i].d>0) {
			int add = dfs(to,min(flow,node[i].d));
			sum += add; //记录增广路之和 
			flow -= add; //减去已经分配过的流量 
			node[i].d -= add;
			node[i^1].d += add; //i^1为反向边 
		}
		if(flow==0) break;
	}
	if(sum==0) dep[u] = -1;
	return sum; 
}
int dinic() {
	int ans = 0;
	while(bfs()) {
		for(int i=0;i<=T;++i) cur[i] = head[i];
		ans += dfs(S,m*n);
	} 
	return ans;
}
bool check(int t) {
	memset(cnt,0,sizeof(cnt));
	int M = 1<<w;
	for(int i=0;i<n;++i) 
		for(int j=0;j<m;++j) {
			int x = 0; //记录各窗户蚊子在时间t下到格点(i,j)的可达状态 
			for(int k=0;k<w;++k)
				if(dis[i][j][k]<=t) x|=(1<<k);
			cnt[x]++; //状态计数 
		}
	//结点编号:状态0~M-1,就是本身的编号,M+0~M+w-1是窗户的编号
	initEdge();//初始化前向星 
	S = M+w; T = S+1; 
	for(int i=0;i<M;++i) {	
		AddEdge(i,T,cnt[i]); //状态到终点的边,为满足该条件的格子的个数
		for(int k=0;k<w;++k) {
			if(i&(1<<k)) AddEdge(M+k,i,a[k]);//连一条无穷大的边(最大为a[k]) 
		} 
	}
	for(int k=0;k<w;++k) //起点到窗户的边,最大流量为a[i] 
		AddEdge(S,M+k,a[k]);
	return dinic()==n*m; 	
}
int main(void) {
	int Ca;
	scanf("%d",&Ca);
	while(Ca--) {
		int sum = 0;
		scanf("%d%d%d",&n,&m,&w);
		for(int i=0;i<w;++i) 
			scanf("%d%d%d",x+i,y+i,a+i),
			sum+=a[i],x[i]--,y[i]--; //数据下标从1开始 
		if(sum<m*n) {
			puts("-1");continue;
		}
		for(int i=0;i<n;++i) //预处理每个坐标和窗户的曼哈顿距离 
			for(int j=0;j<m;++j)	
				for(int k=0;k<w;++k)
					dis[i][j][k] = abs(i-x[k])+abs(j-y[k]); //预处理每个结点到每个窗口的距离 
		int left = -1,right = m+n-1;
		while(right-left>1) { //[) 
			int mid = (left+right)>>1;
			if(check(mid)) right = mid;
			else left = mid;
		}
		printf("%d\n",right);		
	}	
	return 0;
} 

在这里插入图片描述


猜你喜欢

转载自blog.csdn.net/qq_40531479/article/details/107479921