1. 程式人生 > >【解題報告】2015ACM/ICPC亞洲區瀋陽站

【解題報告】2015ACM/ICPC亞洲區瀋陽站

題目連結

B. Bazinga(HDU5510)

思路

設第i個字串儲存在ss[i][]中。本題最直觀最樸素的做法是列舉兩個字串ss[i]和ss[j] (i+1j),再用KMP演算法匹配這兩個字串。首先從大到小列舉j,若對某個ss[j]存在某個ss[i]不是其子串,則立即可以得到答案j。但是,這樣做的時間複雜度太高,因此行不通。不難看出,之所以複雜度這麼高,是因為對任意的ss[i]和ss[j],樸素演算法都要對其進行匹配。這是無疑是一種浪費。實際上我們只需要匹配一次就夠了。
我的做法是,先令所有的ss[i] (in) 與ss[n]進行一次匹配,若出現一次匹配失敗則答案就是n。若全部匹配成功則可以得到一個重要的結論:所有的ss[i] (

in) 都是ss[n]的子串。也就是說我們在往後的比較中可以用ss[i] (in) 在ss[n]中的位置資訊(用區間來表示)取代ss[i]的字串內容來判斷這個字串是不是另一個字串的子串。
例如ss[1] = “ab”, ss[2] = “bc”, ss[3] = “abc”。在ss[1],ss[2]同ss[3]的比較中發現,ss[1]和ss[2]都是ss[3]的子串。對應的位置資訊為 [0,1][1,2] ,由於前者不包含在後者中,因此答案為2。

程式碼:

#include <cstdio>
#include <cstring>

const int
maxn = 5e2 + 5, maxLen = 2e3 + 5; char ss[maxn][maxLen]; int t, n, ans, next[maxLen], a[maxn][2]; void getNext(char* p) { int lp = strlen(p); next[0] = -1; for(int j=0, k=-1; j < lp-1;) { if(k == -1 || p[j] == p[k]) { j++, k++; if(p[j] != p[k]) next[j] = k; else
next[j] = next[k]; } else k=next[k]; } } int kmp(char* s, char* p) { int i = 0, j = 0; int ls = strlen(s), lp = strlen(p); for(; i < ls && j < lp;) { if(j == -1 || s[i] == p[j]) i++, j++; else j = next[j]; } if(j == lp) return i - j; else return -1; } int main() { scanf("%d", &t); for(int kase = 1; kase <= t; kase++) { scanf("%d", &n); for(int i = 1; i <= n; i++) { scanf("%s", ss[i]); } int len = strlen(ss[n]); bool exist = false; // 判斷n是否是答案 for(int i = 1; i < n; i++) { getNext(ss[i]); int res = kmp(ss[n], ss[i]); if(res == -1) { exist = true; break; } // 記錄位置資訊 a[i][0] = res; a[i][1] = res + strlen(ss[i]) - 1; } if(exist) { printf("Case #%d: %d\n", kase, n); continue; } ans = -1; // 列舉兩個字串,比較它們的位置資訊 for(int i = n - 1; i > 0; i--) { for(int j = 1; j < i; j++) { if(a[i][0] > a[j][0] || a[i][1] < a[j][1]) { ans = i; i = 0; break; } } } printf("Case #%d: %d\n", kase, ans); } return 0; }

D. Pagodas(HDU 5512)

思路

根據貝祖定理(擴充套件歐幾里德演算法的理論基礎),對於任意兩個整數 a,b ,存在 x,y 使得 x×a+y×b=gcd(a,b) 。也即是說, a,b 的任意整係數線性組合的結果都是它們的最大公約數的整數倍。而在本題中本結論又可以描述為: a,b 經過加減運算而衍生出來的新數,都是 gcd(a,b) 的整數倍。這樣,我們通過求 gcd(a,b) 可以知道既滿足 m=k×gcd(a,b),(k=1,2,3,...) 又滿足 (mn)m 的個數 num ,根據 num 奇偶性就能知道最後的勝者是誰了。

程式碼

#include <bits/stdc++.h>
using namespace std;

int t, n, a, b, g, ub, times;

int main() {
    scanf("%d", &t);
    for(int kase = 1; kase <= t; kase++) {
        printf("Case #%d: ", kase);
        scanf("%d%d%d", &n, &a, &b);
        g = __gcd(a, b);
        ub = n - n % g;
        times = ub / g - 2;
        puts(times % 2 ? "Yuwgna" : "Iaka");
    }
    return 0;
}

F. Frogs(HDU 5514)

思路(容斥原理)

首先,根據貝祖定理可知,第i只青蛙能夠到達的石塊的編號組成的數列應是以起點編號0為首項,以 gcd(ai,m) 為公差的等差數列。以樣例中的第一組為例,第1只青蛙(ai=2)能夠到達的石塊的編號為 0,2,4,6,8,10
那麼這是不是意味著我們可以列舉每隻青蛙,然後對每隻青蛙對應的等差數列求和,然後再將所有的數列和相加就能得到正確答案了呢?這種思路是不對的,因為這樣簡單地相加是會有重複的。例如 ai=2 的青蛙和 ai=3 的青蛙都會經過編號為 6 的石塊。
所以,接下來的問題是要解決重複。對以樣例第一組為例,最直觀的辦法是額外減去 ai=6 的青蛙(虛擬的青蛙)跳過的編號數列和。但事實上,只考慮減的問題對於某些樣例而言並不足夠。例如這樣的樣例:

n=3,m=12

a1=2,a2=3,a3=4

對第一隻青蛙我們只需要在最後的結果ans變數中加上第一隻青蛙對應的編號數列和即可。然後考慮第二隻青蛙,除了加上數列和以外,還要減少一隻虛擬青蛙 a=6 的數列和來消除重複。最後考慮第三隻青蛙,除了加上第三隻青蛙對應的而編號數列和以外,還需要減去一隻虛擬青蛙 a=4 的數列和。這樣看上去似乎是功德圓滿了。確實,這回沒有重複的數被加到ans中了,但是實際上答案中多減少了一隻虛擬青蛙 a=6 的數列和,這是我們在減去虛擬青蛙 a=6 時不小心減去的。問題似乎變得複雜了。
其實,這樣重複加需要減來抵消,重複減又需要加來抵消的情況符合容斥原理的模型(想象求幾何圖形交的總面積)。實現方法是:記 gi=gcd(ai,m) ,找出對 m 而言可能的 gi 的情況,也就是將 m 的因子找出來,存在fac陣列中,再用陣列painted標記本題實際出現的gi 所能影響的全部因子fac[j],賦值fac[j] = 1。然後遍歷painted陣列,先將對應的數列求和,然後考慮若 painted[i]>0 則將faci影響的所有fac[j](滿足 fac[j]modfac[i]=0 )減去painted[i]的數值,表示facj代表的數列已經全部被加到答案中了。若 painted[i]<0 則將fac[i]影響的所有fac[j]加上painted[i]的數值,表示fac[j]代表的數列受fac[i]的減行為的影響,需要額外的加。
這個過程像極了求幾何圖形交的總面積的過程,因此我才將表示某部分是否已經算入ans的標記陣列的名字命名為painted。

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 1e4 + 10, maxFac = 1e3;
int t, n, m, a, g[maxn], painted[maxFac];
vector <int> fac;
ll ans;

ll sum(ll n, ll d) {
    return n * (n-1) / 2 * d;
}

int main() {
    scanf("%d", &t);
    for(int kase = 1; kase <= t; kase++) {
        scanf("%d%d", &n, &m);
        // 輸入資料的同時預處理出g
        for(int i = 0; i < n; i++) {
            scanf("%d", &a);
            g[i] = __gcd(a, m);
        }
        fac.clear();
        // 列舉m的因數
        for(int i = 1; i * i <= m; i++) {
            if(m % i == 0) {
                fac.push_back(i);
                if(i * i != m) {
                    fac.push_back(m / i);
                }
            }
        }
        sort(fac.begin(), fac.end());
        memset(painted, 0, sizeof(painted));
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < fac.size(); j++) {
            // 初始化painted陣列
                if(fac[j] % g[i] == 0) {
                    painted[j] = 1;
                }
            }
        }
        ans = 0;
        for(int i = 0; i < fac.size(); i++) {
            if(painted[i] != 0) {
                // 將數列和加到最後結果中
                ans += painted[i] * sum(m / fac[i], fac[i]);
                // 維護painted陣列
                for(int j = i + 1; j < fac.size(); j++) {
                    if(fac[j] % fac[i] == 0) {
                        painted[j] -= painted[i];
                    }
                }
            }
        }
        printf("Case #%d: %I64d\n", kase, ans);
    }
    return 0;
}

M.Meeting(HDU5521)

思路

分別以兩隻牛的起點為起點求兩個最短路陣列,然後列舉每個點就能找到最好的那個點。注意,如果確定要在某個點相會,一隻牛先到了的話需要等另外一隻牛。另外,圖的每個完全子圖都要建邊的話複雜度將難以讓人忍受所以對於每個完全子圖,設定一個虛擬的結點,再從虛擬結點向子圖中的所有結點建邊,這樣建邊的複雜度就降低到了線性複雜度。

程式碼

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef pair <ll, int> p;
const int maxn = 1e5 + 5, maxm = 1e6 + 5;
const ll INF = 1e15 + 5;
bool vis[maxn + maxm];
int t, m, n, si, ti;
ll ans, maxTime[maxn];
vector <int> vec;

//  邊的結構體
struct edge {
    int to, dist;
    edge() {}
    edge(int to, int dist): to(to), dist(dist) {}
};

// dijkstra演算法模板
struct dijkstra {
    ll d[2][maxn+maxm];
    vector <edge> G[maxn+maxm];
    void init() {
        for(int