• [Helvetic Coding Contest 2017 online mirror]


    来自FallDream的博客,未经允许,请勿转载,谢谢,


    第一次在cf上打acm...和同校大佬组队打

    总共15题,比较鬼畜,最后勉强过了10题。

    AB一样的题目,不同数据范围,一起讲吧

    你有一个背包,最多装k本书,你在第i天需要拥有一本编号ai的书,但是你可以去商店购买并加入背包。

    问你至少要买多少本书?n,k<=400000

    显然可以贪心,留下下一次需要的时间最早的书就行了。

    #include<iostream>
    #include<cstdio>
    #include<queue>
    #define pa pair<int,int>
    #define mp(x,y) make_pair(x,y)
    #define INF 2000000000
    #define MN 400000
    using namespace std;
    inline int read()
    {
        int x = 0 , f = 1; char ch = getchar();
        while(ch < '0' || ch > '9'){ if(ch == '-') f = -1;  ch = getchar();}
        while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
        return x * f;
    }
    
    priority_queue<pa> q;
    int n,k,a[MN+5],ne[MN+5],la[MN+5],ans=0;
    bool in[MN+5];
    
    int main()
    {
        n=read();k=read();
        for(int i=1;i<=n;++i) a[i]=read();
        for(int i=n;i;--i)
        {
            ne[i]=la[a[i]]?la[a[i]]:INF;
            la[a[i]]=i;
        }
        for(int i=1;i<=n;++i)
        {
            if(in[a[i]])
                ++k,q.push(mp(ne[i],a[i]));
            else
            {
                ++ans;
                while(q.size()==k) in[q.top().second]=0,q.pop();
                q.push(mp(ne[i],a[i]));
                in[a[i]]=1;
            }
    
        }
        cout<<ans;
        return 0;
    }

    C. 还是同样的题面,只不过每本书要的钱不一样了,n,k<=80

    把一本书留到下一次用看作一个区间覆盖,发现这就是一道k可重区间问题,费用流建图即可

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    #define S 0
    #define MN 80
    #define T 81
    #define INF 2000000000
    using namespace std;
    inline int read()
    {
        int x = 0 , f = 1; char ch = getchar();
        while(ch < '0' || ch > '9'){ if(ch == '-') f = -1;  ch = getchar();}
        while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
        return x * f;
    }
    
    int d[MN+5],n,k,head[MN+5],cnt=1,a[MN+5],c[MN+5],ans=0,pi=0,la[MN+5];
    struct edge{int to,next,w,c;}e[MN*MN];
    bool inq[MN+5],mark[MN+5];
    deque<int> q;
    
    inline void ins(int f,int t,int w,int c)
    {
        e[++cnt]=(edge){t,head[f],w,c}; head[f]=cnt;
        e[++cnt]=(edge){f,head[t],0,-c};head[t]=cnt;
    }
    
    bool modlabel()
    {
        for(int i=S;i<=T;++i) d[i]=INF;
        d[T]=0;inq[T]=1;q.push_front(T);
        while(!q.empty())
        {
            int x=q.front();q.pop_front();
            for(int i=head[x];i;i=e[i].next)
                if(e[i^1].w&&e[i^1].c+d[x]<d[e[i].to])
                {
                    d[e[i].to]=d[x]+e[i^1].c;
                    if(!inq[e[i].to])
                    {
                        inq[e[i].to]=1;
                        if(d[e[i].to]<d[q.size()?q.front():0]) q.push_front(e[i].to);
                        else q.push_back(e[i].to);
                    }
                }
            inq[x]=0;
        }
        for(int i=S;i<=T;++i)
            for(int j=head[i];j;j=e[j].next)
                e[j].c+=d[e[j].to]-d[i];
        return pi+=d[S],d[S]<INF;
    }
    
    int dfs(int x,int f)
    {
        if(x==T) return ans+=pi*f,f;
        int used=0;mark[x]=1;
        for(int i=head[x];i;i=e[i].next)
            if(e[i].w&&!e[i].c&&!mark[e[i].to])
            {
                int w=dfs(e[i].to,min(f-used,e[i].w));
                used+=w;e[i].w-=w;e[i^1].w+=w;
                if(used==f) return used;
            }
        return used;
    }
    
    int main()
    {
        n=read();k=read()-1;
        for(int i=1;i<=n;++i) a[i]=read();
        for(int i=1;i<=n;++i) c[i]=read();
        for(int i=1;i<=n;++i) ans+=c[a[i]];
        ins(S,1,k,0);ins(n,T,k,0);
        for(int i=1;i<n;++i) ins(i,i+1,INF,0);
        for(int i=1;i<=n;++i)
        {
            if(la[a[i]])
            {
                if(la[a[i]]==i-1) ans-=c[a[i]];
                else ins(la[a[i]]+1,i,1,-c[a[i]]);
            }
            la[a[i]]=i;
        }
        while(modlabel())
            do memset(mark,0,sizeof(mark));
            while(dfs(S,INF));
        cout<<ans;
        return 0;
    }

    DEF比较牛逼 D题wa了个几十次,还剩半分钟的时候居然过了  感觉这种奇奇怪怪的题并没有发言权...

    G题是送分的

    H题

    给你一个数字n(<=1000000),要求你构造两个字符串,使得第二个字符串作为子序列在第一个字符串中的出现次数恰好是n次,且第一个字符串的长度不超过200

    考虑用组合数来解决。让第二个串为'aaaaab',那么第一个串中每个B的贡献都是他前面的a的个数选出5个的组合数。只要选择适当的地方插入就行了。

    #include<iostream>
    #include<cstdio>
    #define MN 50
    using namespace std;
    inline int read()
    {
        int x = 0 , f = 1; char ch = getchar();
        while(ch < '0' || ch > '9'){ if(ch == '-') f = -1;  ch = getchar();}
        while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
        return x * f;
    }
    
    int num[MN+5],n,C[MN+5][6];
    
    int main()
    {
        n=read();C[0][0]=1;
        for(int i=1;i<=MN;++i)
        {
            C[i][0]=1;
            for(int j=1;j<=5;++j)
                C[i][j]=C[i-1][j]+C[i-1][j-1];
        }
        for(int i=MN;i>=5;--i)
            while(n>=C[i][5]) n-=C[i][5],++num[i];
        for(int i=0;i<=MN;putchar('a'),++i)
            for(int j=1;j<=num[i];++j) putchar('b');
        printf(" aaaaab");
        return 0;
    }

    I

    给定一个长度为n字符串,求每个不同子串的出现次数的平方。

    T(T<=10)组数据,n<=100000

    建出后缀自动机之后,简单dp一下就行了。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    inline int read()
    {
        int x = 0 , f = 1; char ch = getchar();
        while(ch < '0' || ch > '9'){ if(ch == '-') f = -1;  ch = getchar();}
        while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
        return x * f;
    }
    int c[200005][26],step[200005],fail[200005];
    long long val[200005];long long sum[200005];
    char s[200005];
    int cnt=1,last=1,n;
    
    void ins(int x)
    {
        int p=last,np=++cnt;step[np]=step[last]+1;val[np]=1;
        for(;p&&!c[p][x];p=fail[p])c[p][x]=np;
        if(!p)fail[np]=1;
        else
        {
            int q=c[p][x];
            if(step[q]==step[p]+1) fail[np]=q;
            else
            {
                int nq=++cnt;step[nq]=step[p]+1;
                for(int i=0;i<26;i++)c[nq][i]=c[q][i];
                fail[nq]=fail[q];fail[q]=fail[np]=nq;
                for(;c[p][x]==q;p=fail[p])c[p][x]=nq;
            }
        }
        last=np;
    }
    
    int v[200005],rk[200005];
    void work()
    {
        memset(v,0,sizeof(v));
        for(int i=1;i<=cnt;i++)v[step[i]]++;
        for(int i=1;i<=n;i++)v[i]+=v[i-1];
        for(int i=cnt;i;i--) rk[v[step[i]]--]=i;
        for(int i=cnt;i;i--) val[fail[rk[i]]]+=val[rk[i]];
    }
    bool vis[200000];
    long long Dfs(int x)
    {
        if(vis[x]) return sum[x];
        if(x!=1)sum[x]=1LL*val[x]*val[x];vis[x]=1; 
        for(int i=0;i<26;++i) if(c[x][i])
        sum[x]+=Dfs(c[x][i]);    
        return sum[x];
    }
    
    int main()
    {
        for(int T=read();T;--T)
        {
            memset(c,0,sizeof(c));        
            memset(fail,0,sizeof(fail)); 
            memset(step,0,sizeof(step));
            memset(vis,0,sizeof(vis));
            memset(sum,0,sizeof(sum));
            memset(val,0,sizeof(val));
            cnt=last=1;
            scanf("%s",s+1);n=strlen(s+1);
            for(int i=1;s[i];ins(s[i++]-'a'));
            work();
            cout<<Dfs(1)<<endl;
        }
        return 0;
    }

    J是送分的

    K

    给定一棵树,有边权,你要从1号点出发,要求每个点经过次数不超过k的前提下,经过的边的权值和最大(多次经过只算一次)

    n<=100000

    显然是一道树形dp,用f[i]表示回到i号点的最大答案,f2[i]表示不回来的最大答案,然后用优先队列保存前k大就行了。

    #include<iostream>
    #include<cstdio>
    #include<queue>
    #define mp(x,y) make_pair(x,y)
    #define pa pair<int,int>
    #define MN 100000
    using namespace std;
    inline int read()
    {
        int x = 0 , f = 1; char ch = getchar();
        while(ch < '0' || ch > '9'){ if(ch == '-') f = -1;  ch = getchar();}
        while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
        return x * f;
    }
    
    int n,k,cnt=0,f[MN+5],f2[MN+5],head[MN+5],mark[MN+5];
    struct edge{int to,next,w;}e[MN*2+5];
    priority_queue<pa,vector<pa>,greater<pa> > q[MN+5],q2[MN+5];
    
    inline void ins(int f,int t,int w)
    {
        e[++cnt]=(edge){t,head[f],w};head[f]=cnt;
        e[++cnt]=(edge){f,head[t],w};head[t]=cnt;    
    }
    
    void Dp(int x,int fa)
    {
        for(int    i=head[x];i;i=e[i].next)
            if(e[i].to!=fa)
            {
                Dp(e[i].to,x);
                q[x].push(mp(f[e[i].to]+e[i].w,e[i].to));                    
            }
        int mx=0,mx2=0,mm;
        while(q[x].size()>k) q[x].pop();
        if(q[x].size()==k) mm=q[x].top().first,mark[q[x].top().second]=x; else mm=0;
        while(q[x].size()>k-1) q[x].pop();
        while(!q[x].empty())
        {
            pa now=q[x].top();
            f[x]+=now.first;
            mark[now.second]=x;
            q[x].pop();
        }
        for(int i=head[x];i;i=e[i].next)
            if(e[i].to!=fa)
            {
                if(mark[e[i].to]==x)
                    mx=max(mx,f2[e[i].to]-f[e[i].to]);
                else mx2=max(mx2,e[i].w+f2[e[i].to]);
            }
        f2[x]=max(f[x],max(f[x]+mx2,f[x]+mx+mm));
    }
    
    int main()
    {
        n=read();k=read();
        for(int i=1;i<n;++i) 
        {
            int x=read()+1,y=read()+1,w=read();
            ins(x,y,w);    
        }
        Dp(1,0);
        printf("%d
    ",max(f[1],f2[1]));
        return 0;
    }

    M

    给样例猜题意,输出前k小的数字的和就行了。

    N

    给定两个长度为n的序列,你要从每个序列中选出k个,并且满足a序列中选出的第i个的位置小等于从b序列中选出的第i个,求最小的和。

    n<=2000

    考虑二分一个值,并把所有数字减去那个值,通过n^2dp求出最小的情况下最多/最少选出多少个,区间包含了k时输出答案即可

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #define ll long long
    #define MN 2200
    using namespace std;
    inline int read()
    {
        int x = 0 , f = 1; char ch = getchar();
        while(ch < '0' || ch > '9'){ if(ch == '-') f = -1;  ch = getchar();}
        while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
        return x * f;
    }
    
    ll f[MN+5][MN+5],N[MN+5][MN+5],N2[MN+5][MN+5];
    int a[MN+5],b[MN+5],n,k;
    
    int main()
    {
        n=read();k=read();
        for(int i=1;i<=n;++i) a[i]=read();
        for(int i=1;i<=n;++i) b[i]=read();
        int l=-(1e9),r=1e9,mid;
        while(l<=r)
        {
            mid=1LL*l+r>>1;
            memset(f,63,sizeof(f));
            for(int i=0;i<=n;++i) f[0][i]=f[i][0]=0;
            for(int i=1;i<=n;++i)
                for(int j=i;j<=n;++j)
                {
                    if(f[i-1][j]<f[i][j]) f[i][j]=f[i-1][j],N[i][j]=N[i-1][j],N2[i][j]=N2[i-1][j];
                    else if(f[i-1][j]==f[i][j]) N[i][j]=max(N[i][j],N[i-1][j]),N2[i][j]=min(N2[i][j],N2[i-1][j]);
                    if(f[i][j-1]<f[i][j]) f[i][j]=f[i][j-1],N[i][j]=N[i][j-1],N2[i][j]=N2[i][j-1];
                    else if(f[i][j-1]==f[i][j]) N[i][j]=max(N[i][j],N[i][j-1]),N2[i][j]=min(N2[i][j],N2[i][j-1]);
                    if(f[i-1][j-1]+a[i]-mid+b[j]-mid<f[i][j])
                        f[i][j]=f[i-1][j-1]+a[i]-mid+b[j]-mid,N[i][j]=N[i-1][j-1]+1,N2[i][j]=N2[i-1][j-1]+1;
                    else if(f[i-1][j-1]+a[i]-mid+b[j]-mid==f[i][j])
                                                            N[i][j]=max(N[i][j],N[i-1][j-1]+1),N2[i][j]=min(N2[i][j],N2[i-1][j-1]+1);
                }
            if(N[n][n]>=k&&N2[n][n]<=k) return 0*printf("%lld",f[n][n]+2LL*k*mid);
            else if(N[n][n]>k) r=mid-1;
            else l=mid+1;
        }
        return 0;
    }

    O题意相同 数据范围500000

    发现这就像一个二分图匹配,并且每个b之和之前的a连边,考虑二分之后用堆来贪心。

    从前往后确定每一个b的匹配,往堆里加入a,这个b只能和堆顶配对,如果更优秀的话就配对。但是这可能不是最优的,所以之后还要加入-b来表示推流,更换一个b和这个a配对。

    #include<iostream>
    #include<cstdio>
    #include<queue>
    #define ll long long
    #define mp(x,y) make_pair(x,y)
    #define pa pair<ll,int>
    #define MN 500000
    using namespace std;
    inline int read()
    {
        int x = 0; char ch = getchar();
        while(ch < '0' || ch > '9')  ch = getchar();
        while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
        return x;
    }
    
    int A[MN+5],B[MN+5],n,m;ll tot;
    priority_queue<pa,vector<pa>,greater<pa> > q;
    
    int Solve(int x)
    {
        tot=0;int num=0;
        for(int i=1;i<=n;++i)
        {    
            q.push(mp(A[i],0));
            ll t=q.top().first,now=B[i]-x;
            if(now+t<0) 
            {
                tot+=now+t;
                q.pop();
                q.push(mp(-now,1));        
            }
        }
        while(!q.empty()) num+=q.top().second,q.pop();
        return num;
    }
    
    int main()
    {
        n=read();m=read();
        for(int i=1;i<=n;++i) A[i]=read();
        for(int i=1;i<=n;++i) B[i]=read();
        int l=0,r=2e9,mid;
        while(l<=r)
        {
            mid=1LL*l+r>>1;int num=Solve(mid);
            if(num==m) return 0*printf("%lld
    ",tot+1LL*m*mid);
            if(num<m) l=mid+1;
            else r=mid-1;
        }
        return 0;
    }
  • 相关阅读:
    iOS:CALayer锚点的使用
    iOS:UIView的CALayer基本演练
    iOS:iOS中的几种动画
    iOS:CALayer核心动画层
    Objective-C:KVO机制
    Objective-C:KVC机制
    投资方式集汇
    曝光卖假币的店铺和旺旺ID
    android 的开源输入法介绍,及 自动触摸的实现方法
    Intra Refresh of H264 encoder
  • 原文地址:https://www.cnblogs.com/FallDream/p/HelveticCodingContest2017.html
Copyright © 2020-2023  润新知