1. 程式人生 > >2016-2017 ACM-ICPC CHINA-Final 解題報告

2016-2017 ACM-ICPC CHINA-Final 解題報告

題目連結

A. Number Theory Problem

題意:給你一個數N,求形如2k-1且小於2N的數中有多少能被7整除。

解法:觀察二進位制位找規律,答案是N/3。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;

int main() {
    int T,kase=0;
    scanf("%d",&T);
    while(T--) {
        scanf("%d",&n);
        printf("Case #%d: %d\n
",++kase,n/3); } return 0; }
View Code

 

B. Hemi Palindrome

題意:一個串是半迴文串當且僅當它的奇數位是迴文數或偶數位是迴文串。給你兩個數N,K,求長度為N的01串中字典序第K小的半迴文串。

解法:數位dp。從高到低列舉這個串的每一位,首先把這個位置上的數置0,然後算出以已放過數的位為字首的半迴文串有多少個,如果大於K則改為置1並把K減掉相應的個數,否則繼續放下一位。

需要考慮的細節很多,如中心點的選取,臨界判斷,容斥,溢位處理等等。

#include<bits/stdc++.h>
using
namespace std; typedef long long ll; const int N=1e5+10; int bit[N],n,mid,flag[2],kase=0; ll k; int cntall(int l,int r) {return r-l+1;} int cntodd(int l,int r) {return (cntall(l,r)&1)&&(r&1)?cntall(l,r)/2+1:cntall(l,r)/2;} int cnteven(int l,int r) {return cntall(l,r)-cntodd(l,r);} ll f(
int a,int b,int c) { if(a>=62||b>=62)return 1ll<<62; return (a<0?0:(1ll<<a))+(b<0?0:(1ll<<b))-(c<0?0:(1ll<<c)); } void solve() { flag[0]=flag[1]=0; for(int i=1; i<=n; ++i) { if(i<=mid) { bit[i]=0; int a=cntall(i+1,mid)+cntodd(mid+1,n); int b=cntall(i+1,mid)+cnteven(mid+1,n); int c=cntall(i+1,mid); ll t=f(a,b,c); if(t<k)bit[i]=1,k-=t; } else { int op=n&1?n-i+1:(i&1?n-i:n-i+2); bit[i]=0; if(bit[i]!=bit[op])flag[i&1]++; int a=flag[0]?-1:cntodd(i+1,n); int b=flag[1]?-1:cnteven(i+1,n); int c=flag[0]||flag[1]?-1:0; ll t=f(a,b,c); if(t<k) { if(bit[i]!=bit[op])flag[i&1]--; bit[i]=1,k-=t; if(bit[i]!=bit[op])flag[i&1]++; } } } printf("Case #%d: ",++kase); if(k==1)for(int i=1; i<=n; ++i)printf("%d",bit[i]); else printf("NOT FOUND!"); printf("\n"); } int main() { int T; scanf("%d",&T); while(T--) { scanf("%d%lld",&n,&k); mid=(n+1)/2; if(!(n&1)&&((n/2)&1))mid++; solve(); } return 0; }
View Code

 

C. Mr. Panda and Strips

題意:給你一個數列,讓你選擇兩段連續的區間(區間長度可以為0),使兩段區間內所有的數互不相同,且區間長度儘可能大。求最大的區間長度。

解法:暴力列舉第一個區間,然後利用單調性尺取第二個區間。理論複雜度為O(N3),但加上剪枝會快很多。

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N=1000+10;
const int M=1e5+10;
int a[N],c[M],n;

int main() {
    int T,kase=0;
    scanf("%d",&T);
    while(T--) {
        int ans=0;
        memset(c,0,sizeof c);
        scanf("%d",&n);
        for(int i=0; i<n; ++i)scanf("%d",&a[i]);
        int l,r,L,R;
        for(l=0,r=-1; l<n; c[a[l++]]--) {
            if(n-l<=ans)break;
            while(r<l-1)c[a[++r]]++;
            while(r+1<n&&c[a[r+1]]<=0) {
                c[a[++r]]++;
                for(L=r+1,R=L-1; L<n; c[a[L++]]--) {
                    if(n-L+r-l+1<=ans)break;
                    while(R+1<n&&c[a[R+1]]<=0)c[a[++R]]++;
                    ans=max(ans,R+r-L-l+2);
                }
                while(R<L-1)c[a[++R]]++;
                while(R>=L)c[a[R--]]--;
            }
            while(r>=l)c[a[r--]]--;
        }
        printf("Case #%d: %d\n",++kase,ans);
    }
    return 0;
}
View Code

 

D. Ice Cream Tower

題意:有N個冰淇淋球,每K個可以組成一個冰淇淋塔,但要求下面的冰淇淋球的大小不得小於上面的2倍。給你每個冰淇淋球的大小,求最多能做多少個冰淇淋塔。

解法:如果能做x個冰淇淋塔的話,那麼最優做法必然是先取最小的x個冰淇淋球作為第一層,然後一層一層往下鋪。可以把冰淇淋球從小到大排序,然後二分列舉x的值,依次判斷能否做成即可。複雜度O(NlogN)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e5+10;
int n,k;
ll a[N],b[N];

bool ok(int x) {
    for(int i=0; i<x; ++i)b[i]=a[i];
    int t=0,cnt=1;
    for(int i=x; i<n; ++i) {
        if(a[i]/b[t]>=2) {
            if(t==x-1)++cnt;
            b[t]=a[i],t=(t+1)%x;
        }
    }
    return cnt>=k;
}

int main() {
    int T,kase=0;
    scanf("%d",&T);
    while(T--) {
        scanf("%d%d",&n,&k);
        for(int i=0; i<n; ++i)scanf("%lld",&a[i]);
        sort(a,a+n);
        int l=0,r=n;
        while(l<r) {
            int mid=(l+r+1)>>1;
            ok(mid)?l=mid:r=mid-1;
        }
        printf("Case #%d: %d\n",++kase,l);
    }
    return 0;
}
View Code

 

E. Bet

題意:你去投注一場比賽,這場比賽一共有N支隊伍,每支隊伍都有一個賠率Ai:Bi,代表假如你投進x元,如果該隊伍贏了,那麼你將得到x*(1+Bi/Ai)元,否則什麼都得不到。問你最多能投注多少支隊伍,使得只要你投注的任意一支隊伍獲勝,你就不會賠錢。

解法:假設你總共投注了x元錢,那麼你至少要在每支隊伍上投x/(1+Bi/Ai)元才能保證回本。把所有隊伍按Bi/Ai的大小從大到小排序,然後從0開始加上每支隊伍的1/(1+Bi/Ai),加到超過1為止,加過的隊伍數就是最終答案。

這道題卡精度,用Java自帶的高精度浮點數可破,long double加上一點玄學也可破。我求穩只寫Java的好了。

import java.util.*;
import java.io.*;
import java.math.*;

public class Main {
    public static void main(String[] args) throws Exception {
        //System.setIn(new FileInputStream("i.txt"));
        Scanner in = new Scanner(System.in);
        int kase = 0;
        for (int T = in.nextInt(); T > 0; T--) {
            int n = in.nextInt();
            BigDecimal[] a = new BigDecimal[n];
            for (int i = 0; i < n; ++i) {
                String s = in.next();
                String[] s2 = s.split(":");
                BigDecimal x = new BigDecimal(s2[0]);
                BigDecimal y = new BigDecimal(s2[1]);
                a[i] = BigDecimal.ONE.divide(BigDecimal.ONE.add(y.divide(x, 20, RoundingMode.UP)), 20, RoundingMode.UP);
            }
            Arrays.sort(a);
            int ans = 0;
            BigDecimal now = BigDecimal.ZERO;
            while (ans < n) {
                now = now.add(a[ans]);
                if (now.compareTo(BigDecimal.ONE) >= 0)
                    break;
                ans++;
            }
            System.out.printf("Case #%d: %d\n", ++kase, ans);
        }
        in.close();
    }
}
View Code

 

F. Mr. Panda and Fantastic Beasts

題意:給你N個字串,讓你從第一個字串找到一個最短的並且沒有在其他字串中出現過的子串,如果有多解,輸出字典序最小的。

解法:字尾陣列或字尾自動機。

字尾陣列解法:把所有字串用沒有出現過的字元連線起來求字尾陣列,把第一個字串中所有的字尾放進集合A,把其他字串中的字尾放進集合B。依次列舉集合A中的每個字尾,二分或者利用單調性找到B中和該字尾字典序最接近的一個或兩個字尾,求出它們的lcp,則該字尾的位置上長度大於lcp的子串都沒有在B中出現,依次更新答案即可。複雜度O(NlogN)(二分),O(N)(單調性)。

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=3e5+10;
char s[N],buf[N];
int n,k,len1,m,kase=0;
vector<int> v1,v2;
struct SA {
    static const int N=3e5+10;
    int bufa[N],bufb[N],c[N],sa[N],rnk[N],height[N],ST[N][20];
    void getsa(char* s,int n,int m=300) {
        int *x=bufa,*y=bufb;
        x[n]=y[n]=-1;
        for(int i=0; i<m; ++i)c[i]=0;
        for(int i=0; i<n; ++i)c[x[i]=s[i]]++;
        for(int i=1; i<m; ++i)c[i]+=c[i-1];
        for(int i=n-1; i>=0; --i)sa[--c[x[i]]]=i;
        for(int k=1,p=0; k<n; k<<=1,m=p,p=0) {
            for(int i=n-k; i<n; ++i)y[p++]=i;
            for(int i=0; i<n; ++i)if(sa[i]>=k)y[p++]=sa[i]-k;
            for(int i=0; i<m; ++i)c[i]=0;
            for(int i=0; i<n; ++i)c[x[y[i]]]++;
            for(int i=1; i<m; ++i)c[i]+=c[i-1];
            for(int i=n-1; i>=0; --i)sa[--c[x[y[i]]]]=y[i];
            swap(x,y),x[sa[0]]=0,p=1;
            for(int i=1; i<n; ++i)x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]?p-1:p++;
            if(p>=n)break;
        }
    }
    void getheight(char* s,int n) {
        for(int i=0; i<n; ++i)rnk[sa[i]]=i;
        for(int i=0,k=0; i<n; ++i) {
            if(!rnk[i])continue;
            if(k)k--;
            while(s[i+k]==s[sa[rnk[i]-1]+k])k++;
            height[rnk[i]]=k;
        }
        height[0]=height[n]=0;
    }
    void initST(int n) {
        for(int i=0; i<n; ++i)ST[i][0]=height[i];
        for(int j=1; (1<<j)<=n; ++j)
            for(int i=0; i+(1<<j)-1<n; ++i)
                ST[i][j]=min(ST[i][j-1],ST[i+(1<<(j-1))][j-1]);
    }
    void build(char* s,int n) {
        getsa(s,n);
        getheight(s,n);
        initST(n);
    }
    int lcp(int l,int r) {
        if(l==r)return n-sa[l];
        if(l>r)swap(l,r);
        l++;
        int k=0;
        while(1<<(k+1)<=r-l+1)k++;
        return min(ST[l][k],ST[r-(1<<k)+1][k]);
    }
    void solve() {
        v1.clear();
        v2.clear();
        for(int i=0; i<n; ++i) {
            if(sa[i]<len1)v1.push_back(sa[i]);
            else v2.push_back(sa[i]);
        }
        int ansi,anslen=INF;
        for(int i=0,j=0; i<v1.size(); ++i) {
            while(j<v2.size()&&rnk[v2[j]]<rnk[v1[i]])++j;
            int maxlen=-1;
            if(j<n)maxlen=max(maxlen,lcp(rnk[v1[i]],rnk[v2[j]]));
            if(j>0)maxlen=max(maxlen,lcp(rnk[v1[i]],rnk[v2[j-1]]));
            if(maxlen<anslen&&v1[i]+maxlen<len1)anslen=maxlen,ansi=v1[i];
        }
        printf("Case #%d: ",++kase);
        if(anslen!=INF)for(int i=ansi; i<=ansi+anslen; ++i)printf("%c",s[i]);
        else printf("Impossible");
        printf("\n");
    }
} sa;
int main() {
    int T;
    scanf("%d",&T);
    while(T--) {
        n=0;
        scanf("%d",&m);
        for(int i=0; i<m; ++i) {
            scanf("%s",buf);
            int len=strlen(buf);
            if(i==0)len1=len;
            else s[n++]='z'+1;
            for(int i=0; i<len; ++i)s[n++]=buf[i];
        }
        s[n]='\0';
        sa.build(s,n);
        sa.solve();
    }
    return 0;
}
View Code

字尾自動機解法:把除第一個字串以外的所有字串用沒有出現過的字元連線起來建立字尾自動機,拿第一個字串在自動機上跑一遍,每次失配的時候找到失配點對應的最短子串更新答案即可。由於字尾自動機對字典序的處理不如字尾陣列優秀,更新字典序的時候依然要用暴力,不過平攤下來的複雜度還是O(1)的,因此總的複雜度為O(N),比字尾陣列略快一點。

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=3e5+10;
char s1[N],buf[N];
int m,len1,kase=0;

struct SAM {
    static const int N=5e5+10;
    static const int M=27;
    static const int root=0;
    int go[N][M],pre[N],maxlen[N],nnode,last,anslen,ansr;
    void init() {
        last=nnode=0;
        newnode(0);
        pre[root]=-1;
    }
    int newnode(int l) {
        memset(go[nnode],0,sizeof go[nnode]);
        maxlen[nnode]=l;
        return nnode++;
    }
    void extend(int ch) {
        int p=last,np=last=newnode(maxlen[p]+1);
        while(~p&&!go[p][ch])go[p][ch]=np,p=pre[p];
        if(!~p)pre[np]=root;
        else {
            int q=go[p][ch];
            if(maxlen[q]==maxlen[p]+1)pre[np]=q;
            else {
                int nq=newnode(maxlen[p]+1);
                memcpy(go[nq],go[q],sizeof go[nq]);
                pre[nq]=pre[q],pre[q]=pre[np]=nq;
                while(go[p][ch]==q)go[p][ch]=nq,p=pre[p];
            }
        }
    }
    void solve() {
        anslen=INF;
        for(int i=0,p=0; i<len1; ++i) {
            int ch=s1[i]-'a';
            while(~p&&!go[p][ch]) {
                int minlen=p?maxlen[pre[p]]+1:0;
                minlen++;
                if(minlen<anslen||(minlen==anslen&&strncmp(s1+i-minlen+1,s1+ansr-minlen+1,minlen)<0))ansr=i,anslen=minlen;
                p=pre[p];
            }
            if(!~p)p=root;
            p=go[p][ch];
        }
        printf("Case #%d: ",++kase);
        if(anslen==INF)printf("Impossible\n");
        else {
            for(int i=ansr-anslen+1; i<=ansr; ++i)printf("%c",s1[i]);
            printf("\n");
        }
    }
} sam;

int main() {
    int T;
    scanf("%d",&T);
    while(T--) {
        sam.init();
        scanf("%d",&m);
        scanf("%s",s1);
        len1=strlen(s1);
        for(int i=1; i<m; ++i) {
            scanf("%s",buf);
            int len=strlen(buf);
            if(i>1)sam.extend('z'-'a'+1);
            for(int j=0; j<len; ++j)sam.extend(buf[j]-'a');
        }
        sam.solve();
    }
    return 0;
}
View Code

 

G. Pandaria

題意:給你一個有N個點和M條邊的無向圖,圖上每個點都有一個編號代表顏色,每條邊都有一個權值。然後給出Q組詢問,每組詢問有兩個值x,w,讓你求出從x點出發,所經過的所有邊的權值都不超過w的條件下,能夠到達的所有點中顏色最多的是哪一種。如果有多個答案,輸出編號最小的顏色。要求強制線上。

解法:可持久化並查集+線段樹合併+樹上二分(倍增)。

首先對每個點建一棵權值線段樹,記錄這個點所在集合中每種顏色出現了多少次以及出現次數最多的顏色。然後把所有邊按權值從小到大掃一遍,把各個點依次加進並查集。在合併兩個集合的時候,需要新建一個結點,把兩個集合的父節點指向它,並把兩個集合對應的權值線段樹合併,把答案更新到這個結點,同時在該結點上標註上合併這兩個集合的邊的花費。最後對可持久化後的並查集求出倍增陣列,對每組詢問二分找到對應時刻的答案即可。複雜度O(NlogN)。

#include<bits/stdc++.h>

using namespace std;
const int N=2e5+10;
int col[N],fa[N],f[N][20],ans[N],rt[N],cost[N],ntree,ndsu;
int mx[N*20],val[N*20],ls[N*20],rs[N*20];
int n,m,Q,kase=0;

struct Edge {
    int u,v,c;
    bool operator<(const Edge& b)const {
        return c<b.c;
    }
} e[N];

void init() {
    ntree=ndsu=0;
    fa[0]=f[0][0]=0;
    mx[0]=0;
}

int father(int u) {
    return fa[u]?fa[u]=father(fa[u]):u;
}

void pushup(int u) {
    mx[u]=max(mx[ls[u]],mx[rs[u]]);
    if(mx[u]==mx[ls[u]])val[u]=val[ls[u]];
    else val[u]=val[rs[u]];
}

int build(int l,int r,int x) {
    int u=++ntree;
    ls[u]=rs[u]=0;
    if(l==r) {
        mx[u]=1;
        val[u]=l;
    } else {
        int mid=(l+r)>>1;
        if(x<=mid)ls[u]=build(l,mid,x);
        else rs[u]=build(mid+1,r,x);
        pushup(u);
    }
    return u;
}

int Merge(int u,int v,int l,int r) {
    if(!u)return v;
    if(!v)return u;
    if(l==r) {
        mx[u]+=mx[v];
    } else {
        int mid=(l+r)>>1;
        ls[u]=Merge(ls[u],ls[v],l,mid);
        rs[u]=Merge(rs[u],rs[v],mid+1,r);
        pushup(u);
    }
    return u;
}

void db() {
    for(int j=1; j<20; ++j)
        for(int i=1; i<=ndsu; ++i)
            f[i][j]=f[f[i][j-1]][j-1];
}

int main() {
    int T;
    scanf("%d",&T);
    while(T--) {
        printf("Case #%d:\n",++kase);
        init();
        scanf("%d%d",&n,&m);
        for(int i=1; i<=n; ++i)scanf("%d",&col[i]);
        for(int i=0; i<m; ++i)scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].c);
        for(int i=1; i<=n; ++i) {
            fa[i]=f[i][0]=0;
            cost[i]=0;
            rt[i]=build(1,n,col[i]);
            ans[i]=val[rt[i]];
        }
        ndsu=n;
        sort(e,e+m);
        for(int i=0; i<m; ++i) {
            int u=e[i].u,v=e[i].v,c=e[i].c;
            int fu=father(u),fv=father(v);
            if(fu==fv)continue;
            int w=++ndsu;
            fa[w]=f[w][0]=0;
            cost[w]=c;
            fa[fu]=fa[fv]=f[fu][0]=f[fv][0]=w;
            rt[w]=Merge(rt[fu],rt[fv],1,n);
            ans[w]=val[rt[w]];
        }
        db();
        scanf("%d",&Q);
        int last=0;
        while(Q--) {
            int u,c;
            scanf("%d%d",&u,&c);
            u^=last,c^=last;
            for(int j=19; j>=0; --j)if(f[u][j]&&cost[f[u][j]]<=c)u=f[u][j];
            printf("%d\n",ans[u]);
            last=ans[u];
        }
    }
    return 0;
}
View Code

還有一種比較偷懶的解法是用map代替權值線段樹,比線段樹略慢一點,思路都差不多就不再寫了。

 

H. Great Cells

題意:給你一個N*M的矩陣,矩陣上每個數的取值範圍是[1,K],如果該矩陣一個格子上的數比它所在的行以及列上的其他格子上的所有數都大,那麼這個格子就稱作Great cell。求∑ (g=0,NM) (g + 1) · Ag mod (1e9 + 7)的值,Ag代表使得該矩陣中恰有g個Great cell的狀態總數。

解法:雖然這個式子看上去非常玄乎,但其實每個格子作為Great cell出現的局面總數加起來就是這個答案。隨便選一個格子,如果這個格子是Great cell,那麼它所在的行和列上的格子都必須比它小,其他的格子則不受限制。由於所有的格子都是等價的,所以最後乘上N*M即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
ll n,m,k;

ll Pow(ll x,ll p) {
    ll ret=1;
    while(p) {
        if(p&1)ret=ret*x%mod;
        x=x*x%mod;
        p>>=1;
    }
    return ret;
}

int main() {
    int T,kase=0;
    scanf("%d",&T);
    while(T--) {
        ll ans=0;
        scanf("%lld%lld%lld",&n,&m,&k);
        ans+=Pow(k,n*m);
        for(ll i=2; i<=k; ++i)ans=(ans+Pow(i-1,n+m-2)%mod*Pow(k,(n-1)*(m-1))%mod*n%mod*m%mod)%mod;
        printf("Case #%d: %lld\n",++kase,ans);
    }
    return 0;
}
View Code

 

I. Cherry Pick

留坑

 

J. Mr.Panda and TubeMaster

留坑

 

K. Justice Rains From Above

留坑

 

L. World Cup

題意:有4支隊伍,每兩支隊伍之間打一場比賽,贏了計3分,平局計1分,輸了不計分。給出賽後每支隊伍的總分,問是否合理,如果合理的話求出每兩支隊伍之間的比賽勝負情況是否唯一。

解法:一共就36=729種情況,依次枚舉出來看看是否合適即可。

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int score[4],cnt;
int a[][2]= {
    {0,1},
    {0,2},
    {0,3},
    {1,2},
    {1,3},
    {2,3},
};

void dfs(int now) {
    if(now==6) {
        if(score[0]==0&&score[1]==0&&score[2]==0&&score[3]==0)++cnt;
        return;
    }
    score[a[now][0]]-=3;
    dfs(now+1);
    score[a[now][0]]+=3;
    score[a[now][0]]-=1;
    score[a[now][1]]-=1;
    dfs(now+1);
    score[a[now][0]]+=1;
    score[a[now][1]]+=1;
    score[a[now][1]]-=3;
    dfs(now+1);
    score[a[now][1]]+=3;
}

int main() {
    int T,kase=0;
    scanf("%d",&T);
    while(T--) {
        printf("Case #%d: ",++kase);
        cnt=0;
        for(int i=0; i<4; ++i)scanf("%d",&score[i]);
        dfs(0);
        if(cnt==1)puts("Yes");
        else if(cnt==0)puts("Wrong Scoreboard");
        else puts("No");
    }
    return 0;
}
View Code