2009成都网络赛预赛 1008 1009 1010 HDU2435 HDU2436 HDU2437

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、有问题讨论,确定能写的时候其他人可以攻新题。

大概就是这些,这几次比赛的过题数量都还可以,但是罚时都比较不利,以后应该注意。毕竟赛场上金银之差往往就是因为罚时。


加油。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值