(转载)2016 China-Final 解题报告

转自: http://blog.csdn.net/summonlight

这几天又把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巨要了一份代码。。。


#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 ,再枚举左边界 j j 每减小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);
        }
    }
}


py写的短代码

from decimal import Decimal
T = int(input())
for t in range(1, T + 1):
n = int(input())
num = []
for i in range(n):
a, b = input().split(":")
num.append(Decimal(a) / (Decimal(a) + Decimal(b)))
num.sort()
tot = Decimal('0')
ans = 0
for i in num:
if (tot + i >= Decimal('1')):
break
tot = tot + i
ans = ans + 1
print("Case #" + str(t) + ": " + str(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;
}

在此特别感谢博主     summonlight






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值