1. 程式人生 > >“Wishare杯”南郵第八屆大學生程式設計競賽之現場決賽 題解報告

“Wishare杯”南郵第八屆大學生程式設計競賽之現場決賽 題解報告

A.爆炸吧,現充 (紅)

時間限制:1000ms           記憶體限制:65536K

題目描述:

a協有部分脫團分子,日復一日,年復一年地進行秀恩愛虐狗行為,對其他成員持續造成著精神傷害。Kojimai君表示在異端分子長期慘無人道的精神攻擊下,早早的患上了少年痴呆症。為了應對這一症狀,不得不經常把日常瑣事記錄下來,時間一長整本筆記本都記完了,他現在好奇自己一共記下了多少字,已知筆記本共n頁,每頁m行,因為心理因素,他排斥偶數頁號,所以只在奇數頁號的頁面寫字,又因為痴呆他在奇數頁的第i行都只寫i個字(1 <= i <= m)。

輸入:

第一行為一個整數T代表資料組數 T < 10,接下來T行,每行兩個數字n和m,均為小於1000的正整數。

輸出:

Kojimai君記下的字數。

樣例輸入:

2

3 3

2 4

樣例輸出:

12

10

題目分析:這個不會,就比較尷尬了

#include <cstdio>

int main()
{
    int T;
    scanf("%d", &T);
    while(T --)
    {
        int n, m, ans = 0;
        scanf("%d %d", &n, &m);
        for(int i = 1; i <= n; i++)
            if(i % 2)
                ans += m * (m + 1) / 2;
        printf("%d\n", ans);
    }
}


B.漢諾塔最多步數 (綠)

時間限制:1000ms           記憶體限制:65536K

題目描述:

漢諾塔是一個很經典的遊戲,它有A,B,C三個柱子,目標是把A上面的n個盤子一起移動上B或C任意一個柱子上,每次移動只能移動一個盤子從一個柱子到另一個柱子上,並且移動的時候不能把大的盤子放到小的盤子上。我們的問題是:如何用最多步數完成這個遊戲,當然為了避免無限迴圈,我們只允許每個局面最多出現一次。

輸入:

輸入第一行為一個整數T(T<=1000)代表資料組數,每組資料只有一個數字n(1<=n<=38),表示一共有n個盤子。

輸出:

輸出為一個數字,表示最多步數(輸出保證不會超過int64的最大值)。

樣例輸入:

2

1

2

樣例輸出:

2

8

題目分析:假設我們要全部移動到C上,把前n-1個盤子從A柱移到C柱,把第n個判移到B柱,又把那n-1個從C柱移回A柱,再把第n個移到C柱,最後把n-1個移到C柱,為什麼這樣不會出現重複的狀態呢?因為我們假設前n-1個盤子的移動過程中沒有重複狀態,而三次對n-1的呼叫時整個狀態由於第n個盤子的位置不同而不同。移動的次數是3^n-1。因為設n個盤子的移動次數為f(n),根據以上描述可以得到f(n) = 3 * f(n - 1) + 2,f(0) = 0,則可推出f(n) = 3^n - 1,為什麼這樣的操作步數是最多?顯然總的狀態數只有3^n個。

#include <cstdio>
#define ll long long

int main()
{
    int T, n;
    scanf("%d", &T);
    while(T --)
    {
        ll ans = 1ll;
        scanf("%d", &n);
        for(int i = 1; i <= n; i++)
            ans *= 3ll;
        printf("%I64d\n", ans - 1ll);
    }
}


C. 絕望的KSS(深藍)

時間限制:1000ms           記憶體限制:65536K

題目描述:

         你經歷過絕望嗎?

         KSS經歷過,當年KSS一人困在圓形孤島上。因為KSS是個強迫症,所以他一直呆在這個圓形孤島的圓心O處。

         今日,KSS終於等來了遠方的客(救)人(星),這位客人現在位於E點,KSS使用他的超能力獲得了客人與他的距離(比如使用QQ),但是這位客人江湖人稱PDF,有個奇怪的癖好,他要KSS回答一個問題。他射出一道鐳射,在進入這個圓島和離開這個圓島的時候分別留下了一個印記D和B。PDF要KSS求出PDF距離D的距離和距離B的距離,否則就不會去營救KSS。

         KSS祈禱上蒼,請求援助,上蒼說:我將EO相連,交圓於C,A兩點,我能告訴你AB的距離d,剩下的,你自求多福吧。

         KSS絕望了,你能幫幫他嗎?當然KSS也不是吃白飯的,被困多日的他早已使用超能力獲得了圓島的半徑r。 (PDF表示讓他救KSS,其實他是拒絕的)

輸入:

         第一行T(T<=100),是測試資料的數量。以下T行,每行三個數,分別為AB的距離d,圓島半徑r,AE距離。所有資料在double範圍內,d<sqrt(2)*r,AE>2*r。

輸出:

         每個樣例輸出一行,輸出ED和EB的距離,用空格隔開,答案保留兩位小數。

樣例輸入:

2

1.8365856625621313  94.70069211062373  298.415845341548

7.187623220586373  10.91566993868142  44.94354413616857

樣例輸出:

109.02 298.40

24.09 43.11

題目分析:首先有一個結論,圓的內切四邊形對角互補,那麼就可以得到三角形ECD相似於三角形EBA,則DE/AE=CE/BE=CD/AB,然後餘弦定理cos角BAE=(AB^2+AO^2-BO^2)/(2ABAO)=(AB^2+AE^2-BE^2)/2ABAE,兩式聯立,即可解得DE和BE,這題不用相似也可以做,這裡就不說了。

#include <cstdio>
#include <cmath>

int main()
{
    int T, n;
    scanf("%d", &T);
    while(T --)
    {
        double AB, R, AE;
        scanf("%lf %lf %lf", &AB, &R, &AE);
        double BE = sqrt(AB * AB + AE * AE - AB * AB * AE / R);
        double DE = (AE - 2.0 * R) * AE / BE;
        printf("%.2f %.2f\n", DE, BE);
    }
}


D. 有趣的編碼 (紫)

時間限制:4000ms           記憶體限制:32768 K

題目描述:

有種有趣的編碼方式,它不僅被廣泛應用於各工程領域,還經常出現在廣大網際網路公司的筆試面試中,現規定如下幾個規則:

1.第一個碼為全0。

2.任意一個碼只包含0和1兩個數字。

3.任意兩個相鄰的碼只有一位數字不同。

4.第一和最後一個碼也只有一位數字不同。

5.一個n位碼組一共2的n次方個編碼且任意兩個編碼不同

6.在滿足前5點的條件下,若當前有多個符合條件的編碼則選字典序最小的那個,比如2位的碼組是

0 0

0 1

1 1

1 0

而不是

0 0

1 0

1 1

0 1

學過數電的同學都知道是什麼了吧?

現在請你找出n位碼組的第k個編碼。

輸入:

第一行為一個整數T代表資料的組數,每組資料包含兩個數字n和k ()。

輸出:

對於每組資料輸出其n位碼組的第k個編碼。

樣例輸入:

3

1 1

3 5

4 8

樣例輸出:

0

1 1 0

0 1 0 0

題目分析:這種編碼其實叫格雷碼,原題是直接列印全部的n位格雷碼的,輸出資料太多就改成第k個了,可以直接用十位數的2進位制和格雷碼的關係搞,這個百度格雷碼即可,當然也可以一個一個推到第k個,遞推基本就是找規律了,因為有對稱的性質

#include <cstdio>  
#define judge (i % (1 << j)) < ((1 << j) >> 1)  
int main()  
{
    int n, k, T;  
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d %d", &n, &k);  
        int cnt = 0;
        for(int i = 0; i < (1 << n); i++)  
        {
            cnt ++;
            if(cnt == k)
            {
                for(int j = n; j >= 1; j--)
                {
                    if((i / (1 << j)) & 1)  
                        printf("%d%c", judge ? 1 : 0, j == 1 ? '\n' : ' ');  
                    else  
                        printf("%d%c", judge ? 0 : 1, j == 1 ? '\n' : ' ');  
                }
            }
        }
    }  
}  



E. 強大的JP (黃)

時間限制:1000ms           記憶體限制:65536K

題目描述:

         JP體內有陰陽之力,陰之力的數量為x,陽之力的數量為y,他們都是在1到k之間的正整數(即1<=x<=k,1<=y<=k),為了控制體內的陰陽平衡,|x^2-xy-y^2|必須等於1,他的實力H=x^2+y^2。如今他面臨一場世紀之戰,他要爆發出最大的實力,也就是說要使得x^2+y^2最大,請告訴他他要如何控制體內的陰陽之力,即陰之力和陽之力的值需要是多少?

輸入:

第一行為一個整數T(T<=100),表示有T組資料。每組資料為一個正整數n,這裡0<n<1018

輸出:

每組資料輸出一個整數m表示計算結果。

樣例輸入:

2

2

8

樣例輸出:

2 1

8 5


題目分析:對於能滿足那個式子的任意x,y,打表找規律即可。

#include <cstdio>
#define ll long long
int const MAX = 105;
ll const INF = 1e18;
ll y[MAX], t[MAX], x[MAX];
int cnt;

void pre()
{
    y[0] = 1; 
    y[1] = 2;
    t[0] = 3; 
    t[1] = 4;
    x[0] = (y[0] + t[0]) >> 1;
    x[1] = (y[1] + t[1]) >> 1;
    cnt = 2;
    while(true)
    {
        y[cnt] = y[cnt - 1] + y[cnt - 2];
        if(y[cnt] > INF)
            break;
        t[cnt] = t[cnt - 1] + t[cnt - 2];
        x[cnt] = (y[cnt] + t[cnt]) >> 1;
        cnt ++;
    }
}

int main()
{
    pre();
    int T;
    scanf("%d",&T);
    while(T--)
    {
        ll k, ansx, ansy;
        scanf("%I64d", &k);
        for(int i = 0; i < cnt; i++)
        {
            if(y[i] <= k && x[i] <= k)
            {
                ansx = x[i];
                ansy = y[i];
            }
            else
                break;
        }
        printf("%I64d %I64d\n", ansx, ansy);
    }
}


F. 粗心的工作人員(粉)

時間限制:10000ms         記憶體限制:32768K

題目描述:

大牛BJ去某巨頭網際網路公司參加面試,該公司HR不喜歡奇數,因此事先給所有面試者的編號都是偶數,0開始從小到大按順序編號(0,2,4,6,8,…),當然一個編號只能給一個面試者,面試者來到公司首先要去簽到,粗心的工作人員在記錄簽到資訊時竟忘了打空格,於是得到了類似268104012這樣的序列,真正的簽到順序是2,6,8,10,4,0,12。現在已知所有拿到編號將要參加面試的人員都已簽到完畢,沒人缺席!當然工作人員在記錄時還可能把編號輸錯,現在工作人員想知道自己編號有沒有輸錯。

輸入:

第一行為一個整數T (T <= 20)代表資料的組數,每組資料只有一行,即一個數字序列S,S的長度不超過700。(注:S大於300時T不大於10)

輸出:

對於每組資料,若工作人員輸入有錯則輸出一行No,否則輸出Yes。

樣例輸入:

4

122861340

2014228182441001268162622

0

2

樣例輸出:

No

Yes

Yes

No

樣例解釋:

第一個顯然是無解的。

第二個可以將其正確分解。

第三個只有一個0,所以是正確的。

第四個因為我們強調編號是從0開始發的,又因為所有參加面試人員都到齊,所以有2必定有0,但是隻有一個2顯然是錯誤的。

題目分析:這題我出的,坑爹了,題目按道理根據搜尋的方向不同是有多組解的(這裡需要感謝wxy同學不斷的嘗試才讓我意識到這點),所以後來改了,只需要判斷Yes,No即可,不過這並不影響大家過不了這題,因為這題需要很強的剪枝才能過,其實我已經把資料放小且把時限放大了,之前資料更多更大。下面細說這題的每一個剪枝

1.首先根據字串的長度,我們可以求出當前長度下如果有解,最大的那個數是多少,並且在計算過程中也可以去掉一些No的情況,比如一個長度為10的字串,減去個位數的長度即0,2,4,6,8,還剩下5,那麼剩下的都是十位數,顯然長度為5是構成不了若干十位數的

2.既然知道了最大的數字是多少,那麼我們就可以知道從0到最大的那個數字在有解得情況下,0-9這10個數字應該出現的次數,然後和輸入資料得到的次數做比對,如果有出入那顯然是No了

3.搜尋過程中分別列舉1位,2位,3位,要注意列舉的數字必須小於等於最大的那個,且必須是偶數,且之前沒被標記過

4.這是這題最操蛋的剪枝,不過我把這個剪枝的時間放了,就是搜尋前我們可以分別算出個位十位百位分別要搜幾個數字,搜尋時我們可以加三個變數分別代表個位,十位,百位分別已經標記了的數量,然後通過判斷若要搜到解還需的字串長和當前搜尋剩下的長度做比較,如果大於剩下的,那後面的搜尋肯定是無法搜到解的,就可以return了

除了刺激沒別的詞可以形容

#include <cstdio>  
#include <cstring>  
int const MAX = 705;
char s[MAX];  
int num[MAX], tot[10], tot_[10];  
int len, n, GW, SW, BW, ma;  
bool flag, vis[MAX]; 

void DFS(int idx, int cnt, int gw, int sw, int bw) 
{  
    if(flag)   
        return;  
    if((GW - gw) + (SW - sw) * 2 + (BW - bw) * 3 > len - idx + 1) 
        return;  
    if(idx == len && GW == gw && SW == sw && BW == bw)  
    {   
        flag = true;  
        return;  
    }  
    if((gw < GW) && idx < len)
    {
        num[cnt] = s[idx] - '0'; 
        if(!vis[num[cnt]] && !(num[cnt] & 1) && num[cnt] <= ma)  
        {  
            vis[num[cnt]] = true;  
            DFS(idx + 1, cnt + 1, gw + 1, sw, bw);  
            if(flag)  
                return;  
            vis[num[cnt]] = false;  
        }
    }  
    if((sw < SW) && idx < len - 1)  
    {  
        num[cnt] = 10 * (s[idx] - '0') + s[idx + 1] - '0';
        if(num[cnt] <= 2 * (n - 1) && num[cnt] > 9 && !(num[cnt] & 1))  
        {  
            if(!vis[num[cnt]])  
            {  
                vis[num[cnt]] = true;
                DFS(idx + 2, cnt + 1, gw, sw + 1, bw);  
                if(flag)  
                    return;  
                vis[num[cnt]] = false;  
            }  
        }  
    }  
    if((bw < BW) && idx < len - 2)
    {
        num[cnt] = 100 * (s[idx] - '0') + 10 * (s[idx + 1] - '0') + (s[idx + 2] - '0'); 
        if(num[cnt] <= 2 * (n - 1) && num[cnt] > 99 && !(num[cnt] & 1))  
        {  
            if(!vis[num[cnt]])  
            {  
                vis[num[cnt]] = true;
                DFS(idx + 3, cnt + 1, gw, sw, bw + 1);  
                if(flag)  
                    return;  
                vis[num[cnt]] = false;  
            }  
        }  
    } 
}  
  
int main()  
{  
    int T;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%s", s);
        GW = 0;
        BW = 0;
        SW = 0;
        flag = false;  
        memset(vis, false, sizeof(vis));  
        memset(num, 0, sizeof(num));  
        memset(tot, 0, sizeof(tot));
        memset(tot_, 0, sizeof(tot_));
        len = strlen(s);
        for(int i = 0; i < len; i++)
            tot[s[i] - '0'] ++; 
        if(len <= 5)
        {
            n = len;
            GW = n;
        }
        else if(len > 5 && len <= 95)
        {
            if((len - 5) % 2)
            {
                printf("No\n");
                continue;
            }
            else
            {
                n = 5 + (len - 5) / 2;
                GW = 5;
                SW = n - 5;
            }
        }
        else 
        {
            if((len - 95) % 3)
            {
                printf("No\n");
                continue;
            }
            else
            {
                n = 50 + (len - 95) / 3;
                GW = 5;
                SW = 45;
                BW = n - 50;
                bool f = true;
                for(int i = 0; i < 2 * n; i += 2)
                {
                    char tmp[10];
                    sprintf(tmp, "%d", i);
                    for(int j = 0; j < (int) strlen(tmp); j++)
                        tot_[tmp[j] - '0'] ++;
                }
                for(int i = 0; i < 10; i++)
                {
                    if(tot_[i] != tot[i])
                    {
                        f = false;
                        break;
                    }
                }
                if(!f)
                {
                    printf("No\n");
                    continue;
                }
            }
        }
        ma = 2 * (n - 1);
        DFS(0, 0, 0, 0, 0);  
        printf("%s\n", flag ? "Yes" : "No");
    }  
} 


G. 步行者(橘)

時間限制:1000ms           記憶體限制:65536K

題目描述:

         PDF是愛好旅遊的怪咖,現在他的地圖上有n個城市,我們可以編號為0到n-1,城市之間有k條路,這k條路中,不存在任意兩條路的兩個端點相同。也就是說,A,B兩個城市之間,直接相連的最多有一條路。PDF想要知道,從i走過L條路到達j的走法有多少?PDF對旅遊極為熱愛,因此他不介意經過一個城市多次。PDF會詢問你q次。

輸入:

         第一行T,是測試資料的數量T<=10。每組資料第一行為兩個整數n,k。(0<n<=35,0<k<=n*(n-1)/2),以下k行,每行兩個整數a,b,表示編號為a的點和編號為b的點之間有一條邊相連。接下來有一行為兩個整數q,L (0<q<=10000,0<L<=1e7),接下來有q行,每行兩個整數為i,j。

輸出:

對於每次詢問,輸出一行,每行一個整數,為從i到j長度為L的走法的數量對1112取模的值。

樣例輸入:

1

9 17

1 2

2 4

3 4

0 5

0 2

5 7

2 8

1 5

5 8

1 3

2 6

0 6

2 7

3 6

4 6

1 4

6 7

1 1808203

1 2

樣例輸出:

23

題目分析:這個題。。。學過離散的同學都知道有一種矩陣叫鄰接矩陣吧?不知道的百度一下,然後就是裸的矩陣快速冪了

#include <cstdio>  
#include <cstring>  
#include <algorithm>  
using namespace std;  
int const MOD = 1112;  
int n, k;
struct matrix  
{  
    int m[40][40];  
}a;  
  
matrix multiply(matrix x, matrix y)  
{  
    matrix ans;  
    memset(ans.m, 0, sizeof(ans.m));  
    for(int i = 0; i < n; i++)  
        for(int j = 0; j < n; j++)  
            if(x.m[i][j])  
                for(int k = 0; k < n; k++)  
                    ans.m[i][k] = ((ans.m[i][k] % MOD) + ((x.m[i][j] % MOD) * (y.m[j][k] % MOD))) % MOD;  
    return ans;  
}  
  
matrix quickmod(matrix a, int p)  
{  
    matrix ans;  
    memset(ans.m, 0, sizeof(ans.m));  
    for(int i = 0; i < n; i++)  
        ans.m[i][i] = 1;  
    while(p)  
    {  
        if(p & 1)  
            ans = multiply(ans, a);  
        p >>= 1;  
        a = multiply(a, a);  
    }  
    return ans;  
}  
  
int main()  
{     
    int T;
    scanf("%d", &T);
    while(T --)
    {
        matrix a;
        memset(a.m, 0, sizeof(a.m));  
        scanf("%d %d", &n, &k);
        while(k --)
        {
            int x, y;
            scanf("%d %d", &x, &y);
            a.m[x][y] = 1;
            a.m[y][x] = 1;
        }
        int q, L;
        scanf("%d %d", &q, &L);
        matrix ans = quickmod(a, L);
        while(q --)
        {
            int i, j;
            scanf("%d %d", &i, &j);
            printf("%d\n", ans.m[i][j] % MOD);
        }
    }  
}  



H. 自動麻將機(白)

時間限制:1000ms           記憶體限制:65536K

題目描述:

自動機大神愷叔叔自認為掌握所有的自動機,然而雀聖黃大仙不服,表示出一種自動麻將機,贏光愷叔叔所有家當以後請A協所有人吃水林間,被逼瘋的愷叔叔於是找到你來幫忙設計這樣的自動機,麻將是一種棋牌遊戲,具體自動機的功能如下:現給你一副當前的牌,求問當上家出什麼牌的時候可以進行吃、碰、槓、胡四種操作。

其中,

一條至九條表示為A1、A2...A9

一萬至九萬表示為B1、B2...B9

一柄至九柄表示為C1、C2...C9

東風、南風、西風、北風、紅中、發財、白板分別表示為D1、D2...D7 

麻將具體規則如下:

吃:上家打出牌,與手中的牌正好組成一副順子(即相同牌型別,相連三章牌,例如:一條,二條,對方打出三條即可),他就可以吃

碰:上家打出牌,與手中的牌正好組成一副刻子(即相同的牌,例如:手中有兩張北風,對方再次打出一張北風),他就可以碰

槓:其他人打出一張牌,自己手中有三張相同的牌(例如:手中有三張北風,對方再次打出一張北風),即可槓牌 

胡:將自己手中牌和對方打出牌和一起可滿足特定情況,具體胡有三種情況:

普通方法:一副將牌(兩張相同的牌)+四組刻子或者順子(刻子為三張相同的牌,順子為三張相同型別,連續的牌),例如:

  

對對胡:7副對牌(對牌即為兩張相同的牌),例如:

 

十三么:一柄,九柄,一條,九條,一萬,九萬,東風,南風,西風,北風,紅中,發財,白板各一張,其中一種有兩張,例如:

 

輸入:

本題為多組樣例輸入,首先輸入一個整數t(t<= 1010),表示樣例個數,對於每組樣例:

第一行輸入13個字串,表示手中的13張牌。

輸出:

對於每組樣例,輸出四行,第一行輸出所有可以吃的牌,第二行輸出所有可以碰的牌,第三行輸出所有可以槓的牌,第四行輸出所有可以胡的牌,牌與牌之間用空格隔開,樣例與樣例之間輸出一個空行,如果當前操作沒有牌可以完成,則輸出“NONE”,同一操作的牌按照字典序排列。

樣例輸入:

2

A1 A2 A3 A4 A5A6 A7 A8 A9 A9 B1 B2 B3

A1 A1 B8 B8 C8C8 D1 D1 D2 D2 D5 D5 D6

樣例輸出:

A1 A2 A3 A4 A5A6 A7 A8 A9 B1 B2 B3 B4

A9

NONE

A3 A6 A9

NONE

A1 B8 C8 D1 D2D5

NONE

D6

題目分析:聽說這叫大模擬。。。於是直接貼pdf的標程了。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <map>
#include <vector>

using namespace std;

char str[14][3];
bool flag[14];
int mapp[4][10];

int cmp(const void *a, const void *b)
{
    return strcmp((char*)a, (char*)b);
}

bool dfs(int deep)
{
    if(deep == 0)
        return true;
    int i = 0;
    int p1 = -1, p2 = -1, p3 = -1;
    while(i < 14 && !flag[i])
        i++;
    p1 = i++;
    while(i < 14 && (!flag[i] || strcmp(str[p1], str[i]) != 0))
        i++;
    p2 = i++;
    while(i < 14 && (!flag[i] || strcmp(str[p1], str[i]) != 0))
        i++;
    p3 = i;
    if(p3 < 14)
    {
        flag[p1] = false;
        flag[p2] = false;
        flag[p3] = false;
        if(dfs(deep - 3))
            return true;
        flag[p1] = true;
        flag[p2] = true;
        flag[p3] = true;
    }
    if(str[p1][0] != 'D' && str[p1][1] >= '1' && str[p1][1] <= '7')
    {
        char ch[3];
        strcpy(ch, str[p1]);
        ch[1]++;
        i = p1 + 1;
        while(i < 14 && (!flag[i] || strcmp(str[i], ch) != 0))
            i++;
        p2 = i++;
        ch[1]++;
        while(i < 14 && (!flag[i] || strcmp(str[i], ch) != 0))
            i++;
        p3 = i;
        if(p3 < 14)
        {
            flag[p1] = false;
            flag[p2] = false;
            flag[p3] = false;
            if(dfs(deep - 3))
                return true;
            flag[p1] = true;
            flag[p2] = true;
            flag[p3] = true;
        }
    }
    return false;
}

bool solve()
{
    bool pf = true;
    for(int i = 0; i < 14; i += 2)
    {
        if(strcmp(str[i], str[i + 1]) != 0)
        {
            pf = false;
            break;
        }
    }
    if(pf)
        return true;
    for(int i = 0; i < 13; i++)
    {
        if(strcmp(str[i], str[i + 1]) == 0)
        {
            flag[i] = false;
            flag[i + 1] = false;
            if(dfs(12))
                return true;
            flag[i] = true;
            flag[i + 1] = true;
            i++;
        }
    }
    return false;
}

int main()
{
    int ti;
	scanf("%d", &ti);
    while(ti--)
    {
        memset(mapp, 0, sizeof(mapp));
        for(int i = 0 ; i < 13; i++)
        {
            cin >> str[i];
			mapp[str[i][0] - 'A'][str[i][1] - '0']++;
        }
        vector<string> vec[4];
        for(char c = 'A'; c <= 'D'; c++)
        {
            for(char i = '1'; i <= '9'; i++)
            {
                if(c == 'D' && i == '8')
                    break;
                char ch[3];
                ch[0] = c;
                ch[1] = i;
                ch[2] = '\0';
                if(mapp[ch[0] - 'A'][ch[1] - '0'] >= 4)
                    continue;
                mapp[ch[0] - 'A'][ch[1] - '0']++;
                bool f[4];
                memset(f, false, sizeof(f));
                if(ch[0] != 'D')
                {
                    int tmp = ch[1] - '0';
                    int item = ch[0] - 'A';
                    if(tmp - 2 > 0 && mapp[item][tmp - 2] > 0 && mapp[item][tmp - 1] > 0)
                        f[0] = true;
                    if(tmp - 1 > 0 && tmp + 1 < 10 && mapp[item][tmp + 1] > 0 && mapp[item][tmp - 1] > 0)
                        f[0] = true;
                    if(tmp + 2 < 10 && mapp[item][tmp + 2] > 0 && mapp[item][tmp + 1] > 0)
                        f[0] = true;
                }
                if(mapp[ch[0] - 'A'][ch[1] - '0'] >= 3)
                    f[1] = true;
                if(mapp[ch[0] - 'A'][ch[1] - '0'] == 4)
                    f[2] = true;
                char tmp[13][3];
                for(int i = 0; i < 13; i++)
                    strcpy(tmp[i], str[i]);
                strcpy(str[13], ch);
                qsort(str, 14, sizeof(str[0]), cmp);
                memset(flag, true, sizeof(flag));
                if(mapp[0][1] > 0 && mapp[0][9] > 0 && mapp[1][1] > 0 && mapp[1][9] > 0 && mapp[2][9] > 0 && mapp[2][9] > 0)
                {
                    bool flag2 = false;
                    if(mapp[0][1] == 2 || mapp[0][9] == 2 || mapp[1][1] == 2 || mapp[1][9] == 2 || mapp[2][1] == 2 || mapp[2][9] == 2)
                        flag2 = true;
                    f[3] = true;
                    for(int i = 1; i <= 7; i++)
                    {
                        if(mapp[3][i] == 0)
                        {
                            f[3] = false;
                            break;
                        }
                        if(mapp[3][i] == 2)
                            flag2 = true;
                    }
                    if(flag2 == false)
                        f[3] = false;
                }
                if(!f[3])
                    f[3] = solve();
                for(int i = 0; i < 13; i++)
                    strcpy(str[i], tmp[i]);
                for(int i = 0; i < 4; i++)
                {
                    if(f[i])
                    {
                        string str = ch;
                        vec[i].push_back(str);
                    }
                }
                mapp[ch[0] - 'A'][ch[1] - '0']--;
            }
        }
        for(int i = 0; i < 4; i++)
        {
            if(vec[i].size() == 0)
            {
                cout << "NONE" << endl;
                continue;
            }
            for(int j = 0; j < vec[i].size() - 1; j++)
                cout << vec[i][j] << " ";
            cout << vec[i][vec[i].size() - 1] << endl;
        }
        if(ti != 0)
            cout << endl;
    }
    return 0;
}


I. Bojie的電分重修路(淡藍)

時間限制:4000ms           記憶體限制:65536K

題目描述:

Bojie因為沉迷於acm競賽、開源專案以及實習,在大一大二分別兩次掛掉了電路分析。終於意識到自己如果再不復習來通過重修就很可能無法畢業的Bojie,在大三再次重修電路分析時候找到了貝院學霸Kss來幫助他複習。然而Kss發現Bojie因為高中物理課上講的基礎知識都忘光了導致連最基礎的KCL,KVL都無法理解。於是Kss決定從純電阻歐姆定律開始講起。

       給定一個包含N個節點(編號從1到N)的電路圖,由N–1條帶有不同電阻的導線將N個節點連線起來(保證不存在環路且所有節點均可以兩兩抵達)。現在Kss會進行Q次提問,每次提問會詢問如果將節點u和節點v分別連線到一個電動勢為E的理想電源(即無內阻)的正負極上時,通過電源的電流的值是多少。

       儘管Bojie已經下定決心要在大三把電路分析給過掉,但由於他忙於比較各家公司開出的天文數字offer並還在考慮到底該賞臉去哪家,於是他決定把這個問題交給各位校賽參賽選手。

輸入:

僅一組資料,第一行包含僅一個整數n (2 <= n <=100,000)表示節點總數,第2至第n行每行包含三個整數u, v, R(0 < R <= 100)表示有一根電阻為R的導線連線了節點u和v。第n + 1行僅包含一個整數Q (1<= Q <= 100,000)表示詢問總數。第n + 2到第n + Q + 1行每行包含三個整數u, v, E(0 < E <=10^9)表示電源電動勢為E的電源的正負極分別接到節點u和v,保證u,v為不同節點。

輸出:

輸出包含Q行,每行包含一個實數分別對應Q次詢問的通過電源的電流的值,因為kss是一個嚴謹的人,所以他希望答案能保留六位小數。C++中格式輸出,六位小數的輸出表達為 printf("%.6f\n", Current);

樣例輸入:

9

1 2 2

2 3 6

2 4 8

4 5 1

1 6 4

7 6 3

8 6 5

9 6 7

6

1 2 10

5 1 15

5 7 20

3 8 40

1 9 30

3 5 40

樣例輸出:

5.000000

1.363636

1.111111

2.352941

2.727273

2.666667

樣例解釋:

節點1到節點2之間的電阻和為2,電壓為10,所以電流為 10 / 2 = 5.000000

節點5到節點1之間的電阻和為11,電壓為15,所以電流為 15 / 11 = 1.363636

節點5到節點7之間的電阻和為18,電壓為20,所以電流為 20 / 18 = 1.111111

節點3到節點8之間的電阻和為17,電壓為40,所以電流為 40 / 17 = 2.352941

節點1到節點9之間的電阻和為11,電壓為30,所以電流為30 / 11 = 2.727273

節點3到節點5之間的電阻和為15,電壓為40,所以電流為40 / 15 = 2.666667

題目分析:我們的天貓同學出的,然後就是個LCA,線上倍增什麼的寫一寫就過啦

這是他給出的題解:

很容易看出題目給出的電路是一棵樹,然後詢問是求兩點間的邊權和,再與一個數相除得到這個詢問的答案。
因此顯而易見可以利用樹鏈剖分做這道題,將樹劃分成線段之後,進行兩點間的邊權求和實際上就轉化成了區間求和問題。由於不需要修改,因此剖分之後利用字首和陣列來求區間部分和即可,不需要再掛靠線段樹或是樹狀陣列。
同樣由於不需要修改,求兩點間邊權和的問題實際上就變成了求兩點分別到最近公共祖先的邊權和的和。因此也可以用樹上倍增來做,與倍增LCA寫法類似不過需要額外加一個sum域來實現求邊權和。關於倍增LCA的寫法請自行搜尋。
兩種演算法複雜度均為 O(QlogN)
標程為樹鏈剖分+字首和。
P.S. 本題所有計算都控制在int範圍內並且沒有給出短路等很trick的情況應該還是很良心的了。

先貼他的樹鏈剖分(我表示不會寫)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <map>
#include <cstdlib>
#include <cmath>
#include <string>

#define N 150000 + 100

#define X first
#define Y second

#define FOR(i,v) for(__typeof(v.begin()) i = v.begin(); i != v.end(); ++i)

#define m_p make_pair
#define p_b push_back

using namespace std;

struct Edge
{
    int u,v;
    int R;
}E[N];

vector <int> V[N];
int n,pos;
int fa[N];
int deep[N];
int num[N];
int p[N],fp[N]; //fp is the inverse of p
int heavySon[N],topFa[N];

void dfs_ini(int u, int pre, int d)
{
    deep[u] = d;
    fa[u] = pre;
    num[u] = 1;
    FOR(i, V[u])
    {
        if (*i != pre)
        {
            dfs_ini(*i, u, d + 1);
            num[u] += num[*i];
            if (heavySon[u] == -1 || num[*i] > num[heavySon[u]])
                heavySon[u] = *i;
        }
    }
}

void dfs_split(int u, int superFa)
{
    topFa[u] = superFa;
    p[u] = pos++;
    fp[p[u]] = u;
    if (heavySon[u] == -1)
        return;
    dfs_split(heavySon[u], superFa);
    FOR(i, V[u])
    {
        if (*i != heavySon[u] && *i != fa[u])
            dfs_split(*i, *i);
    }
}

int sum[N], Rval[N];

void build_sum()
{
    memset(sum,0,sizeof sum);
    for (int i = 1; i <= n; ++i)
        sum[i] = sum[i - 1] + Rval[i];
}

int part_sum(int ini, int lst)
{
    return sum[lst] - sum[ini - 1];
}

int get_ans(int u, int v)
{
    int f1 = topFa[u], f2 = topFa[v];
    int tot = 0;
    while (f1 != f2)
    {
        if (deep[f1] < deep[f2])
        {
            swap(f1, f2);
            swap(u, v);
        }
        tot += part_sum(p[f1], p[u]);
        u = fa[f1];
        f1 = topFa[u];
    }
    if (u == v)
        return tot;
    if (deep[u] > deep[v])
        swap(u, v);
    return tot + part_sum(p[heavySon[u]], p[v]);
}

int main()
{
    scanf("%d",&n);
    for (int i = 0; i <= n; ++i)
        V[i].clear();
    for (int i = 1; i < n; ++i)
    {
        scanf("%d%d%d",&E[i].u, &E[i].v, &E[i].R);
        V[E[i].u].p_b(E[i].v);
        V[E[i].v].p_b(E[i].u);
    }

    memset(topFa, -1, sizeof topFa);
    pos = 0;
    memset(heavySon, -1, sizeof heavySon);
    dfs_ini(1,0,0);
    dfs_split(1,1);
    memset(Rval, 0 ,sizeof Rval);
    for (int i = 1; i < n; ++i)
    {
        if (deep[E[i].u] > deep[E[i].v])
            swap(E[i].u, E[i].v);
        Rval[p[E[i].v]] = E[i].R;
    }
    build_sum();

    int Q;
    scanf("%d", &Q);
    int u, v, Voltage, Rtotal;
    for (int i = 1;i <= Q; ++i)
    {
        scanf("%d%d%d",&u, &v, &Voltage);
        Rtotal = get_ans(u, v);
        double Current = (double) Voltage / (double) Rtotal;
        printf("%.6f\n", Current);
    }
}

然後是我的線上倍增求LCA
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int const MAX = 100005;
int const POW = 18;
int n, m, cnt;
int p[MAX][20];
int dis[MAX], head[MAX], d[MAX];
bool vis[MAX];
struct Edge
{
    int to, nxt, val;  
}e[MAX << 1];

inline void Init()
{
    cnt = 0;
    memset(head, -1, sizeof(head));
}

inline void Add(int u, int v, int w)
{
    e[cnt].to = u; 
    e[cnt].val = w; 
    e[cnt].nxt = head[v]; 
    head[v] = cnt ++;
}

inline void Get_dis(int u, int fa, int d)
{
    dis[u] = d;
    for(int i = head[u]; i != -1; i = e[i].nxt)
    {
        int v = e[i].to;
        if(v != fa && !vis[v])
        {
            vis[v] = true;
            Get_dis(v, u, d + e[i].val);
        }
    }
}
  
inline void Get_p(int u, int fa)
{  
    d[u] = d[fa] + 1;  
    p[u][0] = fa;  
    for(int i = 1; i < POW; i++) 
        p[u][i] = p[p[u][i - 1]][i - 1];      
    for(int i = head[u]; i != -1; i = e[i].nxt)
        if(e[i].to != fa)
            Get_p(e[i].to, u);
}  

inline int Lca(int a, int b)
{  
    if(d[a] > d[b])
        swap(a, b);
    if(d[a] < d[b])
    {  
        int del = d[b] - d[a];  
        for(int i = 0; i < POW; i++) 
            if(del & (1 << i)) 
                b = p[b][i];  
    }  
    if(a != b)
    {  
        for(int i = POW - 1; i >= 0; i--) 
        {  
            if(p[a][i] != p[b][i])
            {   
                a = p[a][i];
                b = p[b][i]; 
            } 
        }
        a = p[a][0];
        b = p[b][0];  
    }  
    return a;  
}  

int main()
{
    Init();
    int u, v, w;
    scanf("%d", &n);
    for(int i = 1; i < n; i++)
    {
        scanf("%d %d %d", &u, &v, &w);
        Add(u, v, w);
        Add(v, u, w);
    }
    memset(vis, false, sizeof(vis));
    Get_dis(1, -1, 0);
    Get_p(1, 0);
    scanf("%d", &m);
    int x, y, val;
    while(m --)
    {
        scanf("%d %d %d", &x, &y, &val);
        printf("%.6f\n", (double) val / (double) (dis[x] + dis[y] - (2 * dis[Lca(x, y)])));
    }
}


J. 校慶馬拉松接力(黑)

時間限制:8000ms           記憶體限制:32768K

題目描述:

信達天下,自強不息,一代代南郵人在資訊時代的發展征程中披荊斬棘,奮勇前行,今年的4月20日是南京郵電大學辦學74週年紀念日,為此學校首次舉辦連線三牌樓校區和仙林校區的戶外跑步賽事,旨在進一步弘揚南郵精神,營造和諧向上的校園文化環境。

由於報名接力的人數過多,為了讓更多的學子參與進來,組織方決定將接力的規則修改,一段路不只讓一個人跑,這樣不但可以增加實際參賽人數,又增添了活動的趣味性,具體規則如下:

比賽以學院為單位,總路線被分成n段,每段路程為米,對每個學院要求第i段路至少需要有個該學院的選手跑過,每個學院自行挑選競跑選手,在報名的學子中,按個人身體素質分成了m級,第i級選手的平均速度為米每秒,且必須從第段路的起點一直跑到第段路的終點,完成比賽耗費總時間最少的學院獲得勝利。(注:這裡總時間指的是各學院每個參賽選手跑完自己路段時間的總和)

現在給你xxx學院的報名資訊和比賽路段資訊,假設該學院每級報名的人數都無限多,請你先判斷xxx學院能否完成比賽,在可以完成比賽的情況下你能給出一個分配方案使xxx學院完成比賽所耗費的總時間最短嗎?請求出這個最短總時間。

輸入:

輸入第1行為一個整數T代表陣列的組數,每組資料第1行為一個整數n表示共n段路,第2行到第n+1行每行2和整數,,分別表示第i段路的長度以及至少需要跑過的人數,第n+2行為一個整數m,代表選手被分成m級,接下來的m行,每行三個整數,,,分別代表第i級選手要跑的路段區間和其平均速度。 

其餘資料均不超過100000。

輸出:

如果不能完成比賽請輸出一行No,否則第一行輸出Yes,第二行輸出所耗費的最短總時間,答案保留兩位小數。

樣例輸入:

2

2

1 1

2 2

1

1 1 2

3

1 1

2 2

3 1

3

1 2 2

2 3 2

1 3 3

樣例輸出:

No

Yes

3.50

樣例解釋:

第一組樣例第二段路找不到人跑所以無法完成。

第二組樣例可以選擇1個第一級選手和1個第三級選手,耗時是3.50 (1.50+2.00),若選1個第一級選手和1個第二級選手也可以完成比賽,但耗時為4.00 (1.50+2.50)。


題目分析:這題估計都沒什麼人看吧。。。這是一道典型的線性規劃問題,可以用最小費用最大流解決,這題題解寫起來非常的麻煩,我就直接說怎麼建圖了,至於為什麼這樣,有興趣的留言私聊

首先根據字首和求出每類選手跑自己路段所需要的時間,對於相鄰的兩段路i和i-1,若第i-1段路需要跑過的人數小於第i段路則從源點向i引容量為d,費用為0的弧,否則從i向匯點引容量為-d,費用為0的弧,在任意兩個相鄰的路段,從前一段向後一段引容量為無窮大,費用為0的弧,對於每類選手,因為選手數量無限多,所以從起點x到終點y+1引流量為無窮大,費用為跑這段路所要時間的弧,然後跑一個費用流,這樣建圖是基於一系列線性規劃公式變換得到的。還有就是完不成的情況,那麼讀入的時候直接記錄下即可。

對於這題SPFA即可,當然有更快的方法。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
int const MAX = 1005;
int const INF1 = 0x3fffffff;
double const EPS = 1e-10;
double const INF2 = 1e17;
int n, m, cnt;
int src, sk;
int head[MAX], pre[MAX];
int a[MAX];
double sum[MAX], dis[MAX], ans;
bool vis[MAX], mark[MAX], flag;

struct EDGE
{
    int to, nxt, cap;
    double cost;
}e[10005 << 2];

void Init()
{
    flag = false;
    ans = 0;
    cnt = 0;
    memset(head, -1, sizeof(head));
    memset(mark, false, sizeof(mark));
    memset(sum, 0, sizeof(sum));
    memset(a, 0, sizeof(a));
}

void Add(int u, int v, int cap, double cost)
{
    e[cnt].to = v;
    e[cnt].cap = cap;
    e[cnt].cost = cost;
    e[cnt].nxt = head[u];
    head[u] = cnt ++;

    e[cnt].to = u;
    e[cnt].cap = 0;
    e[cnt].cost = -cost;
    e[cnt].nxt = head[v];
    head[v] = cnt ++;
}   

void Build_graph()
{
    int x, y;
    double s, v;
    scanf("%d", &n);
    src = 0;
    sk = n + 2;
    for(int i = 1; i <= n; i++)
    {
        scanf("%lf %d", &s, &a[i]);
        sum[i] = sum[i - 1] + s;
    }
    for(int i = 1; i <= n + 1; i++)
    {
        int d = a[i] - a[i - 1];
        if(d > 0)
            Add(src, i, d, 0);
        else 
            Add(i, sk, -d, 0);
        if(i > 1)
            Add(i, i - 1, INF1, 0);
    }
    scanf("%d", &m);
    for(int i = 0; i < m; i++)
    {
        scanf("%d %d %lf", &x, &y, &v);
        mark[x] = true;
        mark[y] = true;
        Add(x, y + 1, INF1, (sum[y] - sum[x - 1]) / v);
    }
    for(int i = 1; !flag && i <= n; i++)
        if(a[i] && !mark[i])
            flag = true;
}

bool SPFA()
{
    queue <int> q;
    memset(vis, false, sizeof(vis));
    for(int i = 0; i <= sk; i++)
        dis[i] = INF2;
    dis[src] = 0;
    vis[src] = true;
    q.push(src);
    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        vis[u] = false;
        for(int i = head[u]; i != -1; i = e[i].nxt)
        {
            int v = e[i].to;
            int cap = e[i].cap;
            double cost = e[i].cost;
            if(cap > 0 && dis[u] + cost - dis[v] < -EPS)
            {
                pre[v] = i;
                dis[v] = dis[u] + cost;
                if(!vis[v])
                {
                    vis[v] = true;
                    q.push(v);
                }
            }
        }
    }
    return dis[sk] != INF2;
}

void Augment()
{
    while(SPFA())
    {
        int mi = INF1;
        for(int u = sk; u != src; u = e[pre[u] ^ 1].to)
            mi = min(mi, e[pre[u]].cap);
        for(int u = sk; u != src; u = e[pre[u] ^ 1].to)
        {
            e[pre[u]].cap -= mi;
            e[pre[u] ^ 1].cap += mi;
            ans += (double)mi * e[pre[u]].cost;
        }
    }
}

int main()
{
    int T;
    scanf("%d", &T);
    while(T --)
    {
        Init();
        Build_graph();
        if(flag)
            printf("No\n");
        else
        {
            Augment();
            printf("Yes\n%.2f\n", ans);
        }
    }
}