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

扫描二维码关注公众号,回复: 4202358 查看本文章

题意:给你一个数列,让你选择两段连续的区间(区间长度可以为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

猜你喜欢

转载自www.cnblogs.com/asdfsag/p/10009602.html