1632. 搬运工
★ 输入文件:worker.in
输出文件:worker.out
简单对比时间限制:1 s 内存限制:256 MB
【题目描述】
小涵向小宇推荐了一款小游戏。
游戏是这样的,在一个n*n的地图中,有若干个格子上有障碍物。你需要雇佣搬运工,将这些障碍物全部清除。不过每次操作你只能让搬运工将某一行或者某一列的障碍物全部清除。如果你让搬运工清除第i行障碍物,需要付出ai元;如果你让搬运工清除第j列障碍物,需要付出bj元。
小涵告诉小宇,必须用尽可能少的次数消除这些障碍物。若有多种方案,则必须花费尽量少的费用。结果小宇想了很久仍然没有闯过第一关,只好向你求助了。
【输入格式】
第1行,一个正整数n。
第2~n+1行,每行n个字符。第i+1行的第j个字符表示地图的坐标(i, j)的格子(左上角为起点(1, 1))。’*’表示障碍,’.’表示空格。
第n+2行,n个正整数,第i个数表示清除地图第i行的费用。
第n+3行,n个正整数,第i个数表示清除地图第i列的费用。
【输出格式】
输出2行。第1行是最少次数,第2行是在最少次数的前提下费用的最小值。
【样例输入】
3 ... .*. **. 10 5 17 1 8 4
【样例输出】
2 9
【提示】
30%的数据满足对于任意i和j(1 <= i, j <= n),有ai = bj。
100%的数据满足1 <= n <= 200,0 <= ai, bj <= 100.
[样例说明]一共有三个障碍物,坐标分别是(2, 2), (3, 1), (3, 2)。消除第1列和第2列是最优方案。
【来源】
HZOI
分析:
这是个很经典的二分图模型。以行为二分图的x部,列为二分图的y部。若格子(x, y)需要被消除,则连一条从x到y的边。最少次数即为二分图的最小点覆盖数。易证最小点覆盖数等于二分图的最大匹配数。
但是题目还要求代价尽量小。那么怎么办呢?实际上,最大匹配也可以当做最小割做。从源s向每个x部点连一条容量为1的边,从每个y部点向汇t连一条容量为1的边,而二分图的每条边容量设为+∞。一个割会使得s和t不连通,也就使得二分图的每条边的两个端点中至少有一个被选出。同时最小割是所有割中代价最小的。
//===============================到这里,第一问很显然转换成了最小割=============
那么我们按照第一问的思路:从源s向每个x部点连一条容量为a[x](消除行的代价)的边,从每个y部点向汇t连一条容量为b[y](消除列的代价)的边,继续跑最小割,答案一定满足花费尽量少。但不一定满足尽可能少的次数。比如2个花费为1的,1个花费为3的,删去前者整体和后者整体都可以消除。so 只有50分。
//===============================插入一段错误的思想,望大家引以为戒=============
提出这个模型的意义在于可以通过修改边的容量使得它能解决此题。我们考虑修改这个最小割模型的边的容量。将原来容量为1的边修改容量为10^6+ai(或bj)。由于数据范围中ai, bj<=100且n <= 100,所以∑ai+∑bj仍然远小于10^6。所以最小割必然是先满足删去的边尽量少,然后满足附加的容量和尽量小。
最小次数即等于新图最小割除以10^6的商,最小代价等于最小割模10^6的余数。如果用Dinic计算最小割,则时间复杂度是O(n4)。实际上最短增广路算法一般远远达不到理论复杂度,因此可以完美地解决这道题。
//===============================很巧妙的思路转化========================
显示代码纯文本
- #include<cstdio>
- #include<cstring>
- #include<iostream>
- using namespace std;
- const int N=410;
- const int inf=1e6;
- const int INF=2e9;
- struct edge{int v,cap,next;}e[N*N*2];int tot=1,head[N];
- int n,m,S,T,ans1,ans2,a[N],b[N],dis[N],q[N*N*2];bool vis[N];
- char mp[N>>1][N>>1];
- inline int read(){
- int x=0,f=1;char ch=getchar();
- while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
- while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
- return x*f;
- }
- inline void add(int x,int y,int z){
- e[++tot].v=y;e[tot].cap=z;e[tot].next=head[x];head[x]=tot;
- e[++tot].v=x;e[tot].cap=0;e[tot].next=head[y];head[y]=tot;
- }
- inline bool bfs(){
- memset(dis,-1,sizeof dis);
- unsigned short h=0,t=1;q[t]=S;dis[S]=0;
- while(h!=t){
- int x=q[++h];
- for(int i=head[x];i;i=e[i].next){
- if(e[i].cap&&dis[e[i].v]==-1){
- dis[e[i].v]=dis[x]+1;
- if(e[i].v==T) return 1;
- q[++t]=e[i].v;
- }
- }
- }
- return 0;
- }
- int dfs(int x,int f){
- if(x==T) return f;
- int used=0,t;
- for(int i=head[x];i;i=e[i].next){
- if(e[i].cap&&dis[e[i].v]==dis[x]+1){
- t=dfs(e[i].v,min(e[i].cap,f));
- e[i].cap-=t;e[i^1].cap+=t;
- used+=t;f-=t;
- if(!f) return used;
- }
- }
- if(!used) dis[x]=-1;
- return used;
- }
- inline void dinic(){
- int res=0;
- while(bfs()) res+=dfs(S,inf);
- ans1=res/inf;
- ans2=res%inf;
- }
- void init(){
- n=read();S=0;T=n<<1|1;
- for(int i=1;i<=n;i++) scanf("%s",mp[i]+1);
- for(int i=1;i<=n;i++) a[i]=read();
- for(int i=1;i<=n;i++) b[i]=read();
- for(int i=1;i<=n;i++) add(S,i,inf+a[i]);
- for(int i=1;i<=n;i++) add(i+n,T,inf+b[i]);
- for(int i=1;i<=n;i++){
- for(int j=1;j<=n;j++){
- if(mp[i][j]=='*'){
- add(i,j+n,INF);
- }
- }
- }
- dinic();
- printf("%d %d",ans1,ans2);
- }
- int main(){
- freopen("worker.in","r",stdin);
- freopen("worker.out","w",stdout);
- init();
- return 0;
- }