@darkproject
2018-02-14T22:50:13.000000Z
字数 9758
阅读 958
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;
else
slack[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;
}