[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\n",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\n",tot+1LL*m*mid);
        if(num<m) l=mid+1;
        else r=mid-1;
    }
    return 0;
}
posted @ 2017-05-29 18:11  FallDream  阅读(218)  评论(0编辑  收藏  举报