【解题报告】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;
}

(其它题目略)

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