• Codeforces 2018-2019 ICPC, NEERC, Southern Subregional Contest


    2018-2019 ICPC, NEERC, Southern Subregional Contest

    闲谈:

      被操哥和男神带飞的一场ACM,第一把做了这么多题,荣幸成为7题队,虽然比赛的时候频频出锅,差点被鸽,但还算打完了5h

      总的来说这场还是不算难的,7题还是少了点


    A

    题目:

      给出a,b,求出一个数满足是a的倍数,且数字和为b

    题解:

      男神懒得写博客就甩锅了

      直接宽搜,宽搜时队列中的状态保存为x,y,x表示当前的数%a==x,y表示数字和

      然后记录状态和方案就行了

    参考代码:

    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<iostream>
    #include<algorithm>
    #include<cmath>
    #include<queue>
    using namespace std;
    const int N=505,M=5010;
    struct node {
        int x,y;   // %A=x, 和为y , 上一个为k 
    } a[N][M],from[N][M],list[N*M*10]; int head,tail;
    bool v[N][M]; int ans[1000010];
    int main() {
        int A,B; scanf("%d%d",&A,&B);
        head=1; tail=2; list[1]=(node){0,0};
        memset(v,false,sizeof(v)); v[0][0]=true;
        while(head!=tail) {
            int x=list[head].x,y=list[head].y;
            for(int i=0;i<=9;i++) {
                int tx=(x*10+i)%A,ty=y+i;
                if(v[tx][ty]==true||ty>B) continue;
                v[tx][ty]=true;
                from[tx][ty]=(node){x,y};
                list[tail++]=(node){tx,ty};
            } head++; 
        }
        int len=0;
        int x=0,y=B;
        if(v[0][B]==false) { printf("-1
    "); return 0; }
        while(x!=0||y!=0) {
            int tx=from[x][y].x,ty=from[x][y].y;
            ans[++len]=y-ty; x=tx; y=ty;
        }
        for(int i=len;i>=1;i--) printf("%d",ans[i]);
        printf("
    ");
        return 0;
    }
    A(男神)

    B(*)

    题目:

      给出n个IPv4地址,表示n个区间

      形如a.b.c.d/x,相当于左端点为a*224+b*216+c*28+d,长度为232-x的区间(若形如a.b.c.d则与a.b.c.d/32相同)

      每个区间有颜色,黑区间或白区间,剩下没给出的地址中(也就是从0.0.0.0到255.255.255.255中没被覆盖的地址)非黑非白

      PS:这道题IPv4地址的定义是a.b.c.d/x中a*224+b*216+c*28+d的二进制位中后32-x位必须都是0

      求出最少的区间(以IPv4地址形式)能够覆盖所有黑区间(非黑非白的地址可以被覆盖,但是白区间不能被覆盖)

    题解:

      用01字典树来做

      首先按照给出的区间的左端点的二进制位从大到小x位插进字典树中,之所以只插x位是因为这段区间实际上就是以插进最后一位的所在节点的满子树(也就是所有点都有01孩子(深度不超过32))

      那我们就可以用一个节点来表示一段区间,并可以保证满足IPv4的定义

      然后我们对字典树上的点染色,黑色区间左端点插字典树的时候就将点值或1,白色就将点值或2

      那么整棵字典树上,显然点值为1的节点就表示这个点可以用来覆盖黑区间(因为不受白区间影响)

      我们就可以直接DFS求出最少的点来覆盖所有叶子节点,那肯定是找到的点深度越浅越好

      对于-1的情况,就提前排序判断一下区间重叠的情况就行了

    参考代码:

    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<cmath>
    #include<algorithm>
    using namespace std;
    typedef long long LL;
    struct bw
    {
        LL l;int d,col;
    }p[210000];
    bool cmp(bw n1,bw n2){return n1.l<n2.l;}
    char st[31];
    void read(int x)
    {
        scanf("%s",st+1);
        int col;
        if(st[1]=='+') col=2;
        else col=1;
        int len=strlen(st+1);
        int b=24;
        LL l=0,r=0,d=0;
        for(int i=2;i<=len;i++)
        {
            if(st[i]=='.')
            {
                l+=d*(1LL<<b);
                b-=8;d=0;
            }
            else if(st[i]=='/')
            {
                l+=d*(1LL<<b);
                d=0;for(int j=i+1;j<=len;j++) d=d*10+st[j]-'0';
                break;
            }
            else
            {
                d=d*10+st[i]-'0';
                if(i==len) l+=d*(1LL<<b),r=l,d=32;
            }
        }
        p[x]=(bw){l,32-d,col};
    }
    struct trie
    {
        int c[2],col;
        trie()
        {
            col=0;
            memset(c,-1,sizeof(c));
        }
    }t[7100000];int tot;
    struct answer
    {
        LL d;int x;
    }ans[7100000];int cnt;
    void dfs(int x,LL k,int dep)
    {
        if(t[x].col==1)
        {
            ans[++cnt]=(answer){k,dep};
            return ;
        }
        if(t[x].c[0]!=-1) dfs(t[x].c[0],k,dep-1);
        if(t[x].c[1]!=-1) dfs(t[x].c[1],k+(1LL<<(dep-1)),dep-1);
    }
    int main()
    {
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++) read(i);
        sort(p+1,p+n+1,cmp);
        for(int i=1;i<n;i++)
        {
            if(p[i].col!=p[i+1].col&&p[i].l+(1LL<<p[i].d)-1>=p[i+1].l)
            {
                printf("-1
    ");
                return 0;
            }
        }
        tot=0;
        for(int i=1;i<=n;i++)
        {
            LL l=p[i].l;int d=p[i].d,col=p[i].col;
            int x=0;t[x].col|=col;
            for(int j=31;j>=d;j--)
            {
                int y=(l>>j)&1;
                if(t[x].c[y]==-1) t[x].c[y]=++tot;
                x=t[x].c[y];t[x].col|=col;
            }
        }
        cnt=0;dfs(0,0,32);
        printf("%d
    ",cnt);
        LL mk=(1<<8)-1;
        for(int i=1;i<=cnt;i++)
        {
            LL d=ans[i].d;
            printf("%lld.%lld.%lld.%lld/%d
    ",(d>>24)&mk,(d>>16)&mk,(d>>8)&mk,d&mk,32-ans[i].x);
        }
        return 0;
    }
    B

    C

    咕咕咕:

      本来可以提前2hA掉的题目,结果因为男神没开long long,主席树又爆空间,贡献13发罚时

      结果操哥直接就又写了一发扫描线过掉了,结果男神重开long long,也过了

      一题两种解法。。直接出门直走[飞机]


    D

    咕咕咕:

      男神一波甩锅[飞机]


    E(*)

    题目:

      给出n个任务,每个任务有它需要花费的时间,每做完m个任务就要休息等同于做完这m个任务所需要的时间的时间

      求出一个值d,表示只有花费的时间<=d的任务才会被完成

      在符合d的条件下,任务需要按照初始的顺序依次完成

      给出总时间t,要求做完任务的时间(包括休息时间)要<=t,若完成了最后想要完成的任务后需要休息时,可以不休息,但也不能再做任务了

      求出一个d,使得能做完的任务尽量多,求出最大可完成任务数和任意一个能得到最大可完成任务数的d值

    题解:

      一眼二分题(虽说是赛后才A的)

      先将原花费时间排序去重,然后二分位置,因为d值一定可以为其中一个任务的花费时间

      然后很容易想到实际上排序后每个任务的花费时间作为d值来求出的任务数,是呈单峰的

      那么我们就可以二分,然后对于前后的取值判断当前在单峰的哪个位置,然后再继续二分就好了

    参考代码:

    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<cmath>
    #include<algorithm>
    using namespace std;
    typedef long long LL;
    int a[210000],s[210000];
    int n,m;LL t;
    int check(int x)
    {
        x=s[x];
        int ans=0,d=0;
        LL tt=0,t1=0;
        for(int i=1;i<=n;i++)
        {
            if(a[i]>x) continue;
            if(tt+a[i]>t) return ans;
            tt+=a[i];
            t1+=a[i];d++;
            ans++;
            if(d==m)
            {
                if(tt+t1>t) return ans;
                tt+=t1;
                t1=0;d=0;
            }
        }
        return ans;
    }
    int main()
    {
        int T;
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d%d%lld",&n,&m,&t);
            for(int i=1;i<=n;i++) scanf("%d",&a[i]),s[i]=a[i];
            sort(s+1,s+n+1);int len=unique(s+1,s+n+1)-s-1;
            int l=1,r=len,sum=0,ans=1;
            while(l<=r)
            {
                int mid=(l+r)/2;
                int d=check(mid);
                if(d>sum) sum=d,ans=mid;
                if(check(mid+1)>d) l=mid+1;
                else r=mid-1;
            }
            printf("%d %lld
    ",sum,min(t,LL(s[ans])));
        }
        return 0;
    }
    E

     F

    题目:

      当前,有两位参与选拔的人Alice和Bob,有n个观众,每个观众有自己的影响力

      每个观众要么谁都不支持,要么支持Alice,要么支持Bob,要么两个都支持

      现在选拔现场的工作人员想要选出若干个观众来到现场(设人数为m),若a为支持Alice的人数,b为支持Bob的人数

      则现场的观众必须满足2a>=m且2b>=m,求出满足情况的条件下,能够请到的观众的影响力之和最大

    题解:

      裸裸的贪心,比赛的时候叫AKC验了想法,就直接做了

      首先对于两者都支持的观众一定可以选,因为他们都对a,b有贡献,而只会让m+1,所以无论什么情况都可以请

      然后用三个大根堆分别保存只支持Alice和只支持Bob和谁都不支持的影响力

      然后在两者都有值的情况下,分别取两个堆的堆顶加到答案里,因为这样会使得a+1,b+1,m+2,也是不影响答案的

      接下来对于剩下的一个堆,和谁都不支持的堆,显然接下来只能取m-2*(若剩下的堆支持Alice,则为b,若支持Bob,则为a)

      因为直到最后一步为止,a和b的值一直是相等的,所以对于哪个堆已经被取完,哪个堆的a或b就会成为约束条件

      然后将剩下的堆和谁都不支持的堆合起来取最大的m-2*(a或b)个影响力就行了

    参考代码:

    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<cmath>
    #include<algorithm>
    #include<queue>
    using namespace std;
    typedef long long LL;
    char st[4];
    int id()
    {
        if(st[1]=='1')
        {
            if(st[2]=='0') return 1;
            else return 4;
        }
        else
        {
            if(st[2]=='1') return 2;
            else return 3;
        }
    }
    priority_queue<LL> A;
    priority_queue<LL> B;
    priority_queue<LL> C;
    int main()
    {
        int n;
        scanf("%d",&n);
        LL ans=0,a=0,b=0,m=0;
        for(int i=1;i<=n;i++)
        {
            LL d;
            scanf("%s%lld",st+1,&d);
            int p=id();
            if(p==1) A.push(d);
            if(p==2) B.push(d);
            if(p==3) C.push(d);
            if(p==4) ans+=d,m++,a++,b++;
        }
        while(A.empty()==0&&B.empty()==0)
        {
            ans+=A.top();
            ans+=B.top();
            a++;b++;m+=2;
            A.pop();B.pop();
        }
        if(A.empty()!=0&&B.empty()!=0)
        {
            while(2LL*a>=(m+1)&&C.empty()==0)
            {
                ans+=C.top();
                m++;C.pop();
            }
        }
        else
        {
            if(A.empty()==0)
            {
                LL d=2LL*a-m;
                while(d!=0)
                {
                    if(A.empty()!=0&&C.empty()!=0) break;
                    else if(A.empty()!=0&&C.empty()==0) ans+=C.top(),C.pop();
                    else if(C.empty()!=0&&A.empty()==0) ans+=A.top(),A.pop();
                    else if(A.top()>C.top()) ans+=A.top(),A.pop();
                    else ans+=C.top(),C.pop();
                    d--;
                }
            }
            else
            {
                LL d=2LL*a-m;
                while(d!=0)
                {
                    if(B.empty()!=0&&C.empty()!=0) break;
                    else if(B.empty()!=0&&C.empty()==0) ans+=C.top(),C.pop();
                    else if(C.empty()!=0&&B.empty()==0) ans+=B.top(),B.pop();
                    else if(B.top()>C.top()) ans+=B.top(),B.pop();
                    else ans+=C.top(),C.pop();
                    d--;
                }
            }
        }
        printf("%lld
    ",ans);
        return 0;
    }
    F

     G

    咕咕咕:

      因为是AKC的,所以甩锅,出门直走[飞机]

      最后一小时捡的漏


    H

    题目:

      给出n个模式串,有Q个询问,每个询问给出一个字符串,求出这个字符串是多少个模式串的子串,并求出任意一个模式串

    题解:

      签到题,直接字典树保存Q个询问的字符串,然后枚举每个模式串的每个子串,然后在字典树上跑,跑到尽头的时候将当前字典树的点权+1就行了

      然后判一下一个模式串中有多个相同子串的情况就行了

    参考代码:

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #include<cstdlib>
    #include<queue>
    using namespace std;
    char st[11000][11];
    char a[11];
    struct trie
    {
        int c[41],s,t;
        trie()
        {
            memset(c,-1,sizeof(c));
        }
    }t[810000];int tot;
    int to[51000];
    int id(char cc)
    {
        if(cc>='0'&&cc<='9') return cc-'0'+1;
        if(cc>='a'&&cc<='z') return cc-'a'+1+10;
        return 37;
    }
    void bt(int p)
    {
        int x=0,len=strlen(a+1);
        for(int i=1;i<=len;i++)
        {
            int y=id(a[i]);
            if(t[x].c[y]==-1) t[x].c[y]=++tot;
            x=t[x].c[y];
        }
        to[p]=x;
    }
    int v[810000];
    int main()
    {
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%s",st[i]+1);
        int Q;
        scanf("%d",&Q);
        tot=0;
        for(int i=1;i<=Q;i++)
        {
            scanf("%s",a+1);
            bt(i);
        }
        memset(v,0,sizeof(v));
        for(int i=1;i<=n;i++)
        {
            int len=strlen(st[i]+1);
            for(int l=1;l<=len;l++)
            {
                int x=0;
                for(int r=l;r<=len;r++)
                {
                    int y=id(st[i][r]);
                    if(t[x].c[y]==-1) break;
                    x=t[x].c[y];
                    if(v[x]!=i)
                    {
                        v[x]=i;
                        t[x].s++;
                        t[x].t=i;
                    }
                }
            }
        }
        for(int i=1;i<=Q;i++)
        {
            printf("%d ",t[to[i]].s);
            if(t[to[i]].s==0) printf("-
    ");
            else printf("%s
    ",st[t[to[i]].t]+1);
        }
        return 0;
    }
    H

    I(*)

    咕咕咕:

      赛后甩锅大法好[飞机]


    J(*)

    咕咕咕:

      甩锅大法好[飞机]


    K

    题目:

      给出n个数,要分成连续的k段,使得每段的和相同

      若有合法分段的的情况就输出Yes并输出每一段的数的个数

      否则输出No

    题解:

      签到题,直接乱搞就行了,水题

    参考代码:

    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<cmath>
    #include<algorithm>
    using namespace std;
    int a[110000];
    int ans[110000];
    int main()
    {
        int n,k;
        scanf("%d%d",&n,&k);
        int sum=0;
        for(int i=1;i<=n;i++) scanf("%d",&a[i]),sum+=a[i];
        int tot=0;
        if(sum%k!=0) printf("No
    ");
        else
        {
            sum/=k;int d=0,cnt=0;
            int kk=0;
            for(int i=1;i<=n;i++)
            {
                d+=a[i];cnt++;
                if(d>sum){printf("No
    ");return 0;}
                if(d==sum)
                {
                    ans[++kk]=cnt;
                    cnt=0;d=0;
                }
            }
            printf("Yes
    ");
            for(int i=1;i<k;i++) printf("%d ",ans[i]);
            printf("%d
    ",ans[k]);
        }
        return 0;
    }
    K

    L(*)

    咕咕咕:

      究极甩锅[飞机]

  • 相关阅读:
    CSS复合选择器
    CSS样式规则及字体样式
    jQuery 样式操作
    jQuery 选择器
    jQuery 的基本使用
    jQuery 介绍
    本地存储
    移动端常用开发框架
    移动端常用开发插件
    移动端click 延时解决方案
  • 原文地址:https://www.cnblogs.com/Never-mind/p/9828450.html
Copyright © 2020-2023  润新知