[bzoj2547]玩具兵<Spfa+二分+匈牙利算法>
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2547
挺有意思的一道题,这道题可以划分成几个小题。。。。。。。
题目大意:
三个兵种在一个n*m的图上,图的每一个格子有一个高度,三个兵种一种走不下降的路,一种走不上升的路,一种随便走。数量分别是k,k,1
然后t个目的地,每个目的地必须有ri个兵,ri之和为2*k+1
然后你可以交换兵,让不能走的情况又变成能走,例如,第一种兵走到了山顶,四周都是下降的,这时候就可以和第二,三种兵交换位置,继续走下去。。。
【解题思路】
这道题分解下来就是先来一个spfa找每一个兵到每一个点的最小交换次数,然后就是二分这个一共的交换次数,接着就是二分图匹配。。。
我们跑spfa,以每个兵开始扩展,遇到不能走就换一次,记录每个兵到每个点的最小交换次数,然后把当前兵到指定点的值记录入一个新的数组(表示二分图的连接情况)
然后二分。。因为最少是不交换,最大交换2*k次,因为可以每个兵和天兵交换一次然后去终点,天兵是随便走,所以最多交换2*k次,然后二分次数。。二分当中的check就是用二分图匹配了。。。Check成立的判断时交换次数+最大匹配数,如果这个值大于了兵的数量,说明这个方案是可行的。。。
这是大致思路,但是还是要注意一些细节的地方。
Spfa要注意当前的兵种是在变化的,所以要结合到当前位置之前的交换次数和最初状态
最初为0,交换偶数次,当前还是0,交换奇数次,当前为1
最初为1,交换偶数次,当前还是1,交换奇数次,当前为0
假设开始是c,次数是t,这个最终结果就是c^(t&1)
另外一个就是要注意二分图匹配时,除了要求vis[i]==0之外,还有x兵到i点的次数小于我们二分出来的限制。。。
然后就还是结合代码理解吧,我看别人的思路都不是很懂,还是结合代码读懂的。。。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<iostream> 5 #include<cmath> 6 #include<cstdlib> 7 #include<queue> 8 #define maxn 105 9 using namespace std; 10 11 const int dx[]={0,-1,1,0,0}; 12 const int dy[]={0,0,0,-1,1}; 13 14 int att[maxn][maxn],h[maxn][maxn]; 15 int f[maxn][maxn];//表示到达这个位置的最小交换次数 16 int n,m,k,t,r; 17 pair<int,int>a[maxn]; 18 pair<int,int>b[maxn]; 19 queue<pair<int,int> >q; 20 21 void init() 22 { 23 scanf("%d%d%d%d",&n,&m,&k,&t); 24 for(int i=1;i<=2*k+1;i++) 25 { 26 scanf("%d%d",&a[i].first,&a[i].second); 27 } 28 int tot=1; 29 for(int i=1;i<=t;i++) 30 { 31 scanf("%d%d%d",&b[tot].first,&b[tot].second,&r); 32 r--;tot++; 33 for(r;r>=1;r--,tot++){ 34 b[tot]=b[tot-1]; 35 } 36 } 37 for(int i=1;i<=n;i++) 38 for(int j=1;j<=m;j++) 39 scanf("%d",&h[i][j]); 40 } 41 42 void bfs(int x,int y,int cnt){ 43 memset(f,0x3f3f3f,sizeof(f)); 44 f[x][y]=0; 45 q.push(make_pair(x,y)); 46 while(!q.empty()){ 47 int nx=q.front().first,ny=q.front().second; 48 for(int i=1;i<=4;i++) 49 { 50 int nowx=nx+dx[i],nowy=ny+dy[i]; 51 if(nowx<1||nowx>n||nowy<1||nowy>m)continue; 52 int tt=0; 53 if(cnt^(f[nx][ny]&1)){ 54 if(h[nowx][nowy]>h[nx][ny])tt=1; 55 }else if(h[nowx][nowy]<h[nx][ny])tt=1; 56 if(f[nowx][nowy]>f[nx][ny]+tt){ 57 f[nowx][nowy]=f[nx][ny]+tt; 58 q.push(make_pair(nowx,nowy)); 59 } 60 61 } 62 q.pop(); 63 } 64 65 } 66 67 int vis[maxn*10],ck[maxn*10]; 68 69 int ok(int x,int limit){ 70 for(int i=1;i<=2*k+1;i++) 71 { 72 if(vis[i]==0&&att[x][i]<=limit){ 73 vis[i]=1; 74 if(ck[i]==0||ok(ck[i],limit)){ 75 ck[i]=x;return 1; 76 } 77 } 78 } 79 return 0; 80 } 81 82 int check(int x){ 83 memset(ck,0,sizeof(ck)); 84 int tot=0; 85 for(int i=1;i<=2*k;i++){ 86 memset(vis,0,sizeof(vis)); 87 if(ok(i,x))tot++; 88 } 89 if(tot+x>=2*k)return 1; 90 else return 0; 91 } 92 93 int ans(){ 94 int l=0,r=2*k;//最坏情况就是天兵挨着和每一个交换,最多换2*k次 95 while(l<r){ 96 int mid=(l+r)>>1; 97 if(check(mid))r=mid;else l=mid+1; 98 } 99 return l; 100 } 101 102 int main() 103 { 104 init(); 105 for(int i=1;i<=2*k;i++) 106 { 107 if(i<=k)bfs(a[i].first,a[i].second,0); 108 else bfs(a[i].first,a[i].second,1); 109 for(int j=1;j<=2*k+1;j++){ 110 att[i][j]=f[b[j].first][b[j].second]; 111 } 112 } 113 printf("%d\n",ans()); 114 }
总结:
把一个大题打乱成多个小题是一个很有效的方式
注意分析状态的变化,比如题中的兵种在spfa的时候是变化的