@darkproject
2018-02-14T14:50:13.000000Z
字数 9758
阅读 1082
2018寒假集训
A - Ants (UVA - 1411)
n个白点和黑点,要求将白点和黑点连接起来,每一个线段一端是黑点另一端是白点,其中保证线段不相交。输出每个白点对应的黑点编号。黑白2色分开连可以看作一个二分图,此时最小权完美匹配即是问题的解。最小权完美匹配不会存在边相交的情况,可以画一个四边形,取对角线绝对不是最小的情况。这题卡精度需要注意还有就是if对于高精度的eps比较
#include<iostream>#include<algorithm>#include<cstdio>#include<cstring>#include<cmath>using namespace std;const int MAXN = 110;const double INF = 0xffffffffffff;const double eps = 1e-6;struct Node{double x,y;}Dot1[MAXN],Dot2[MAXN];double Dist(Node a,Node b){return -sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));}int N,NX,NY;double Map[MAXN][MAXN];int link[MAXN];double lx[MAXN],ly[MAXN],slack[MAXN];int visx[MAXN],visy[MAXN];int FindPath(int u){visx[u] = 1;for(int i = 1; i <= NY; ++i){if(visy[i])continue;double temp = lx[u] + ly[i] - Map[u][i];if(fabs(temp) <= eps){visy[i] = 1;if(link[i] == -1 || FindPath(link[i])){link[i] = u;return 1;}}else{if(slack[i] > temp)slack[i] = temp;}}return 0;}void KM(){memset(lx,0,sizeof(lx));memset(ly,0,sizeof(ly));memset(link,-1,sizeof(link));for(int i = 1; i <= NX; ++i){lx[i] = -INF;for(int j = 1; j <= NY; ++j)if(Map[i][j] > lx[i])lx[i] = Map[i][j];}for(int i = 1; i <= NX; ++i){for(int j = 1; j <= NY; ++j)slack[j] = INF;while(1){memset(visx,0,sizeof(visx));memset(visy,0,sizeof(visy));if(FindPath(i))break;double d = INF;for(int j = 1; j <= NY; ++j)if(!visy[j] && d > slack[j])d = slack[j];for(int j = 1; j <= NX; ++j)if(visx[j])lx[j] -= d;for(int j = 1; j <= NY; ++j){if(visy[j])ly[j] += d;elseslack[j] -= d;}}}}int main(){int N;while(~scanf("%d",&N)){memset(Map,0,sizeof(Map));for(int i = 1; i <= N; ++i)scanf("%lf%lf",&Dot1[i].x,&Dot1[i].y);for(int i = 1; i <= N; ++i)scanf("%lf%lf",&Dot2[i].x,&Dot2[i].y);for(int i = 1; i <= N; ++i)for(int j = 1; j <= N; ++j)Map[j][i] = Dist(Dot1[i],Dot2[j]);NX = NY = N;KM();for(int i=1;i<=N;i++)printf("%d\n",link[i]);printf("\n");}return 0;}
B - Golden Tiger Claw (UVA - 11383)
n*n的矩阵,矩阵内每个格子都有一个整数,我们需要对每行每列确定一个整数要求对任意格子w(i,j)<=row(i)+col(j).根据km的性质,格子整数的值w可以看做权值边,而row和col为顶标,km在执行过程中顶标之和肯定大于等于权值,且结束后所有顶标和最小。因此我们只需要执行一遍km输出顶标及顶标和即可。
#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>using namespace std;const int maxn = 505;const int INF = 0x3f3f3f3f;int n, nx, ny, w[maxn][maxn];int match[maxn], lx[maxn], ly[maxn], slack[maxn];bool visx[maxn], visy[maxn];bool dfs(int x){visx[x] = true;for(int y = 1; y <= ny; y++){if(visy[y]) continue;int t = lx[x] + ly[y] - w[x][y];if(t == 0){visy[y] = true;if(match[y] == -1 || dfs(match[y])){match[y] = x;return true;}}else if(slack[y] > t){slack[y] = t;}}return false;}void km(){nx = ny = n;memset(match, -1, sizeof(match));memset(ly, 0, sizeof(ly));for(int i = 1; i <= nx; i++){lx[i] = -INF;for(int j = 1; j <= ny; j++){if(w[i][j] > lx[i]) lx[i] = w[i][j];}}for(int x = 1; x <= nx; x++){for(int i = 1; i <= ny; i++){slack[i] = INF;}while(true){memset(visx, false, sizeof(visx));memset(visy, false, sizeof(visy));if(dfs(x)) break;int d = INF;for(int i = 1; i <= ny; i++){if(!visy[i] && d > slack[i]) d = slack[i];}for(int i = 1; i <= nx; i++){if(visx[i]) lx[i] -= d;}for(int i = 1; i <= ny; i++){if(visy[i]) ly[i] += d;else slack[i] -= d;}}}}int main(){while(scanf("%d", &n) != EOF){int mxr=0;for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%d",&w[i][j]);km();for(int i=1;i<n;i++){mxr+=lx[i];printf("%d ",lx[i]);}mxr+=lx[n];printf("%d\n",lx[n]);for(int i=1;i<n;i++){mxr+=ly[i];printf("%d ",ly[i]);}mxr+=ly[n];printf("%d\n%d\n",ly[n],mxr);}return 0;}
D - SAM I AM (UVA - 11419)
一个RxC的网格上面放一些目标,而在每行每列的位置我们可以放加农炮,会攻击所在行或列的位置,问至少需要多少个加农炮才能击杀所有目标,输出加农炮的个数和所放位置。我们将每行和每列看作结点,若(x,y)存在目标x向y连边,不难看出这是一个最小点覆盖问题的求解,难点在于输出最小点覆盖集。我们从左边未匹配点沿未匹配边标记点。解集便为左边未标记点+右边已标记点 证明:http://www.matrix67.com/blog/archives/116
#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>using namespace std;const int maxn = 1005;int check[maxn],linkery[maxn],linkerx[maxn];int r,c,n,nx,ny;int map[maxn][maxn],visx[maxn];bool dfs(int u){visx[u]=1;for(int v=1;v<=c;v++){if(map[u][v]&&!check[v]){check[v]=1;if(linkery[v]==-1||dfs(linkery[v])){linkery[v]=u;linkerx[u]=v;return true;}}}return false;}int hungarian(){int ans=0;memset(linkery,-1,sizeof(linkery));memset(linkerx,-1,sizeof(linkerx));for(int x=1;x<=r;x++){memset(check,0,sizeof(check));if(dfs(x)) ans++;}return ans;}int main(){while(scanf("%d%d%d",&r,&c,&n)){if(r==0&&c==0&&n==0) break;memset(map,0,sizeof(map));int x,y;for(int i=0;i<n;i++){scanf("%d%d",&x,&y);map[x][y]=1;}printf("%d",hungarian());memset(visx,0,sizeof(visx));memset(check,0,sizeof(check));for(int i=1;i<=r;i++)if(linkerx[i]==-1) dfs(i);for(int i=1;i<=r;i++)if(!visx[i]) printf(" r%d",i);for(int i=1;i<=c;i++)if(check[i]) printf(" c%d",i);printf("\n");}return 0;}
F - Taxi Cab Scheme (UVALive - 3126)
已知每个人的出发时间地点和目的地,2个地方距离所要时间为|x1-x2|+|y1-y2|.出租车接客人至少提前1分钟,问至少需要多少辆出租车才能接送完所有的客人。如果能接送客人a后还能接送b则a向b连边,可以得到一个DAG,接下来求解DAG上的最小边覆盖即可。DAG的最小边覆盖就点拆分为与若图G中有i->j,则i->j`,结果为原图结点数-现图最大匹配数。
#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>#include <cmath>using namespace std;const int maxn =505;int m,a[maxn],b[maxn],c[maxn],d[maxn];int map[maxn][maxn],check[maxn],linker[maxn];int w[maxn],cap[maxn];bool dfs(int u){for(int v=1;v<=m;v++){if(map[u][v]&&!check[v]){check[v]=1;if(linker[v]==-1||dfs(linker[v])){linker[v]=u;return true;}}}return false;}int hungarian(){int ans=0;memset(linker,-1,sizeof(linker));for(int i=1;i<=m;i++){memset(check,0,sizeof(check));if(dfs(i)) ans++;}return ans;}int main(){int n;scanf("%d",&n);while(n--){scanf("%d",&m);memset(map,0,sizeof(map));for(int i=1;i<=m;i++){int hh,mm;scanf("%d:%d",&hh,&mm);cap[i]=hh*60+mm;scanf("%d%d%d%d",&a[i],&b[i],&c[i],&d[i]);w[i]=abs(a[i]-c[i])+abs(b[i]-d[i]);}for(int i=1;i<=m;i++)for(int j=i+1;j<=m;j++){if(cap[j]-cap[i]>w[i]+abs(c[i]-a[j])+abs(d[i]-b[j]))map[i][j]=1;}printf("%d\n",m-hungarian());}return 0;}
G - Optimal Bus Route Design (UVALive - 3353)
有n个景点,规划几条bus路线使每个景点仅在一个bus路线中,这个路线必须是环。可以看作n个结点的有向图,每个结点仅在一个环中,求满足这个条件最小的所有环的长度和,如果不存在满足条件的情况则输出N。将有向图结点拆分为i与i`看作二分图,二分图是否有完美匹配可以判断此图是否为环或多环利用km求出最小权完美匹配即可得出需要值,如果无法得到完美匹配则输出N。
ps:这里有WA点,输入有重边,而根据km的原理使用领接矩阵的话会导致边的权值被覆盖,我们这里取最大边的情况即可。也可以改作领接表存储
#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>using namespace std;const int maxn = 505;const int INF = 0x3f3f3f3f;int n, nx, ny, w[maxn][maxn];int match[maxn], lx[maxn], ly[maxn], slack[maxn];bool visx[maxn], visy[maxn];bool dfs(int x){visx[x] = true;for(int y = 1; y <= ny; y++){if(visy[y]) continue;int t = lx[x] + ly[y] - w[x][y];if(t == 0){visy[y] = true;if(match[y] == -1 || dfs(match[y])){match[y] = x;return true;}}else if(slack[y] > t){slack[y] = t;}}return false;}void km(){nx = ny = n;memset(match, -1, sizeof(match));memset(ly, 0, sizeof(ly));for(int i = 1; i <= nx; i++){lx[i] = -INF;for(int j = 1; j <= ny; j++){if(w[i][j] > lx[i]) lx[i] = w[i][j];}}for(int x = 1; x <= nx; x++){for(int i = 1; i <= ny; i++){slack[i] = INF;}while(true){memset(visx, false, sizeof(visx));memset(visy, false, sizeof(visy));if(dfs(x)) break;int d = INF;for(int i = 1; i <= ny; i++){if(!visy[i] && d > slack[i]) d = slack[i];}for(int i = 1; i <= nx; i++){if(visx[i]) lx[i] -= d;}for(int i = 1; i <= ny; i++){if(visy[i]) ly[i] += d;else slack[i] -= d;}}}}int main(){while(scanf("%d", &n)){int ans=0;int flag=0;if(n==0) break;for(int i=1;i<=n;i++)//重边考虑for(int j=1;j<=n;j++)w[i][j]=-INF;for(int i=1;i<=n;i++){int a,d;while(scanf("%d",&a)){if(a==0) break;scanf("%d",&d);w[i][a]=max(w[i][a],-d);//chongbian}}km();for(int i=1;i<=n;i++){if(w[match[i]][i]==-INF){flag=1;break;}ans+=w[match[i]][i];}if(flag) printf("N\n");else printf("%d\n",-ans);}return 0;}
补题:
H - Cat vs. Dog (UVALive - 4288)
有c只猫d只狗,u个投票人,投票人中分为猫党和狗党,他们有keep票和throw out票一张,给出每个人赞同和反对的猫狗。使能满足的投票人的最大个数。这里一眼建图,将每个投票人的赞同的动物和反对动物连边,求最大独立集,但这求的是能留下来的最大动物数。这里应该将投票人作为结点,如果投票人之间有冲突连边,求最大独立集即是最后的解。
#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>#include <string>using namespace std;const int maxn = 505;const int inf =0x3f3f3f3f;int t,c,d,u;int map[maxn][maxn],check[maxn],linker[maxn];string a[maxn],b[maxn];bool dfs(int x){for(int v=1;v<=u;v++){if(map[x][v]&&!check[v]){check[v]=1;if(linker[v]==-1||dfs(linker[v])){linker[v]=x;return true;}}}return false;}int hungarian(){int ans=0;memset(linker,-1,sizeof(linker));for(int i=1;i<=u;i++){memset(check,0,sizeof(check));if(dfs(i)) ans++;}return ans;}int main(){scanf("%d",&t);while(t--){scanf("%d%d%d",&c,&d,&u);memset(map,0,sizeof(map));for(int i=1;i<=u;i++)cin>>a[i]>>b[i];for(int i=1;i<=u;i++)for(int j=1;j<=u;j++)if(a[i]==b[j]||b[i]==a[j])map[i][j]=map[j][i]=1;printf("%d\n",u-hungarian()/2);}return 0;}
I - The Great Wall Game (UVALive - 3276)
题意比较简单nxn的方格有n个棋子,求用最小的步数将其摆放为横或纵或斜连着的n颗。暴力km求最小权匹配,将棋子看作结点,棋子之间移动曼哈顿距离看作边权。复杂度O(),具体看代码注释。
#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>#include <cmath>using namespace std;const int maxn =20;const int inf =0x3f3f3f3f;int n,w[maxn][maxn];int nx,ny,match[maxn],lx[maxn],ly[maxn],slack[maxn];bool visx[maxn],visy[maxn];int xx[maxn],yy[maxn];bool dfs(int x){visx[x]=true;for(int y=1;y<=ny;y++){if(visy[y]) continue;int t=lx[x]+ly[y]-w[x][y];if(t==0){visy[y]=true;if(match[y]==-1||dfs(match[y])){match[y]=x;return true;}}else if(slack[y]>t)slack[y]=t;}return false;}int km(){memset(match,-1,sizeof(match));memset(ly,0,sizeof(ly));for(int i=1;i<=nx;i++){lx[i]=-inf;for(int j=1;j<=ny;j++)if(w[i][j]>lx[i]) lx[i]=w[i][j];}for(int x=1;x<=nx;x++){for(int i=1;i<=ny;i++)slack[i]=inf;while(1){memset(visx,0,sizeof(visx));memset(visy,0,sizeof(visy));if(dfs(x)) break;int d=inf;for(int i=1;i<=ny;i++)if(!visy[i]&&d>slack[i]) d=slack[i];for(int i=1;i<=nx;i++)if(visx[i]) lx[i]-=d;for(int i=1;i<=ny;i++){if(visy[i]) ly[i]+=d;else slack[i]-=d;}}}int res=0;for(int i=1;i<=ny;i++)res+=w[match[i]][i];return -res;}void init(){memset(w,0,sizeof(w));}int main(){int cnt=0;while(~scanf("%d",&n)){int ans=inf;if(n==0) break;nx=ny=n;for(int i=1;i<=n;i++)scanf("%d%d",&xx[i],&yy[i]);for(int i=1;i<=n;i++)//讨论移动行{init();for(int j=1;j<=n;j++)//枚举棋子for(int k=1;k<=n;k++) //讨论移动列{w[j][k]=abs(xx[j]-i)+abs(yy[j]-k);w[j][k]=-w[j][k];}ans=min(ans,km());}for(int i=1;i<=n;i++)//讨论移动列{init();for(int j=1;j<=n;j++)//枚举棋子for(int k=1;k<=n;k++)//讨论移动行{w[j][k]=abs(xx[j]-k)+abs(yy[j]-i);w[j][k]=-w[j][k];}ans=min(ans,km());}init();for(int j=1;j<=n;j++)//讨论正斜边for(int k=1;k<=n;k++){w[j][k]=abs(xx[j]-k)+abs(yy[j]-k);w[j][k]=-w[j][k];}ans=min(ans,km());init();for(int j=1;j<=n;j++)//讨论负斜边for(int k=1;k<=n;k++){w[j][k]=abs(xx[j]-k)+abs(yy[j]-(n-k+1));w[j][k]=-w[j][k];}ans=min(ans,km());printf("Board %d: %d moves required.\n\n",++cnt,ans);}return 0;}