2016 China-Final 解题报告

来源:互联网 发布:java web书籍推荐 编辑:程序博客网 时间:2024/04/29 23:43

这几天又把China-Final的题补了一些,比较蛋疼的是为啥根本搜不到题解。。。我觉得我要多加些关键字,2016 ICPC China-Final, codeforces gym101194。题目在cf可以交,因为只有PDF就不每一题都加链接了。http://codeforces.com/gym/101194

A. Number Theory Problem

7的二进制表示为111,所以111, 1110(111移位的结果)都能被7整除。而2k1的二进制表示为111,共k个1。所以只需要k被3整除即可。

#include <bits/stdc++.h>using namespace std;typedef long long ll;typedef pair<int,int> PII;int main(){    int T,n;    scanf("%d",&T);    for (int t=1;t<=T;t++) {        scanf("%d",&n);        printf("Case #%d: %d\n",t,n/3);    }    return 0;}

C. Mr. Panda and Strips

这题据说比赛的时候O(n4)加优化就能过。。。反正我们也没试。。。
我最开始看到的是出题人的题解,消化了很久并且按照他讲的写了,结果WA了以后仔细改了改又TLE了。然后再回去看他的题解,感觉错误不止一个啊。。。= =
我出于无奈只好直接问q巨要了一份代码。。。

//By quailty, contest: 2016-2017 ACM-ICPC CHINA-Final, problem: (C) Mr. Panda and Strips, Accepted, ##include<cstdio>#include<cstring>#include<cstdlib>#include<cmath>#include<iostream>#include<algorithm>#include<set>using namespace std;const int MAXN=1005;const int MAXV=100005;int c[MAXN],r[MAXN];bool vis[MAXV];vector<int>loc[MAXV];set<pair<int,int> >st;multiset<int>len;inline int getMax(){    return (len.empty() ? 0 : *(--len.end()));}inline void setBlock(int x){    auto itr=st.lower_bound(make_pair(x+1,0));    if(itr==st.begin())return;    itr--;    int ml=x,mr=x;    while(1)    {        if(itr->second<x)break;        len.erase(len.lower_bound(itr->second-itr->first+1));        ml=min(ml,itr->first);        mr=max(mr,itr->second);        if(itr==st.begin())        {            st.erase(itr);            break;        }        else st.erase(itr--);    }    if(ml<x && st.find(make_pair(ml,x-1))==st.end())        st.insert(make_pair(ml,x-1)),len.insert(x-ml);    if(mr>x && st.find(make_pair(x+1,mr))==st.end())        st.insert(make_pair(x+1,mr)),len.insert(mr-x);}int main(){    int T;    scanf("%d",&T);    for(int ca=1;ca<=T;ca++)    {        int n;        scanf("%d",&n);        for(int i=1;i<=n;i++)        {            scanf("%d",&c[i]);            loc[c[i]].push_back(i);        }        for(int i=1;i<=n;i++)        {            for(int j=1;j<=n;j++)vis[c[j]]=0;            r[i]=i-1;            while(r[i]<n && !vis[c[r[i]+1]])vis[c[++r[i]]]=1;        }        int res=0;        for(int i=1;i<=n;i++)        {            for(int j=1;j<=n;j++)vis[c[j]]=0;            st.clear(),len.clear();            for(int j=i+1;j<=n;j++)            {                st.insert(make_pair(j,r[j]));                len.insert(r[j]-j+1);            }            res=max(res,getMax());            for(int j=i;j>=1;j--)            {                if(vis[c[j]])break;                vis[c[j]]=1;                for(int k=0;k<(int)loc[c[j]].size();k++)                    if(loc[c[j]][k]>i)setBlock(loc[c[j]][k]);                res=max(res,getMax()+(i-j+1));            }        }        printf("Case #%d: %d\n",ca,res);        for(int i=1;i<=n;i++)            loc[c[i]].clear();    }    return 0;}

这个代码的意思大概就是先预处理每个颜色分布在哪几个位置,每个位置最多能延伸的区间。然后枚举第一个区间的右边界i,再枚举左边界jj每减小1,就用新加入的颜色a[j]在之后出现的位置 x(a[x]==a[j], x>j) 去截保存在set里的一些区间(所有满足 lxr 的区间)。对于每一个x,在全截完了以后再插入在x左边的最长的[ml,x1],在x右边的最长的[x+1,mr]。代码中multiset<int> len是辅助的保存长度的有序列。
这个算法对于每一个i都是先插入O(n)个区间,再删除和插入O(n)个区间,显然复杂度是O(n2logn)。不过时间达到了3.5s,原因是常数太大了,操作的区间总个数更接近于4n

后来又看到一份代码(是cf上的vj号交的,所以也不知道是哪位大侠)感觉很不错,跟这个其实是一个思路的,于是就学习了一下。
预处理时采用了时间戳的int数组,也可以像上面那样每次清空bool数组。

#include <bits/stdc++.h>using namespace std;typedef pair<int,int> PII;const int N=1005,MAX=1e5+5;vector<int> p[MAX];int a[N],dp[N][N];bool isok[N][N];int vis[MAX];set<PII> ival;multiset<int> q;void split(int x){    auto it=ival.lower_bound(PII(x+1,0));    if (it==ival.begin()) return;    it--;    int a=it->first,b=it->second;    ival.erase(it);    q.erase(q.find(dp[a][b]));    if (a<x) {        ival.insert(PII(a,x-1));        q.insert(dp[a][x-1]);    }    if (x<b) {        ival.insert(PII(x+1,b));        q.insert(dp[x+1][b]);    }}int main(){    int T,n;    scanf("%d",&T);    for (int t=1;t<=T;t++) {        scanf("%d",&n);        memset(vis,0,sizeof(vis));        for (int i=1;i<MAX;i++) {            p[i].clear();        }        for (int i=1;i<=n;i++) {            scanf("%d",&a[i]);            p[a[i]].push_back(i);        }        for (int i=1;i<=n;i++) {            bool f=true;            for (int j=i;j<=n;j++) {                if (vis[a[j]]==i) {                    f=false;                }                vis[a[j]]=i;                isok[i][j]=f;            }        }        for (int len=1;len<=n;len++) {            for (int i=1;i+len-1<=n;i++) {                int j=i+len-1;                if (isok[i][j]) {                    dp[i][j]=j-i+1;                } else {                    dp[i][j]=max(dp[i+1][j],dp[i][j-1]);                }            }        }        int ans=-1;        memset(vis,0,sizeof(vis));        for (int i=1;i<=n;i++) {            ival.clear();q.clear();            ival.insert(PII(1,n));            q.insert(dp[1][n]);            for (int j=i;j<=n;j++) {                if (vis[a[j]]==i) {                    break;                }                vis[a[j]]=i;                for (auto x:p[a[j]]) {                    split(x);                }                int cur=j-i+1;                if (!q.empty()) {                    cur+=*q.rbegin();                }                ans=max(cur,ans);            }        }        printf("Case #%d: %d\n",t,ans);    }    return 0;}

这个方法更优的地方在于它并没有一定要保存合法的区间(如[1,n]通常不合法),它保存的是相对于第一个区间合法的区间,以及该区间内自我合法的最大长度
复杂度同样为O(n2logn),但是实际操作区间接近2n,所以运行时间也大概是一半。

D. Ice Cream Tower

这题应该说比较容易。读懂题目应该可以想到答案具有单调性,所以可以二分答案。记二分当前结果为m,那么问题转化为判定能不能堆出m个塔。这个也比较容易,我们只要将冰淇淋按从小到大排序,一层一层贪心地堆就可以了,因为这个冰淇淋如果当前不能用上,那么后续更不可能用上。(从大到小堆我没有测试,据说也可以通过)
不想动态申请内存的可以像这样使用滚动数组。

#include <bits/stdc++.h>using namespace std;typedef long long ll;typedef pair<int,int> PII;const int N=3e5+5;int T,n,k;ll b[N],to[N][2];bool ok(int m){    int p=1;    int pre=0,now=1;    for (int j=1;j<=m;j++) {        to[j][pre]=b[p++];    }    for (int i=1;i<k;i++) {        for (int j=1;j<=m;j++) {            while (p<=n&&b[p]<to[j][pre]*2) p++;            if (p>n) return false;            to[j][now]=b[p++];        }        swap(pre,now);    }    return true;}int bs(int l,int r){    int res=0;    while (l<=r) {        int m=(l+r)>>1;        if (ok(m)) {            res=m;l=m+1;        } else {            r=m-1;        }    }    return res;}int main(){    scanf("%d",&T);    for (int t=1;t<=T;t++) {        scanf("%d%d",&n,&k);        for (int i=1;i<=n;i++) {            scanf("%lld",&b[i]);        }        sort(b+1,b+1+n);        int ans=bs(1,n/k);        printf("Case #%d: %d\n",t,ans);    }    return 0;}

E. Bet

。。。这题本身是容易的。。。但是。。。
记对第i个赌局下注ci,则题意就是对于答案集合S中的每个i都有

ci+cibiai>iiSci=>ciiSici>aiai+bi

该式对i求和可得iSiaiai+bi<1。接下来排个序贪个心就不用说了吧。。。然而boss却是精度问题。。。多少队伍交了10+发就是因为精度问题。。。反正听说有1-(1e-50)的= =
于是我又是问q巨要的代码。。。然后我感觉Java很厉害。。。

import java.math.BigInteger;import java.util.Scanner;public class Main {    public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        int T = sc.nextInt();        BigInteger[] up = new BigInteger[100];        BigInteger[] down = new BigInteger[100];        for (int t = 1; t <= T; t++) {            int n = sc.nextInt();            sc.nextLine();            for (int i = 0; i < n; i++) {                String str = sc.nextLine();                String[] da = str.split(":");                double x = Double.parseDouble(da[0]) * 1000;                double y = Double.parseDouble(da[1]) * 1000;                BigInteger a = BigInteger.valueOf((long) x);                BigInteger b = BigInteger.valueOf((long) y);                BigInteger g = a.gcd(b);                up[i] = a.divide(g);                down[i] = a.add(b).divide(g);            }            for (int i = 0; i < n; i++) {                for (int j = i + 1; j < n; j++) {                    if (up[i].multiply(down[j]).compareTo(down[i].multiply(up[j])) > 0) {                        BigInteger tmp = up[i];                        up[i] = up[j];                        up[j] = tmp;                        tmp = down[i];                        down[i] = down[j];                        down[j] = tmp;                    }                }            }            BigInteger su = BigInteger.ZERO;            BigInteger sd = BigInteger.ONE;            int ans = 0;            for (int i = 0; i < n; i++) {                BigInteger nu = su.multiply(down[i]).add(sd.multiply(up[i]));                BigInteger nd = sd.multiply(down[i]);                BigInteger g = nu.gcd(nd);                su = nu.divide(g);                sd = nd.divide(g);                if (su.compareTo(sd) < 0) {                    ans += 1;                } else {                    break;                }            }            System.out.println("Case #" + t + ": " + ans);        }    }}

F. Mr. Panda and Fantastic Beasts

比赛的时候还什么都不会。。。现在知道可以用后缀数组或者后缀自动机做,先只学后缀数组吧。。。
这么多串第一步就是加特殊字符全连在一起,用倍增法实现SA。记第一个串为S1,对于起点在S1的每一个后缀suffix(i),如果按字典序向前找到第一个不始于S1的后缀suffix(p)(位置越近重合长度越长),那么要想答案串在S1之外的串中不出现,长度至少要lcp(suffix(i),suffix(p))+1,同理向后找,找到suffix(q),长度至少要lcp(suffix(i),suffix(q))+1,所以用两者的最大值更新答案长度。注意,这个答案长度不能超过S1剩下的部分!
The solution above is based on forever97’s.
至于lcp可以用ST实现的RMQ,但是因为本来就是线性向前向后找,实际上使用最原始的一连串height值的最小值就可以啦。这个方法反而跑得飞快,10s的题在cf上只用了400+ms,复杂度我不太会分析。。。= =

#include <bits/stdc++.h>using namespace std;typedef long long ll;const int MAXN=300030;char s[MAXN];int str[MAXN];int sa[MAXN];int t1[MAXN],t2[MAXN],c[MAXN];int rk[MAXN],height[MAXN];bool cmp(int *r,int a,int b,int l){    return r[a] == r[b] && r[a+l] == r[b+l];}void da(int str[],int sa[],int rank[],int height[],int n,int m){    n++;    int i, j, p, *x = t1, *y = t2;    for(i = 0; i < m; i++)c[i] = 0;    for(i = 0; i < n; i++)c[x[i] = str[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(j = 1; j <= n; j <<= 1) {        p = 0;        for(i = n-j; i < n; i++)y[p++] = i;        for(i = 0; i < n; i++)if(sa[i] >= j)y[p++] = sa[i] - j;        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],j)?p-1:p++;        if(p >= n)break;        m = p;    }    int k = 0;    n--;    for(i = 0; i <= n; i++)rank[sa[i]] = i;    for(i = 0; i < n; i++) {        if(k)k--;        j = sa[rank[i]-1];        while(str[i+k] == str[j+k])k++;        height[rank[i]] = k;    }}void solve(int lim,int n){    int ans=n+1,pos=-1;    for (int i=1;i<=n;i++) {        while (i<=n&&sa[i]>=lim) i++;        if (i>n) break;        int cur1=height[i];        for (int p=i-1;p&&sa[p]<lim;p--) {            cur1=min(cur1,height[p]);        }        int cur2=height[i+1];        for (int q=i+1;q<=n&&sa[q]<lim;q++) {            cur2=min(cur2,height[q+1]);        }        int cur=max(cur1,cur2);        if (cur<lim-sa[i]) {            if (cur+1<ans) {                ans=cur+1;                pos=sa[i];            }        }    }    if (pos==-1) {        puts("Impossible");    } else {        for (int i=0;i<ans;i++) {            putchar(str[pos+i]);        }        puts("");    }}int main(){    int T,n;    scanf("%d",&T);    for (int t=1;t<=T;t++) {        scanf("%d",&n);        int up=140,len=0,firstl;        for (int i=1;i<=n;i++) {            scanf("%s",s);            int nl=strlen(s);            if (i==1) firstl=nl;            for (int j=0;j<nl;j++) {                str[len++]=s[j];            }            str[len++]=up+i;        }        str[--len]=0;        da(str,sa,rk,height,len,up+n);        printf("Case #%d: ",t);        solve(firstl,len);    }    return 0;}

warning: 不赞成使用 height[0]/height[n+1] 。

H. Great Cells

拆开来用贡献做实在是太神奇了。。。dp+容斥不太适合我的能力。。。

g=0NM (g+1)Ag=g=0NM gAg+g=0NM Ag

第一项相当于每个格子当一次great cell就计数一次,第二项相当于所有填法的总和。
g=0NM gAg=NMcontrib=NMi=2K(i1)N1+M1K(N1)(M1)

第一项转化为每一个格子的贡献(当great cell的次数)。一个格子为great cell当且仅当这个格子填i时,它所在的行和列填的都是小于i的数((i1)N1+M1),其他格子随便填(K(N1)(M1))。注意,N=1,M=1需要另外讨论!
于是这道题成为了本场代码第二短的。。。

#include <bits/stdc++.h>using namespace std;typedef long long ll;const int MO=1e9+7;ll fpow(ll x,int n){    ll res=1;    while (n) {        if (n&1) {            res=res*x%MO;        }        n>>=1;        x=x*x%MO;    }    return res;}int main(){    int T;    scanf("%d",&T);    for (int t=1;t<=T;t++) {        ll n,m,k,ans=0;        scanf("%I64d%I64d%I64d",&n,&m,&k);        for (int i=1;i<k;i++) {            ans=(ans+fpow(i,n+m-2))%MO;        }        ans=ans*fpow(k,(n-1)*(m-1))%MO;        ans=ans*n%MO*m%MO;        ans=(ans+fpow(k,n*m))%MO;        if (n==1&&m==1) {            ans=(ans+1)%MO;        }        printf("Case #%d: %I64d\n",t,ans);    }    return 0;}

L. World Cup

总共六场比赛,每场三种结果,暴力。

#include <bits/stdc++.h>using namespace std;typedef long long ll;typedef pair<int,int> PII;int match[][2]={{1,2},{1,3},{1,4},{2,3},{2,4},{3,4}};struct Result {    int a,b,c,d;    Result() {    }    Result(int w,int x,int y,int z) {        a=w;b=x;c=y;d=z;    }} r[1000];int top,s[5];void dfs(int dep){    if (dep>=6) {        r[top++]=Result(s[1],s[2],s[3],s[4]);        return;    }    s[match[dep][0]]+=3;    dfs(dep+1);    s[match[dep][0]]-=3;    s[match[dep][1]]+=3;    dfs(dep+1);    s[match[dep][1]]-=3;    s[match[dep][0]]+=1;    s[match[dep][1]]+=1;    dfs(dep+1);    s[match[dep][0]]-=1;    s[match[dep][1]]-=1;}int count(int a,int b,int c,int d){    int res=0;    for (int i=0;i<top;i++) {        if (r[i].a==a&&r[i].b==b&&r[i].c==c&&r[i].d==d) res++;    }    return res;}int main(){    top=0;    memset(s,0,sizeof(s));    dfs(0);    int T;    scanf("%d",&T);    for (int t=1;t<=T;t++) {        int a,b,c,d;        scanf("%d%d%d%d",&a,&b,&c,&d);        printf("Case #%d: ",t);        int cnt=count(a,b,c,d);        if (cnt==0) puts("Wrong Scoreboard");        else if (cnt==1) puts("Yes");        else puts("No");    }    return 0;}

Coda

因为总是搜不到题解,于是就劳烦了好多人。。。但是大佬们都很友好,作为一个鶸我真的非常感动,值得学习!
特别鸣谢 quailty@codeforces, forever97@codeforces 我q人见人爱!

1 0
原创粉丝点击