There is a war解题报告【比赛1008 / HDU2435】
题目地址:http://acm.hdu.edu.cn/showproblem.php?pid=2435
题目大意:有N个城市(编号从1开始),连有一些桥(有向边),破坏每条桥需要一个代价(有向边上的权)。
Country N要用最小的代价(下面记为MIN_COST)破坏掉一些桥,让Country 1无法到达Country N。
在Country N的摧毁行动前,Country 1可以先选择一座桥把这座桥变成无法拆除的状态,或者新建一座无法拆除的桥,不过这两种桥的端点不能是城市1和城市N。Country 1也可以对桥不作任何改动。
Country 1的目标是,使Country N的MIN_COST最大。
解题思路:首先是最暴力的方法,每次在2..N-1中选择两个端点,搭建或者改造原有桥梁(下面称为“造无敌桥”),计算最小割。但是这样显然是会TLE的。
接下来考虑,造无敌桥的两个端点必然是分别在最小割的两端。如果有多个最小割,选择其中任意一个最小割都可以,这一点我不知道怎么证明,不过自己画几个图可以想象一下。
那么优化可以先求一个全局最大流,从源点1出发进行一次DFS,可以得到一个最小割的分割结果INIT_FLOW。不妨记作“源集”和“汇集”,“源集”到“汇集”之间的边就是最小割,流量等于容量。
造无敌桥的端点一头在“源集”,一头在“汇集”,枚举即可,那么怎么计算造桥之后的MIN_COST呢?
其实不需要重新计算一次全局的网络流。计算INIT_FLOW之后可以得到一张残余网络c,在c上Country 1和Country N已经不联通了。而加上无敌桥梁(i, j)后,Country1和Country N可能重新联通。在残余网络c上计算F1=max_flow(1, i), F2=max_flow(j, n),分别是破坏1到i,j到n之间联通关系所需要的代价,INIT_FLOW+min(F1, F2)就是造无敌桥之后的MIN_COST。而这里的F1,F2在得到“源集”和“汇集”之后是可以预处理一下存起来方便枚举的。
源代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn=110;
int usr_min(int x, int y){if (x<y) return x; return y;}
int EdmondsKarp(int g[][maxn],int n,int src,int dst,int f[][maxn])
{
int d[maxn]={},p[maxn],r[maxn]={},c[maxn]={n};
int flow=0,delta=INT_MAX,h[maxn],v=src;
memset(f,0,sizeof(*f)*n);
p[src]=-1;
while(d[src]<n)
{
bool flag=true;;
h[v]=delta;
for(int i=r[v];i<n;i++)
if(g[v][i]>f[v][i]&&d[v]==d[i]+1)
{
if(delta>g[v][i]-f[v][i]) delta=g[v][i]-f[v][i];
flag=false;
r[v]=i;p[i]=v;v=i;
break;
}
if(flag)
{
int t=n-1;
for(int i=0;i<n;i++)
if(t>d[i]&&f[v][i]<g[v][i]) {t=d[i];r[v]=i;}
if(--c[d[v]]==0) break;
d[v]=t+1;
c[d[v]]++;
if(p[v]!=-1) {v=p[v];delta=h[v];}
}
else if(v==dst)
{
flow+=delta;
while(p[v]!=-1)
{
f[v][p[v]]-=delta;
f[p[v]][v]+=delta;
v=p[v];
}
delta=INT_MAX;
}
}
return flow;
}
void dfs(int k, int n, bool v[], int c[][maxn]) //找源集合
{
v[k]=true;
for (int i=0; i<n; i++)
{
if (!v[i] && c[k][i])
dfs(i, n, v, c);
}
}
int main()
{
int cs, n, m, a, b, cc;
int init; //初始流
static int f1[maxn], f2[maxn];
int ans, tmp;
static int g[maxn][maxn];
static int f[maxn][maxn];
static int c[maxn][maxn];
static int ff[maxn][maxn];
bool v[maxn];
int src[maxn], dst[maxn];
int n1, n2;
scanf("%d", &cs);
while (cs--)
{
scanf("%d%d", &n, &m);
if (m==0){printf("0\n");continue;}
memset(g, 0, sizeof(g));
for (int i=0; i<m; i++)
{
scanf("%d%d%d", &a, &b, &cc);
g[a-1][b-1]=cc;
}
ans=init=EdmondsKarp(g, n, 0, n-1, f); //最小割
for (int i=0; i<n; i++) //得残余网络
for (int j=0; j<n; j++)
{
c[i][j]=g[i][j]-f[i][j];
}
memset(v, 0, sizeof(v));
dfs(0, n, v, c); //源集
n1=n2=0;
for (int i=1; i<n-1; i++) //源集汇集
if (v[i]) src[n1++]=i;
else dst[n2++]=i;
for (int i=0; i<n1; i++)
f1[i]=EdmondsKarp(c, n, 0, src[i], f);
for (int i=0; i<n2; i++)
f2[i]=EdmondsKarp(c, n, dst[i], n-1, f);
for (int i=0; i<n1; i++)
for (int j=0; j<n2; j++)
{
tmp=usr_min(f1[i], f2[j])+init;
if (tmp>ans) ans=tmp;
}
printf("%d\n", ans);
}
return 0;
}
Collision Detection解题报告【比赛1009 / HDU2436】
题目地址:
http://acm.hdu.edu.cn/showproblem.php?pid=2436
题目大意:
求长方体和球是否相交(相切看做相交)长方体的每条边和坐标轴平行
解题思路:
记圆心为(x0, y0, z0)。容易想到,计算长方体上离圆心最近一点(x', y', z')到圆心的距离dismin就可以判断出YES和NO。
由于“长方体的每条边和坐标轴平行”(这个条件非常重要),可以知道长方体上任意一点左边(x, y, z)满足xmin<=x<=xmax, y,z相同。而xmin, xmax等可以直接由长方体的八个顶点坐标得到。
从dismin^2=(x0-x)^2+(y0-y)^2+(z0-z)^2可以知道,要找到(x', y', z')这一点,其实xyz三个那种歌方向上是完全独立的,分别在xmin<=x<=xmax, ymin<=y<=ymax, zmin<=z<=zmax中间选取合适的x, y, z使(x0-x)^2、(y0-y)^2和(z0-z)^2都最小就可以了。
根本不需要传说中的计算几何啊……
源代码:
#include <cstdio>
#include <cmath>
using namespace std;
typedef struct {
double x, y, z;
}POINT;
double dis(double x1, double y1, double z1, double x2, double y2, double z2)
{
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)+(z1-z2)*(z1-z2));
}
int main()
{
int cs;
POINT cube[10], ball;
double r, mindis;
double xmax, ymax, zmax, xmin, ymin, zmin, x, y, z;
scanf("%d", &cs);
while (cs--)
{
for (int i=0; i<8; i++)
scanf("%lf%lf%lf", &cube[i].x, &cube[i].y, &cube[i].z);
scanf("%lf%lf%lf%lf", &ball.x, &ball.y, &ball.z, &r);
xmin=cube[0].x, xmax=cube[0].x;
ymin=cube[0].y, ymax=cube[0].y;
zmin=cube[0].z, zmax=cube[0].z;
for (int i=1; i<8; i++)
{
if (cube[i].x<xmin) xmin=cube[i].x;
if (cube[i].x>xmax) xmax=cube[i].x;
if (cube[i].y<ymin) ymin=cube[i].y;
if (cube[i].y>ymax) ymax=cube[i].y;
if (cube[i].z<zmin) zmin=cube[i].z;
if (cube[i].z>zmax) zmax=cube[i].z;
}
if (ball.x<xmin) x=xmin;
else if (ball.x>xmax) x=xmax;
else x=ball.x;
if (ball.y<ymin) y=ymin;
else if (ball.y>ymax) y=ymax;
else y=ball.y;
if (ball.z<zmin) z=zmin;
else if (ball.z>zmax) z=zmax;
else z=ball.z;
mindis=dis(x, y, z, ball.x, ball.y, ball.z);
if (mindis>r)
printf("No\n");
else
printf("Yes\n");
}
return 0;
}
Jerboas解题报告【比赛1010 / HDU2437】
题目地址:
http://acm.hdu.edu.cn/showproblem.php?pid=2437
题目大意:
某种鸟有两种住所P和T,给出鸟的起点,鸟的终点可以是任何一种P住所。这只鸟希望到终点时的总路程是K的倍数,问最近满足要求的住所是哪一个,路径多少。
解题思路:
感谢CBX测试了一下数据里头并没有重边,于是直接DFS。用数组a[i][j]=b表示从起点到i点,路程mod K为j的最短路为b。这样可以有一个剪枝。
源代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 1010
#define maxm 50000
const int INF=(1<<29);
struct edge {int v,w,next;};
int n, m, s, k;
int res, dst;
int g[maxn];
edge e[maxm];
char str[2048];
bool v[maxn];
int a[maxn][maxn];
void dfs(int now, int len)
{
// printf("visit %d\n", now);
if (str[now]=='P' && len%k==0)
{
if (len<res || (len==res && now<dst)) {res=len; dst=now;}
return;
}
for (int i=g[now]; i!=-1; i=e[i].next)
{
if (a[e[i].v][(len+e[i].w)%k]==-1 || len+e[i].w<a[e[i].v][(len+e[i].w)%k])
{
a[e[i].v][(len+e[i].w)%k]=len+e[i].w;
dfs(e[i].v, len+e[i].w);
}
}
}
int main()
{
int cs, aa, bb, cc;
scanf("%d", &cs);
for (int css=1; css<=cs; css++)
{
//init
scanf("%d%d%d%d", &n, &m, &s, &k);
scanf("%s", str);
memset(g, 255, sizeof(g));
for (int i=0, j=0; i<m; i++)
{
scanf("%d%d%d", &aa, &bb, &cc);
aa--; bb--;
e[j].v=bb; e[j].w=cc; e[j].next=g[aa]; g[aa]=j++;
}
memset(a, 255, sizeof(a));
res=INF;
a[s-1][0]=0;
dfs(s-1, 0);
if (res==INF)
printf("Case %d: -1 -1\n", css);
else
printf("Case %d: %d %d\n", css, res, dst+1);
}
while (1);
return 0;
}
赛后个人总结:
1009:当时直接想到复杂的计算几何去了。一直到在纸上画了图才意识到“长方体的每条边和坐标轴平行”这个重要条件。AC的速度不够,好像还WA了一次当时考虑不够周全。以后做计算几何题的时候还是应该自己多写几个数据测一下。
1008:最近几场多校里最大流最小割用了很多次。这道题需要对增广路有比较清晰的认识。这一种加边的方式和优化的方式确实值得想一想,以后遇上了就没问题了。写的时候对最小割不唯一的时候“源集”和“汇集”的划分方式不是很确定,还好过了。
1010:最早CBX的思路是拓补排序然后利用那个a数组搞,但是我写了一半的时候发现可能时间上不是很允许,然后想到了DFS。
总体上来讲今天过题的准确率不是很高,有5次罚时,以后应该避免。
赛后队伍总结:
队伍合作的感觉总体还是应该不错的,不过策略还有待慢慢磨合,也期待大家碰面以后的训练。
1、开场第一道题太晚开始写,对先对哪题发起进攻选择太慢,这样对最后的总罚时也会比较不利。个人感觉开场如果没有直接发现水题应该跟风做第一道题,如果比别人晚20分钟开始写其实也相当于一次WA了。
2、做之前思路清晰再开始写,如果思路不清晰宁愿不写。在有三台机子的时候,一旦开始写了以后不要轻易换题,这在时间上也是很不利的。除非是一直WA一直WA调不出来再放弃。
3、以后一起写程序的时候,拿一张大纸专门记录每到题的情况,再拿一些纸,看过题的人稍微详细地写一下题意。
4、有问题讨论,确定能写的时候其他人可以攻新题。
大概就是这些,这几次比赛的过题数量都还可以,但是罚时都比较不利,以后应该注意。毕竟赛场上金银之差往往就是因为罚时。
加油。