「备战PKUWC2018」2016-2017 ACM-ICPC CHINA-Final

这场比赛好难啊。。。
最终成绩:
这里写图片描述
如果不算 Hash_Table 自己切掉的 J 题,自己还能在区域赛搞个 Ag?

Problem A. Number Theory Problem

题意:求有多少小于 2N 且形如 2k1 且能被 7 整除的数。
题解:签到题。printf("%d\n, n / 3);
Hash_Table’s Code:

#include
using namespace std;

int n,T;

int main()
{
    cin>>T;
    for(int i=1;i<=T;i++)
    {
        cin>>n;
        printf("Case #%d: %d\n",i,n/3);
    }
    return 0;
}

Problem B. Hemi Palindrome

题意:定义「半回文串」是奇数位或偶数位构成回文串的字符串,如 10111111111101 是「半回文串」 ,而 10100111000 不是。
求长度为 N 字典序第 K 小的字符集为 0,1 的「半回文串」 。

N105,K1018

题解: 按位考虑,枚举当前位填什么,然后算出以当前位为前缀的「半回文串」的数目,确定当前位的数字。需要考虑很多种情况,代码很恶心。

My Code:

#include 

using namespace std;
typedef long long ll;
typedef pair<int, int> P;
int n; ll m;
int N;
int a[100005];
int flag = 0;
ll pw[100005];

void dfs(int cur, ll k, int f0, int f1){
    //printf("%d %d %d %d\n", cur, k, f0, f1);
    if(cur > n) return;
    ll tmp = 1;
    int cr = cur;
    if(cur <= N){
        tmp = tmp * pw[N - cur];
        if(tmp >= k){
            a[cur] = 0;
            dfs(cur + 1, k, 1, 1);
            return;
        }
        cr = n - N;
    //  printf("%d %d\n", cur, N);
        ll t1 = pw[cr / 2] + pw[cr - cr / 2] - 1;
        if(t1 >= k / tmp + 1){
            a[cur] = 0;
            dfs(cur + 1, k, 1, 1);
            return;
        }
        tmp *= t1;
        if(tmp >= k){
            a[cur] = 0;
            dfs(cur + 1, k, 1, 1);
            return;
        }
        k -= tmp;
        a[cur] = 1;
        dfs(cur + 1, k, 1, 1);
        return;
    }
    if(f0 == 1 && f1 == 1){
        ll t0 = 0, t1 = 0;
        int cr;
        if(cur % 2 == 0 && n % 2 == 0) cr = n - cur + 2;
        if(cur % 2 == 0 && n % 2 == 1) cr = n - cur + 1;
        if(cur % 2 == 1 && n % 2 == 0) cr = n - cur;
        if(cur % 2 == 1 && n % 2 == 1) cr = n - cur + 1;
        int ccr = n - cur + 1;
        if(cr % 2 == 0){
            if(ccr % 2 == 0){
                if(a[cr] == 0) t0 += pw[ccr / 2] + pw[ccr / 2 - 1] - 1, t1 += pw[ccr / 2 - 1];
                else t1 += pw[ccr / 2] + pw[ccr / 2 - 1] - 1, t0 += pw[ccr / 2 - 1];
            }else{
                if(a[cr] == 0) t0 += pw[ccr / 2] + pw[ccr / 2] - 1, t1 += pw[ccr / 2];
                else t1 += pw[ccr / 2] + pw[ccr / 2] - 1, t0 += pw[ccr / 2];
            }
        }else{
            if(ccr % 2 == 0){
                if(a[cr] == 0) t0 += pw[ccr / 2] + pw[ccr / 2 - 1] - 1, t1 += pw[ccr / 2 - 1];
                else t1 += pw[ccr / 2] + pw[ccr / 2 - 1] - 1, t0 += pw[ccr / 2 - 1];
            }else{
                if(a[cr] == 0) t0 += pw[ccr / 2] + pw[ccr / 2] - 1, t1 += pw[ccr / 2];
                else t1 += pw[ccr / 2] + pw[ccr / 2] - 1, t0 += pw[ccr / 2];
            }

        }

        if(t0 >= k){
            a[cur] = 0;
            if(a[cr] == 0) dfs(cur + 1, k, 1, 1);
            else if(cr % 2 == 0) dfs(cur + 1, k, 0, 1);
            else if(cr % 2 == 1) dfs(cur + 1, k, 1, 0);
            return;
        }
        k -= t0;
        if(t1 >= k){
            a[cur] = 1;
            if(a[cr] == 1) dfs(cur + 1, k, 1, 1);
            else if(cr % 2 == 0) dfs(cur + 1, k, 0, 1);
            else if(cr % 2 == 1) dfs(cur + 1, k, 1, 0);
            return;
            return;
        }
        flag = 1;
        return;
    }
    if(f0 == 0 && f1 == 1){
        ll t0 = 0, t1 = 0;
        int cr;
        if(cur % 2 == 0 && n % 2 == 0) cr = n - cur + 2;
        if(cur % 2 == 0 && n % 2 == 1) cr = n - cur + 1;
        if(cur % 2 == 1 && n % 2 == 0) cr = n - cur;
        if(cur % 2 == 1 && n % 2 == 1) cr = n - cur + 1;
        int ccr = n - cur + 1;
        if(cr % 2 == 0){
            if(ccr % 2 == 0){
                t0 += pw[ccr / 2 - 1], t1 += pw[ccr / 2 - 1];
            }else{
                t0 += pw[ccr / 2], t1 += pw[ccr / 2];
            }
        }else{
            if(ccr % 2 == 0){
                if(a[cr] == 0) t0 += pw[ccr / 2];
                else t1 += pw[ccr / 2]; 
            }else{
                if(a[cr] == 0) t0 += pw[ccr / 2];
                else t1 += pw[ccr / 2]; 
            }
        }

        if(t0 >= k){
            a[cur] = 0;
            dfs(cur + 1, k, 0, 1);
            return;
        }
        k -= t0;
        if(t1 >= k){
            a[cur] = 1;
            dfs(cur + 1, k, 0, 1);
            return;
        }
        flag = 1;
        return;
    }
    if(f0 == 1 && f1 == 0){
        ll t0 = 0, t1 = 0;
        int cr;
        if(cur % 2 == 0 && n % 2 == 0) cr = n - cur + 2;
        if(cur % 2 == 0 && n % 2 == 1) cr = n - cur + 1;
        if(cur % 2 == 1 && n % 2 == 0) cr = n - cur;
        if(cur % 2 == 1 && n % 2 == 1) cr = n - cur + 1;
        int ccr = n - cur + 1;
        if(cr % 2 == 1){
            if(ccr % 2 == 0){
                t0 += pw[ccr / 2 - 1], t1 += pw[ccr / 2 - 1];
            }else{
                t0 += pw[ccr / 2], t1 += pw[ccr / 2];
            }

        }else{
            if(ccr % 2 == 0){
                if(a[cr] == 0) t0 += pw[ccr / 2];
                else t1 += pw[ccr / 2]; 
            }else{
                if(a[cr] == 0) t0 += pw[ccr / 2];
                else t1 += pw[ccr / 2]; 
            }
        }

        if(t0 >= k){
            a[cur] = 0;
            dfs(cur + 1, k, 1, 0);
            return;
        }
        k -= t0;
        if(t1 >= k){
            a[cur] = 1;
            dfs(cur + 1, k, 1, 0);
            return;
        }
        flag = 1;
        return;
    }
}

void Solve(){
    N = ((n - 1) / 4 + 1) * 2 - (n % 4 == 1);
    flag = 0;
    dfs(1, m, 1, 1);
    if(flag == 1 || (n == 1 && m > 2) || (n == 2 && m > 4)){
        printf("NOT FOUND!\n");
        return;
    }
    for(int i = 1; i <= n; i ++){
        printf("%d", a[i]);
    }
    printf("\n");
}

int main(){
    int T;
    pw[0] = 1;
    for(int i = 1; i <= 100000; i ++){
        pw[i] = pw[i - 1] * 2;
        if(pw[i] > 1e18) pw[i] = 1e18 + 2;
    }
    scanf("%d", &T);
    for(int I = 1; I <= T; I ++){
        scanf("%d%I64d", &n, &m);
        printf("Case #%d: ", I);
        Solve();    
    }
    return 0;
} 

Problem C. Mr. Panda and Strips

题意:一个长度为 N 的序列,要求选出两段不重叠的区间,要求两个区间包含的元素互不相同,最大化两段区间的长度和。

N1000

题解:枚举右区间左端点,然后不断右移右端点,用 std::set 维护左面的情况。 复杂度 O(N2log2N)
Hash_Table’s Code:

#include
#define debug(x) cout<<#x<<"="<
using namespace std;
const int maxn = 1009;

int t[maxn],a[maxn],cnt[maxn];
bool vis[maxn];
int n,T,tot,ans,poi;
pair<int,int> b[maxn];
vector<int> pos[maxn];
setint,int> > S;
setint,int> >::iterator it,tmp;

inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

int getans()
{
    while(!cnt[poi]&&poi) poi--;
    if(poi==0) return 0;
    return poi;
}
void work(int x)
{
    it=S.lower_bound(make_pair(x+1,0));
    if(it==S.begin()) return ;
    it--;
    int L=x,R=x;
    while(true)
    {
        if((*it).secondbreak;
        int l=(*it).first,r=(*it).second;
        cnt[r-l+1]--;
        L=min(L,l);R=max(R,r);
        if(it==S.begin()){S.erase(it);break;}
        tmp=it;it--;S.erase(tmp);
    }
    if(Lint,int> line=make_pair(L,x-1);
        if(S.find(line)==S.end()) S.insert(line),cnt[x-L]++;
    }
    if(xint,int> line=make_pair(x+1,R);
        if(S.find(line)==S.end()) S.insert(line),cnt[R-x]++;
    }
}
void ClearLove()
{
    for(int i=1;i<=tot;i++) pos[i].clear();
    ans=tot=0;
}

int main()
{
    T=read();
    for(int I=1;I<=T;I++)
    {
        n=read();ClearLove();
        for(int i=1;i<=n;i++) a[i]=t[i]=read();
        sort(t+1,t+1+n);tot=unique(t+1,t+1+n)-t-1;
        for(int i=1;i<=n;i++) a[i]=lower_bound(t+1,t+1+tot,a[i])-t,pos[a[i]].push_back(i);
        for(int i=1;i<=n;i++)
        {
            memset(vis,0,sizeof vis);
            int now=i;vis[a[now]]=1;
            while(!vis[a[now+1]]&&now1;
            b[i]=make_pair(i,now);
        }
        for(int i=1;i<=n;i++)
        {
            memset(vis,0,sizeof vis);
            memset(cnt,0,sizeof cnt);
            S.clear();poi=n;
            for(int j=i+1;j<=n;j++) S.insert(b[j]),cnt[b[j].second-b[j].first+1]++;
            for(int j=i;j>=1;j--)
            {
                int x=a[j],sz=pos[x].size();
//              debug(x);
                if(vis[x]) break;vis[x]=1;
                for(int k=0;kint p=pos[x][k];
                    work(p);
                }
                ans=max(ans,i-j+1+getans());
            }
        }
        printf("Case #%d: %d\n",I,ans);
    }
    return 0;
}

Problem D. Ice Cream Tower

题意:有 N 个冰激凌,你需要做出一些大小均为 K 的冰激凌塔。
一个冰激凌塔 {a1,a2,,ak} 需要满足对于任意 i<k ,都满足 2aiai+1
问最多能做出多少个冰激凌塔。

N3×105

题解:二分答案,贪心的从小到大选择,不断填满每一层。因为如果当前冰激凌不能放在第 i 层,显然不能放在第 i+1 层,贪心的正确性得以证明。

My Code:

#include 

using namespace std;
typedef long long ll;
typedef pair<int, int> P;

int n, k;
ll a[300005];
ll b[300005][2]; int tp[300005]; 

int judge(int mid){
    for(int i = 1; i <= mid; i ++){
        b[i][1] = a[i];
    }
    int p = 0, q = 1;
    int pos = mid + 1;
    for(int i = 2; i <= k; i ++){
        for(int j = 1; j <= mid; j ++){
            while(pos <= n && a[pos] < b[j][q] * 2) pos ++;
            if(pos > n) return 0;
            b[j][p] = a[pos];
            pos ++;
        }
        swap(p, q);
    }
    return 1;

}

int main(){
    int T;
    scanf("%d", &T);
    for(int I = 1; I <= T; I ++){
        scanf("%d%d", &n, &k);
        for(int i = 1; i <= n; i ++){
            scanf("%I64d", &a[i]);
        }
        sort(a + 1, a + n + 1);
        int l = 0, r = n / k;
        while(l <= r){
            int mid = (l + r) >> 1;
            if(judge(mid)) l = mid + 1;
            else r = mid - 1;
        }
        printf("Case #%d: %d\n", I, r);
    }
    return 0;
} 

Problem E. Bet

题意:你参加了一次赌球。赌球的规则如下:
一共有 N 支队伍参赛,第 i 支队伍有一个形如 AiBi 的赔率。你可以给每支队伍下注(任意金额)。加入你给一支队伍下注 x 元,如果该队没有胜利则输掉本金,如果该队获胜则返还本金,并另外获得 xBiAi 的奖金。你需要求出最多给多少个队伍下注(给每个队伍下注大于 0 的可以不相同的任意金额),使得任意一个你下注的队伍获胜,你都可以赚钱。

N100

题解:考虑你下注的每一支队伍,都需要满足:(设 pi 为给第 i 支队伍下的赌注占总赌注的比例)

pi(1+BiAi)>1

pi>AiAi+Bi

由于 pi=1 所以可以贪心的按照 AiAi+Bi 排序,然后从小到大选。需要用 long double 。 复杂度 O(Nlog2N)

My Code:

#include 

using namespace std;
typedef long long ll;
typedef pair<int, int> P;
const ll mod = 1e9 + 7;

long double d[100005];

int n, m;

int main(){
    int T;
    scanf("%d", &T);
    for(int I = 1; I <= T; I ++){
        scanf("%d", &n);
        for(int i = 1; i <= n; i ++){
            double x, y;
            scanf("%lf:%lf", &x, &y);
            int xx = (int)(x * 1000 + 0.01);
            int yy = (int)(y * 1000 + 0.01);
            d[i] = 1.0 * xx / (long double)(xx + yy);
        }
        sort(d + 1, d + n + 1);

        long double sum = 0;
        int cnt = 0;
        for(int i = 1; i <= n; i ++){
            sum = sum + d[i];
            if(sum >= 1) break;
            cnt++;
        }
        printf("Case #%d: %d\n", I, cnt);  
    }
    return 0;
} 

Problem F. Mr. Panda and Fantastic Beasts

题意:有 n 个小写字母构成的字符串 s1,s2,sn ,定义「魔咒」为仅为 s1 的子串,且长度最小(长度相同的取字典序最小)的字符串。求出「魔咒」或者回答其不存在。

n5×104,ni=1|si|3×106

题解,把 n 个字符串用分隔符隔开建一个后缀数组。对于每一个属于 s1 的后缀,求出与他的 lcp 最长的不属于 s1 的后缀 (可以利用 height 数组的单调性用指针维护),然后答案是 s1 与其他后缀 lcp 的最大的位置开始的长度为 lcp+1 的子串。

My Code:

#include 

using namespace std;
typedef long long ll;
typedef pair<int, int> P;

int sa[300005], wys[300005], wv[300005], rk[300005], rk2[300005], h[300005];

int cal(int *r, int i, int j, int l){
    return r[i] == r[j] && r[i + l] == r[j + l];
}

void DA(int *r, int *sa, int n, int m){
    int *x = rk, *y = rk2, *t;
    for(int i = 1; i <= m; i ++) wys[i] = 0;
    for(int i = 1; i <= n; i ++) wys[x[i] = r[i]] ++;
    for(int i = 1; i <= m; i ++) wys[i] += wys[i - 1];
    for(int i = n; i >= 1; i --) sa[wys[x[i]] --] = i;
    for(int j = 1, p = 1; j <= n; j <<= 1, m = p){
        p = 0;
        for(int i = n - j + 1; i <= n; i ++) y[++p] = i;
        for(int i = 1; i <= n; i ++) if(sa[i] - j > 0) y[++p] = sa[i] - j;
        for(int i = 1; i <= n; i ++) wv[i] = x[y[i]];
        for(int i = 1; i <= m; i ++) wys[i] = 0;
        for(int i = 1; i <= n; i ++) wys[wv[i]] ++;
        for(int i = 1; i <= m; i ++) wys[i] += wys[i - 1];
        for(int i = n; i >= 1; i --) sa[wys[wv[i]] --] = y[i];
        t = x; x = y; y = t; p = 2; x[sa[1]] = 1;
        for(int i = 2; i <= n; i ++){
            x[sa[i]] = cal(y, sa[i], sa[i - 1], j) ? p - 1 : p++;
        }
    }
} 

void calheight(int *r, int *sa, int n, int m){
    int k = 0;
    for(int i = 1; i <= n; i ++) rk[sa[i]] = i;
    for(int i = 1; i <= n; i ++){
        if(rk[i] == 1) {h[rk[i]] = 0; continue;}
        int j = sa[rk[i] - 1];
        while(r[i + k] == r[j + k]) k ++;
        h[rk[i]] = k;
        if(k > 0) k--;
    }
}

int a[300005][20];
int lg[300005];
int N;
void build_rmq(){
    int t1 = -1, t2 = 1;
    for(int i = 1; i <= N; i ++){
        if(t2 <= i) t2 <<= 1, t1 ++;
        lg[i] = t1;
    }
    for(int i = 1; i <= N; i ++){
        a[i][0] = h[i];
    }
    for(int j = 1; j <= lg[N]; j ++){
        for(int i = 1; i + (1 << j) - 1 <= N; i ++){
            a[i][j] = min(a[i][j - 1], a[i + (1 << (j - 1))][j - 1]);
        }
    }
}

int lcp(int l, int r){
    if(l == r) return N - sa[l] + 1;
    l++;
    int ln = r - l + 1;
    //printf("%d %d %d %d\n", l, r, lg[ln], r - (1 << lg[ln]) + 1);;
    return min(a[l][lg[ln]], a[r - (1 << lg[ln]) + 1][lg[ln]]);
}

int n;
int rs[300005];
char ch[300005];
int bl[300005], len[300005];
int pre[300005], nxt[300005];
void Solve(){
    scanf("%d", &n);
    N = 1;
    rs[1] = 128;
    for(int i = 1; i <= n; i ++){
        scanf("%s", ch + 1);
        len[i] = strlen(ch + 1);
        for(int j = 1; j <= len[i]; j ++){
            rs[N + j] = ch[j];
            bl[N + j] = i;
        }
        N += len[i];
        N ++;
        rs[N] = 128 + i;
    }
    DA(rs, sa, N, 128 + n);
    calheight(rs, sa, N, 128 + n);
    build_rmq();
    int pos = 0;
    for(int i = N; i >= 1; i --){
        nxt[i] = pos;
        if(bl[sa[i]] != 1){
            pos = i;
        }
    }
    pos = 0;
    for(int i = 1; i <= N; i ++){
        pre[i] = pos;
        if(bl[sa[i]] != 1){
            pos = i;
        }
    }
    int ans = 0x3f3f3f3f; pos = 0;
    for(int i = 2; i <= len[1] + 1; i ++){
        int tmp = 0;
        if(pre[rk[i]]){
            tmp = max(tmp, lcp(pre[rk[i]], rk[i]));
        }
        if(nxt[rk[i]]){
            tmp = max(tmp, lcp(rk[i], nxt[rk[i]]));
        }
        tmp = tmp + 1;
        //printf("%d %d %d %d %d\n", rk[i], pre[rk[i]], nxt[rk[i]], lcp(rk[i], pre[rk[i]]), lcp(rk[i], nxt[rk[i]]));
        if(tmp > len[1] - i + 2) continue;
        if(tmp < ans || (tmp == ans && rk[i] < rk[pos])){
            ans = tmp;
            pos = i;
        }
    }
    if(ans > len[1]){
        printf("Impossible\n");
    }else{
        for(int i = 1; i <= ans; i ++){
            putchar(rs[pos + i - 1]);
        }
        putchar('\n');
    }
}

int main(){
    int T;
    scanf("%d", &T);
    for(int I = 1; I <= T; I ++){
        printf("Case #%d: ", I);
        Solve();
    }
    return 0;
} 

Problem G. Pandaria

坑。

Problem H. Great Cells

题意:有一个 N M 列的矩阵,每一个格子是 [1,k] 之间的一个整数。
定义一个格子是吼的,当且仅当该格子的值严格大于与其同行同列的所有格子种的值。定义 Ag 为恰好有 g 个吼的格子的互不相同的矩阵数目,求:

g=0NM(g+1)Agmod 109+7

题解:
(总感觉这道题那么眼熟,好像是之前做过的某场NOIP模拟赛?)
先把式子拆一下:

g=0NMgAg+g=0NMAg

后者为所有矩阵的数目。
g=0NMgAg+kNM

前者可以看成所有的矩阵的吼的格子的数目之和。
如果一个格子是吼的,那么该行该列所有的数都要严格小于该格子中的数。剩余 N1 M1 列的格子可以随便填,那么我们枚举这个数就可以了。

最后的答案为:

NMi=0k(i1)N1+M1k(N1)(M1)+kNM

My Code:

#include 

using namespace std;
typedef long long ll;
typedef pair<int, int> P;
const ll mod = 1e9 + 7;
ll qpow(ll a, ll b){
    ll ret = 1;
    for(; b; b >>= 1, a = a * a % mod){
        if(b & 1) ret = ret * a % mod;
    }
    return ret;
} 
int n, m, k;

int main(){
    int T;
    scanf("%d", &T);
    for(int I = 1; I <= T; I ++){
        scanf("%d%d%d", &n, &m, &k);
        ll ans = 0;
        for(int i = 1; i <= k; i ++){
            ans = (ans + qpow(i - 1, n - 1 + m - 1) * qpow(k, (n - 1) * (m - 1)) % mod * n % mod * m % mod) % mod;
        }
        ans = (ans + qpow(k, n * m)) % mod;
        printf("Case #%d: %I64d\n", I, ans);
    }
    return 0;
} 

Problem I. Cherry Pick

坑。

Problem J. Mr.Panda and TubeMaster

坑。(据说是清华集训某题,Hash_Table A掉了,我还是太蒻了,做不了清华集训)。

Problem K. Justice Rains From Above

坑。

Problem L. World Cup

题意:世界杯小组赛,每组有 4 支队伍,每两组队伍之间比赛一场。每场胜者得 3 分,负者得 0 分,平局各得 1 分。给出 4 个队伍的得分,回答能否根据该得分确定所有的比赛的胜负情况,或者回答得分不合法。

题解:签到题。暴力枚举每种胜负情况,检查每种得分是否只有一种情况对应既可。

My Code:

#include 

using namespace std;
typedef long long ll;
typedef pair<int, int> P;

int T; 
int f[10][10][10][10];
int a[10], b[10], tp;
int vis[5];
int main(){
    scanf("%d", &T);
    int N = 729;
    for(int i = 1; i <= 3; i ++){
        for(int j = i + 1; j <= 4; j ++){
            a[tp] = i; b[tp] = j; tp++;
        }
    }
    for(int i = 0; i < N; i ++){
        int tmp = i; memset(vis, 0, sizeof(vis));
        for(int j = 0; j < 6; j ++){
            if(tmp % 3 == 0){
                vis[a[j]] += 3;
            }else if(tmp % 3 == 1){
                vis[b[j]] += 3;
            }else{
                vis[a[j]] += 1; vis[b[j]] += 1;
            }
            tmp /= 3;
        }
        f[vis[1]][vis[2]][vis[3]][vis[4]] ++;
    }

    for(int i = 1; i <= T; i ++){
        int x1, x2, x3, x4;
        scanf("%d%d%d%d", &x1, &x2, &x3, &x4);
        printf("Case #%d: ", i);
        if(x1 >= 10 || x2 >= 10 || x3 >= 10 || x4 >= 10){
            printf("Wrong Scoreboard\n");
            continue;
        }
        if(f[x1][x2][x3][x4] == 1){
            printf("Yes\n");
        }else if(f[x1][x2][x3][x4] == 0){
            printf("Wrong Scoreboard\n");
        }else{
            printf("No\n"); 
        }
    }
    return 0;
} 

你可能感兴趣的:(codeforces,ACM)