泰山挑夫【三】解題報告

http://acm.hust.edu.cn/vjudge/contest/view.action?cid=29405#problem/A

這場比賽可能題目有點難,只出了一個水題,鬱悶啊!  另外還可以做的題有這麼幾個!

H題,告訴你多組比賽,還有勝敗情況,看有多少人能夠確定最終名次!  當時想到拓撲排序,但是無論正着拍,逆向拍都不對,我後來又改成迭代,但是仍然不對,鬱悶啊!

自己想了測試數據,想了好幾個都沒測出問題了,後來還是老卜告訴我一組數據,但是沒有對題目理解透徹,即便知道了測試數據仍然沒有做對!  後來想想也是,怎麼才能把一個人的最終名次確定了呢, 那就是在總人數爲n的前提下,贏他的有m人,輸他的有k人,m+k=n-1那麼他的名詞就一定能確定了呢!   當然,很多關係是能傳遞的,1》2,2》3  那麼1》3  贏不一定是直接贏,簡介贏也行,那麼就需要用floyd處理一下……  然後記錄入度和出度就好,每找出一個人就把這個人從總人數中去掉,然後把他在人數中的關係也都去掉,這樣再次迭代下去,直到沒有不能更新答案爲止……


G 這是到DP,很明顯的DP,而且是純粹的DP,枉我搞DP快一暑假了,仍然一點頭緒沒有,後來才知道自己陷入誤區了

先介紹下題意吧,n個時間1W,然後給每個時間能跑的距離,沒跑動一次疲勞就上升一個值,最多到M,如果休息的話,每停一次疲勞下降1,但是疲勞下降的時候不能終止,必須降到0才能開始下一次運動……   疲勞從0開始,但是最後在n時刻,疲勞必須要降到0,否則很難做下一次訓練,休息不好時不行的!


當時各種想法都出來了,就是沒有一個靠譜的,要不就是不知道該記錄那個狀態,要不就是沒法記錄狀態,再就是沒法轉移,對狀態把握不清楚,當時想用dp[i][0/1]的形式,就是每個點選還是不選的形式,但是後來怎麼弄都不對,因爲還有個疲勞值不超過m限制在這裏…… 而且dp裏面記錄跑的最遠的距離或者最小疲勞值都不對啊……   知道看到別人的講解才恍然大悟。我可以這樣開dp[n][m] 的形式啊,最終需要的結果就是時間爲n,疲勞爲0的狀態

當然這裏轉移的時候需要注意,dp[i][0]可能有上一個的1疲勞轉移過來,也可能由前兩個的2疲勞轉移過來,或者前k個k疲勞轉移過來……   這樣就很清楚了,因爲他要連續休息知道j個疲勞回覆到0啊!


F題是個圖論題,就是給一些邊,然後建圖,類似於求最短路一樣,把節點1和節點n連接起來,然是有k條邊是不用自己花錢的,剩下的邊由jhon自己掏錢,花費是,最長的一條的長度!  當時沒讀明白題,到現在其實也不是很明白,我以爲k是長度,而不是邊的數目!  

這樣題意明確之後就應該知道,儘量在求解修路的時候使得長的邊算到k裏面,剩下的自己搞;可是把那些邊算到k裏面不花錢,那些需要算到Jhon的頭上,確實不好搞啊,怎麼辦呢?  難道枚舉,爆搜,貪心,orDP ,而且不知道怎麼判斷是否是用那些邊不用那些邊; 後來看了別人的講解有點明白了,二分啊,二分長度,看至少有k條邊大於L,然後用這些邊修路看能否修好,怎麼算是修好了,而且恰好用了那幾條選中的邊呢? 修改權值,超過L的設成1,其他的弄成0; 若如果修好的最短路答案恰恰是k,那麼正好全用了……  這樣來驗證……   通過修改權值的辦法;這樣在最短路里面把無關的邊設成0消除其影響!

當然,貌似也有用DP+搜所做出來的,貌似思路是枚舉這條邊看能否歸0,就是讓通訊公司掏錢而不是讓可憐Jhon掏錢,然後DP記錄最優值,減少枚舉搜索次數http://hi.baidu.com/lxxstar1226/item/d036b2e45b534a334cdcaf65


B題是大水題。披着計算幾何的外衣,而且裏面的詞彙全是計算幾何裏面碰到的詞彙。要不是哥剛做過幾個計算幾何,連題目大意都讀不懂啊,給你一個矩形,然後看最小的等腰三角形把矩形套起來,使矩形上的點要麼在內部要麼在邊上上,而且等腰三角形的兩邊已經確定了,和XX重合,花花圖,三角形相似,直接利用題目中所給的值就好,都不用計算的,只不過座標得處理下,分類討論加上正負號就好了……




G題:

G題:
#include <iostream>
#include<cstdio>
using namespace std;
#define N 100000
#define M 500
int dp[N+10][M+10];
int cnt[N+10][2];
int d[N+10];
int sum[N+10];
int n,m;
int main()
{
///該死的DP,做了這麼久的DP還是別難住了,悲劇啊,都不好意思說自己是搞DP的,
///做DP貌似有點盲目了,丟掉了思考,最近做多了dp[i][0/1] 這一位放還是不放的形式忽略了一般的二維數組形式的DP
///忘記了最終狀態是需要時間是n,但是疲勞值是0的最終狀態,因爲每個狀態需要積累疲勞值啊,當時光盲目的開兩個數組
///卻忽略最基本的二維數組的形式啊,哎,忽略了二維數組的形式導致好多狀態沒法記錄啊
    cin>>n>>m;
    for(int i=1; i<=n; ++i)
    {
        scanf("%d",&d[i]);
        sum[i]=sum[i-1]+d[i];
    }

    dp[1][0]=0;
    dp[1][1]=d[1];
    for(int i=1;i<=m;++i)
    {
        dp[1][i]=d[1];
    }

    for(int i=2;i<=n;++i)
    {
        dp[i][0]=max(dp[i-1][0],dp[i][0]);
        for(int j=1;j<=m;++j)
        {
            dp[i][j]=max(dp[i-1][j-1]+d[i],dp[i][j]);
            ///下面這句有點難理解,爲什麼呢,就是說如果i時消耗爲0的話,那麼消耗爲爲j的就得從i-j開始轉移,
            ///因爲這段必須是是連續的休息時間
            if(i>j)dp[i][0]=max(dp[i-j][j],dp[i][0]);
        }

    }
    cout<<dp[n][0]<<endl;

    return 0;
}





H題:
#include <iostream>
#include<cstring>
#include<stack>
#include<algorithm>
using namespace std;

int n,m;


int cnt2[110];///表示出度
int cnt1[110];///表示入度
int vis[110];
int map[110][110];
int u,v,ans;
int a1=0,a2=0;

void floyd()
{///floyd一邊是關係傳遞……
    for(int k=1; k<=n; ++k)
    {
        for(int i=1; i<=n; ++i)
        {
            for(int j=1; j<=n; ++j)
            {
                if(map[i][k]&&map[k][j])map[i][j]=1;
            }
        }
    }
}

void slove()
{
    int t=n;
    int tmp=-1;
    while(tmp!=t)
    {///迭代的過程,直到不能找到爲止
        tmp=t;
        for(int i=1;i<=n;++i)
        {
            if(!vis[i]&&cnt1[i]+cnt2[i]==t-1)
            {///如何贏的人輸的人還有他自己是總人數的話,那麼一定能確定他的名詞了
                ans++;vis[i]=1;
                t--;
                for(int j=1;j<=n;++j)
                {
                    if(map[j][i])cnt2[j]--;
                    if(map[i][j])cnt1[j]--;
                }
            }
        }
    }
}
void init()
{
    memset(map,0,sizeof(map));
    memset(cnt1,0,sizeof(cnt1));
    memset(cnt2,0,sizeof(cnt2));
    memset(vis,0,sizeof(vis));
    ans=0;
}
int main()
{///如何根據一個人的比賽情況確定排名呢,那就是根據他贏的人數還有輸的人數!!!   就是入度和出度
///當年usaco月賽上曾經有一個題是將帶編號打亂編號在重新排列,告訴你每個牛左邊有多少個比他編號小的,然後輸出
///每個牛的編號,怎麼辦呢?  倒着推啊,比如說全班有多少個人比他成績高,那麼他是倒數第幾不就很好確定了嗎
///可惜當時沒想明白,而且怎麼找數據就是沒有找出來,後來老卜給了我一組  1,2都打敗3,3打敗4,5
///那麼3號的名詞一定是確定了,  我原來是正向一邊拓撲排序,逆向一邊拓撲排序;光想着從兩頭找了,沒有想到中間也是有可能的



    cin>>n>>m;
    for(int i=0; i<m; ++i)
    {
        cin>>u>>v;
        map[u][v]=1;
    }
    floyd();///傳遞

    ///統計入度出度
    for(int i=1; i<=n; ++i)
    {
        for(int j=1; j<=n; ++j)
            if(map[i][j]&&i!=j)cnt1[j]++,cnt2[i]++;;
    }
    slove();
    cout << ans << endl;
    return 0;
}


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