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、有問題討論,確定能寫的時候其他人可以攻新題。

大概就是這些,這幾次比賽的過題數量都還可以,但是罰時都比較不利,以後應該注意。畢竟賽場上金銀之差往往就是因爲罰時。


加油。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章