【解題報告】2016 Multi-University Training Contest 4

[題目鏈接](http://acm.split.hdu.edu.cn/search.php?field=problem&key=2016+Multi-University+Training+Contest+4&source=1&searchmode=source)

A.Another Meaning(HDU 5763)

大意

給定一個句子 A ,和一個單詞 B ,其中單詞 B 有兩種含義,問句子 A 有多少種含義。

思路

考慮句子的前綴 A[0..i] ,令 d[i] 爲該前綴的含義的個數。如果考慮單詞 B 的第一種含義, d[i]=d[i]+d[i1] 。若考慮單詞的第二種含義,且 A[0..i] 的某個後綴爲 Bd[i]=d[i]+d[ilengthB] 。判斷 A[0..i] 是否有後綴 B 的方法是,在預處理的時候用 KMP 算法,當 A[i..j]=B 的時候(發生匹配的時候)令 ok[i]=true ,也就是標記一下 i 。所以算法就是從 0 開始按照上述的關係遞推計算 d[i] 。最後 d[lengthA1] 就是答案。

代碼

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

const int maxn = 1e5 + 10, mod = 1e9 + 7;
bool ok[maxn];
char s[maxn], p[maxn];
int t, sn, pn, nxt[maxn], d[maxn];

// 計算next數組
void getNext() {
    int j, k;
    nxt[j=0] = k = -1;
    while(j < pn) {
        if(k == -1 || p[k] == p[j]) {
            nxt[++j] = ++k;
        }
        else {
            k = nxt[k];
        }
    }
}

// KMP算法
void kmpCount() {
    int j = 0, ans = 0;
    for(int i = 0; i < sn; i++) {
        while(j && s[i] != p[j]) {
            j = nxt[j];
        }
        if(s[i] == p[j]) {
            j++;
        }
        if(j == pn) {
            ok[i-pn+1] = true;
            j = nxt[j];
        }
    }
}

int main() {
    scanf("%d", &t);
    for(int kase = 1; kase <= t; kase++) {
        memset(ok, 0, sizeof(ok));
        memset(d, 0, sizeof(d));
        scanf("%s%s", s, p);
        sn = strlen(s);
        pn = strlen(p);
        getNext();
        kmpCount();
        for(int i = 0; i < sn; i++) {
            if(ok[i] == true) {
                d[i+pn-1] = (d[i+pn-1] + (i > 0 ? d[i-1] : 1)) % mod;
            }
            d[i] = (d[i] + (i > 0 ? d[i-1] : 1)) % mod;
        }
        printf("Case #%d: %d\n", kase, d[sn-1]);
    }
    return 0;
}

E.Lucky7(HDU 5768)

大意

給定一個區間 [x,y]n 個數對 (pi,ai) ,問在區間中的能被 7 整除的,並且對所有 i ,被 pi 除不餘 ai 的數共有多少個。

思路

本題實際上要我們求對於一個給定的 r ,在區間 (0,r] 中,被 7 整除的數的個數 seven(r) 與“被 7 整除且存在某個 pi ,使其除它餘 ai ”的數的個數 notOk(r) 之差(簡單地說就是 seven(r)notOk(r) )。爲什麼要將原問題向這個方向轉化呢?因爲 seven(r)notOk(r) 都是可求的。其中 seven(r)=r/7 ,而 notOk(r) 可以用容斥原理算出。
notOk(r)相當於集合“ {x|xmod7=0}{x|xmodp1=a1}...{x|xmodpn=an} ”的元素數量。因將取並集之前的集合的元素數量簡單相加會產生重複,因此要用容斥原理解決(大並集元素個數等於若干個小交集元素個數相加減的結果)。
由於 pi 之間是互質的,所以我們可以用中國剩餘定理求出容斥原理的過程中,某個狀態的“小交集”的元素的個數(方法類似於 seven(r)=r/7 )。

代碼

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

typedef long long ll;
const int maxn = 20;
ll T, n, l, r, p[maxn], a[maxn], m[maxn], b[maxn];

// 快速冪算法
ll modMul(ll a, ll b, ll mod) {
    ll ans = 0;
    for(; b > 0; b >>= 1) {
        if (b & 1) {
        ans = (ans + a) % mod;
    }
        a = (a << 1) % mod;
    }
    return ans;
}

// 擴展歐幾里得算法
void extGcd(ll a, ll b, ll& x, ll& y) {
    if(b == 0) {
        x = 1;
        y = 0;
        return;
    }
    extGcd(b, a % b, x, y);
    ll tmp = x;
    x = y;
    y = tmp - (a / b) * y;
}

// 中國剩餘定理
ll CRT(ll a[], ll m[], ll M, ll n) {
    ll ans = 0;
    for(int i = 0; i <= n; i++) {
        ll x, y, Mi = M / m[i];
        extGcd(Mi, m[i], x, y);
        x = (x % M + M) % M;
        ans = (ans + modMul(modMul(Mi, x, M), a[i], M)) % M;
    }
    return (ans + M) % M;
}

// 計算(0, r]內有多少滿足條件的數
ll count(ll r) {
    if(r < 0) {
        return 0;
    }
    ll x, num, ans = r / 7;
    // 狀態壓縮的容斥原理
    for(int mask = 1; mask < (1 << n); mask++) {
        ll tail = 0, cnt = 0, M = 7;
        for(int j = 0; j < n; j++) {
            if(mask & (1 << j)) {
                m[++tail] = p[j];
                b[tail] = a[j];
                M *= m[tail];
                cnt++;
            }
        }
        x = CRT(b, m, M, tail);
        if(r < x) {
            continue;
        }
        num = (r - x) / M + 1;
        ans += (cnt % 2 ? -1 : 1) * num;
    }
    return ans;
}

int main() {
    cin >> T;
    m[0] = 7;
    for(int kase = 1; kase <= T; kase++) {
        cin >> n >> l >> r;
        for(int i = 0; i < n; i++) {
            cin >> p[i] >> a[i];
        }
        cout << "Case #" << kase << ": ";
        cout << count(r) - count(l - 1) << endl;
    }
    return 0;
}

F.Substring(HDU 5769)

大意

求字符串 S 中含有字符 X 的不同的子串的個數。

思路

如果是求字符串 S 中不同子串的個數的話,則可以對字符串計算其後綴數組 sa 和高度數組 height 。對於一個後綴 S[i..n1] ,它能夠構成 nsa[i] 個子串,其中又有 height[i] 個與其它子串重複的子串(可以證明,但此處空白太小寫不下),即位置 i 能夠貢獻 nsa[i]height[i] 個不同的子串。 S 的不同子串的個數就是 n1i=0nsa[i]height[i]
現還要滿足子串中含有字符 X 。對於後綴 S[i..n1] ,設在位置 i 之後第一次出現 X 的位置爲 next[i] ,那麼位置 i 能夠貢獻 nmax(next[i],sa[i]+height[i]) 個含有 X 的不同的子串。則本題的答案就是 n1i=0(nmax(next[i],sa[i],height[i]))

代碼

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

typedef long long ll;
const int maxn = 1e5 + 10;
char s[maxn];
int sa[maxn], t1[maxn], t2[maxn], c[maxn];
int Rank[maxn], height[maxn];
char ch[5];
int nxt[maxn];

// 計算高度數組
void getHeight(int n){
    int k = 0;
    for(int i=1;i<=n;i++)Rank[sa[i]] = i;
    for(int i=0;i<n;i++){
        if(k)k--;
        int j = sa[Rank[i]-1];
        while(s[i+k]==s[j+k])k++;
        height[Rank[i]] = k;
    }
}

bool cmp(int *r,int a,int b,int l){
    return (r[a]==r[b] && r[a+l]==r[b+l]);
}

// 計算後綴數組
void build_sa(int m,int n){
    int i,*x=t1,*y=t2,k,p;
    for( i=0;i<m;i++)c[i] = 0;
    for( i=0;i<n;i++)c[x[i] = s[i]]++;
    for( i=1;i<m;i++)c[i] += c[i-1];
    for( i=n-1;i>=0;i--)sa[-- c[x[i]]] = i;
    for(k=1,p=0;p<n;m=p,k<<=1){
        p = 0;
        for(i=n-k;i<n;i++)y[p++] = i;
        for(i=0;i<n;i++)if(sa[i]>=k)y[p++] = sa[i]-k;
        for(i=0;i<m;i++)c[i] = 0;
        for(i=0;i<n;i++)c[x[y[i]]]++;
        for(i=1;i<m;i++)c[i] += c[i-1];
        for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]] = y[i];
        swap(x,y);
        p = 1; x[sa[0]] = 0;
        for(i=1;i<n;i++)
            x[sa[i]] = cmp(y,sa[i-1],sa[i],k)?p-1:p++;
    }
    getHeight(n-1);
}

// 計算不同子串的個數
ll solve(int n) {
    int last = n;
    ll ans = n - sa[0];
    // 預處理上文提到的next數組
    for(int i = n; i >= 0; i--) {
        if(s[i] == ch[0]) {
            last = i;
        }
        nxt[i] = last;
    }
    for(int i = 1; i <= n; i++) {
        ans += n - max(nxt[sa[i]], sa[i] + height[i]);
    }
    return ans;
}

int main(){
    int T;
    scanf("%d", &T);
    for(int kase = 1; kase <= T; kase++) {
        scanf("%s%s", ch, s);
        int n = strlen(s);
        build_sa(255, n + 1);
        printf("Case #%d: %I64d\n", kase, solve(n));
    }
    return 0;
}

J.The All-purpose Zero(HDU 5773)

大意

題給一個數字序列,其中的每個數字 0 都可以修改成任意整數。問將數字修改後(也可以不修改),這個序列的最長上升子序列( LIS )是多少。

思路

假設我們將序列 a 中的所有 0 去掉,這樣會形成一個新序列 bbLIS 是否爲所求呢?顯然不是,將 bLIS (設其最左端和最右端的位置分別爲 lr )在 a 中對應的段 [l,r] 的左邊的所有 0 改成極小值,右邊的所有 0 改成極大值後, aLIS 還可以提高。
那麼問題來了,如果將 [l,r] 中的 0 改爲某些值, aLIS 是否還能提高?答案顯然是能的,而且利用等價的思想還能比較方便的實現。假設兩個數字時間夾了若干個 03,0,0,6 ,我們將 0 右邊的數字減去表示 0 的個數的數字,再將 0 消去,那麼序列變成了 3,6 ,對它求 LIS ,那麼原序列的 LIS 等於消去 0 後的 LIS 加上消去的 0 的個數,也就是 4
綜上所述,算法就是將 a 中所有的 0 (設其個數爲 num )按照上述方法消去後得到 b ,求 ans=LIS(b) ,那麼答案就是 ans+num

代碼

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

const int maxn = 1e5 + 10;
int t, n, zero, num, tail, idx, ans, a[maxn], b[maxn], c[maxn];

int main() {
    scanf("%d", &t);
    for(int kase = 1; kase <= t; kase++) {
        scanf("%d", &n);
        for(int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
        }
        zero = num = 0;
        tail = 0;
        // 構造消去0後的新序列b
        for(int i = 1; i <= n; i++) {
            if(a[i] > 0) {
                b[++tail] = a[i] - num;
                num = 0;
            }
            else {
                zero++;
                num++;
            }
        }
        ans = 0;
        fill(c + 1, c + n + 1, INT_MAX);
        c[0] = -INT_MAX;
        // 求b的LIS
        for(int i = 1; i <= tail; i++) {
            idx = lower_bound(c + 1, c + n + 1, b[i]) - c;
            ans = max(ans, idx);
            c[idx] = b[i];
        }
        printf("Case #%d: %d\n", kase, ans + zero);
    }
    return 0;
}

K.Where Amazing Happens(HDU 5774)

思路

給出 NBA 歷年的總冠軍情況,然後給定球隊名字,求其歷年獲得的總冠軍數之和。

思路

先編寫一個輔助程序,將答案製成表格。然後在主程序中根據輸入輸出即可。

代碼

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

// 打表需要的輔助程序
/*
int x, y;
string s;
map <string, int> mp;
map <string, int> :: iterator it;

int main() {
    freopen("data.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    while(scanf("%d-%d ", &x, &y) == 2) {
        getline(cin, s);
        mp[s]++;
    }
    for(it = mp.begin(); it != mp.end(); it++) {
        cout << "mp[\"" << it->first << "\"] = " << it->second << ";\n";
    }
    return 0;
}
*/

///*

int t;
string s;
map <string, int> mp;

// 根據輔助程序的輸出結果製表
void init() {
    mp["Baltimore Bullets"] = 1;
    mp["Boston Celtics"] = 17;
    mp["Chicago Bulls"] = 6;
    mp["Cleveland Cavaliers"] = 1;
    mp["Dallas Mavericks"] = 1;
    mp["Detroit Pistons"] = 3;
    mp["Golden State Warriors"] = 2;
    mp["Houston Rockets"] = 2;
    mp["L.A. Lakers"] = 11;
    mp["Miami Heat"] = 3;
    mp["Milwaukee Bucks"] = 1;
    mp["Minneapolis Lakers"] = 5;
    mp["New York Knicks"] = 2;
    mp["Philadelphia 76ers"] = 2;
    mp["Philadelphia Warriors"] = 2;
    mp["Portland Trail Blazers"] = 1;
    mp["Rochester Royals"] = 1;
    mp["San Antonio Spurs"] = 5;
    mp["Seattle Sonics"] = 1;
    mp["St. Louis Hawks"] = 1;
    mp["Syracuse Nats"] = 1;
    mp["Washington Bullets"] = 1;
}

int main() {
    init();
    scanf("%d\n", &t);
    for(int kase = 1; kase <= t; kase++) {
        getline(cin, s);
        cout << "Case #" << kase << ": " << mp[s] << endl;
    }
    return 0;
}
//*/

L.Bubble Sort(HDU 5775)

大意

給定一個排列並對這個排列進行冒泡排序,求該排列中每個元素在冒泡排序的過程中將會達到的最右位置和最左位置的差值。

思路

爲了方便討論,將排列中的數分成兩種

  • 排序後的位置在排序前的位置的左邊。那麼它的目的位置就是它能到達的最左邊的位置,它能到達的最右邊的位置是它的位置 i 加上在它右邊的比它小的數的個數 rightLess[i]
  • 排序後的位置在排序前的位置的右邊。那麼它的起始位置就是它能到達的最左邊的位置,它能到達的最右邊的位置是它的位置 i 加上在它右邊的比它小的數的個數 rightLess[i]

rightLess[i] 可以用樹狀數組快速求得。

代碼

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

const int maxn = 1e5 + 10;
int t, n, a[maxn], b[maxn], bit[maxn];

// 樹狀數組模板
void add(int x) {
    for(; x <= n; x += x & -x) {
        bit[x]++;
    }
}

int sum(int x) {
    int res = 0;
    for(; x >= 1; x -= x & -x) {
        res += bit[x];
    }
    return res;
}

int main() {
    scanf("%d", &t);
    for(int kase = 1; kase <= t; kase++) {
        scanf("%d", &n);
        for(int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
        }
        memset(bit, 0, sizeof(bit));
        for(int i = n; i >= 1; i--) {
            // 計算差值
            b[a[i]] = i + sum(a[i]) - min(i, a[i]);
            add(a[i]);
        }
        printf("Case #%d:", kase);
        for(int i = 1; i <= n; i++) {
            printf(" %d", b[i]);
        }
        puts("");
    }
    return 0;
}

(其它題目略)

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