APIO 2014

Posted ditoly

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了APIO 2014相关的知识,希望对你有一定的参考价值。

练习赛,评测的时候好像出了些问题,最后我拿自己机子测的212/300,第二题负责评测的写的SPJ就判了第一行的答案,不知道有没出什么问题。

 

T1.palindrome

题目大意:给定一个长度为N的字符串,从中找出一个回文串使其出现次数*长度最大,求出这个值。

思路:做的时候几乎对回文串一无所知,听我旁边某位大神传授manacher就现场学了下,然后按manacher找回文串的方式搞出了一个奇怪的暴力,最后好像骗了很多分(只T了一个点,WA了3个好像是哪里写挂了),但因为太丑又复杂就不解说了。想知道的看看代码脑补吧。

得分:92/100

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
#define MN 300000
#define MV 1200000
#define MOD 9875321
char s[MN+5],st[MN*2+5];
int l[MN*2+5],f[MN*2+5],cnt,rr[MV+5],ss[MV+5];
ll hs[MV+5];
struct P{int p,l;}pi[MV+5];
bool cmp(P a,P b){return a.l<b.l;}
struct map
{
    int h[MOD],nx[MV*3/2+5],z[MV*3/2+5],en,lz;
    ll s[MV*3/2+5],ls;
    int&operator[](ll x)
    {
        if(x==ls)return z[lz];
        int p=(x%MOD+MOD)%MOD,i;
        for(i=h[p];i;i=nx[i])if(s[i]==x)return z[lz=i];
        nx[!en||z[en]?++en:en]=h[p];s[en]=x;h[p]=en;return z[lz=en];
    }
}mp;
int main()
{
    freopen("palindrome.in","r",stdin);
    freopen("palindrome.out","w",stdout);
    int i,r=0,p;ll mx=0;
    scanf("%s",s);
    for(st[i=0]=\'(\';s[i];++i)st[(i<<1)+1]=\'.\',st[i+1<<1]=s[i];st[(i<<1)+1]=\'.\';st[i+1<<1]=0;
    for(i=1;st[i];++i)
    {
        if(r>i)l[i]=min(l[(p<<1)-i],r-i),f[i]=(p<<1)-i;
        else l[i]=1;
        pi[++cnt]=(P){i,l[i]};
        while(st[i-l[i]]==st[i+l[i]])pi[++cnt]=(P){i,++l[i]};
        if(i+l[i]>r)r=i+l[i],p=i;
    }
    sort(pi+1,pi+cnt+1,cmp);
    for(i=1;i<=cnt;++i)
    {
        if(pi[i].l>1)
        {
            for(r=pi[i].p;!mp[(ll)r*(MV+3)+pi[i].l-1];r=f[r]);
            hs[i]=hs[rr[i]=mp[(ll)r*(MV+3)+pi[i].l-1]];
        }
        hs[i]=hs[i]*31+st[pi[i].p+pi[i].l-1];
        mp[(ll)pi[i].p*(MV+3)+pi[i].l]=i;
    }
    memset(&mp,0,sizeof(mp));
    for(i=cnt;i;--i)
    {
        if(pi[i].l==l[pi[i].p])++ss[i];
        mx=max(mx,(ll)(pi[i].l-1)*(mp[hs[i]]+=ss[i]));
        ss[rr[i]]+=ss[i];
    }
    cout<<mx;
    fclose(stdin);fclose(stdout);return 0;
}
View Code

正解:听某位巨强的学长说,这是道回文树裸题……我只听过回文树的大名,不知道是什么东西,学了下发现,真是裸题……

#include<cstdio>
#define MN 300000
char s[MN+5];
int l[MN+5],p[MN+5],c[MN+5][26],f[MN+5],tn;
int main()
{
    int i,j;long long ans=0;
    scanf("%s",s+1);
    f[0]=1;l[++tn]=-1;
    for(i=j=1;s[i];++i)
    {
        while(s[i-l[j]-1]!=s[i])j=f[j];
        if(!c[j][s[i]-\'a\'])
        {
            l[++tn]=l[j]+2;
            for(f[tn]=f[j];s[i-l[f[tn]]-1]!=s[i];)f[tn]=f[f[tn]];
            f[tn]=c[f[tn]][s[i]-\'a\'];
            c[j][s[i]-\'a\']=tn;
        }
        ++p[j=c[j][s[i]-\'a\']];
    }
    for(i=tn;i>1;--i)
    {
        if(1ll*l[i]*p[i]>ans)ans=1ll*l[i]*p[i];
        p[f[i]]+=p[i];
    }
    printf("%lld",ans);
}

 

T2.sequence

题目大意:一个长度为N的序列,进行K次操作,每次可以将一段序列截成两段,获得两段和的乘积的得分,求最大得分及方案(1<=K<=N<=100,000,K<=200)。

思路:实际上跟截的顺序没有关系,只要知道最后截的是哪些点就能知道得分,f[i][j]表示前j个分成i段的最大得分,s表示前缀和,则f[i][j]=max(f[i-1][k]+(s[j]-s[k])*s[k]),可以O(KN^2)完成DP,把式子展开发现满足斜率优化条件,复杂度降为O(KN)。序列中元素可能为0,所以有可能出现重点使斜率优化出错,一个解决方案是算斜率的时候减去一个极小的值,就能算出一个靠谱的斜率。方案随便记下转移路径就行了,另外卡内存,需要滚动。

得分:100/100

#include<cstdio>
#include<iostream>
using namespace std;
#define ll long long
inline int read()
{
    int x=0;char c;
    while((c=getchar())<\'0\'||c>\'9\');
    for(;c>=\'0\'&&c<=\'9\';c=getchar())x=x*10+c-\'0\';
    return x;
}
#define MK 200
#define MN 100000
int s[MN+5],d[MK+1][MN+5],q[MN+5],l,r;
ll f[2][MN+5];
double cal(int p,int i,int j){return double(f[p][i]-f[p][j])/(s[j]-s[i]-1e-9);}
int main()
{
    freopen("sequence.in","r",stdin);
    freopen("sequence.out","w",stdout);
    int n,k,i,j,p,pl;
    n=read();k=read();
    for(i=1;i<=n;++i)s[i]=s[i-1]+read();
    for(i=p=1,pl=0;i<=k;++i,p^=1,pl^=1)
    {
        l=r=0;
        for(j=1;j<=n;++j)
        {
            while(l<r&&cal(pl,q[l+1],q[l])<s[j])++l;
            d[i][j]=q[l];
            f[p][j]=f[pl][q[l]]+(ll)s[j]*s[q[l]];
            f[pl][j]-=(ll)s[j]*s[j];
            while(l<r&&cal(pl,j,q[r])<cal(pl,q[r],q[r-1]))--r;
            q[++r]=j;
        }
    }
    cout<<f[pl][n]<<endl;
    for(i=n,j=k;j;--j)printf("%d ",i=d[j][i]);
    fclose(stdin);fclose(stdout);return 0;
}

 

T3.beads

题目大意:一开始你有一个点,每次你可以选择其中一种操作:1.选一个已有的点,向新的点连一条红边;2.选择一条红边,在红边上插一个新点,使红边被拆成两条蓝边。现在给你一颗树,给出边上权值,要求你给边染色,使得染色后的树可能是由一个点进行一系列操作得到的,要求最大化蓝边总长,求出这个值。(点数<=200,000)

思路:做的时候好像迷之理解错了,大致理解成一开始有n个点然后互相连边或插入(实际上每次只能把一个新点加入已有的一棵树中)。然后得到的结论是每次能把两条相邻的红边染成蓝色,乱写了个DP,结果只有20(WAWAWA)。

得分:20/100

#include<cstdio>
#include<algorithm>
using namespace std;
inline int read()
{
    int x=0;char c;
    while((c=getchar())<\'0\'||c>\'9\');
    for(;c>=\'0\'&&c<=\'9\';c=getchar())x=x*10+c-\'0\';
    return x;
}
#define MN 200000
#define INF 0x3fffffff
struct edge{int nx,t,w;}e[MN*2+5];
int h[MN+5],en,f[MN+5][2];
inline void ins(int x,int y,int w)
{
    e[++en]=(edge){h[x],y,w};h[x]=en;
    e[++en]=(edge){h[y],x,w};h[y]=en;
}
void dp(int x,int fa)
{
    int i,p,mx=-INF,mx2=-INF;
    for(i=h[x];i;i=e[i].nx)if(e[i].t!=fa)
    {
        dp(e[i].t,x);
        f[x][0]+=p=max(f[e[i].t][1]+e[i].w,f[e[i].t][0]);
        p=f[e[i].t][0]+e[i].w-p;
        if(p>mx)mx2=mx,mx=p;
        else if(p>mx2)mx2=p;
    }
    f[x][1]=f[x][0]+mx;
    f[x][0]=max(f[x][0],f[x][0]+mx+mx2);
}
int main()
{
    freopen("beads.in","r",stdin);
    freopen("beads.out","w",stdout);
    int n=read(),i,x,y;
    for(i=1;i<n;++i)x=read(),y=read(),ins(x,y,read());
    dp(1,0);
    printf("%d",f[1][0]);
    fclose(stdin);fclose(stdout);return 0;
}
View Code

正解:先考虑暴力枚举一个点作为最开始的点,定为树的根,然后以插入来加入树的点必然是插入在自己父亲和一个儿子之间(而不可能是自己的两个儿子),DP用f[i][0/1]表示以i为根的子树,i节点是否为被插入的点来DP,每次DP可以O(n)。得到一个点为根的DP状态时,我们可以用O(1)移动这个根(dfs,要转到一个儿子,先删掉自己的这棵子树,再在子树中加上自己,可能需要维护次大值)。总复杂度O(n)。

#include<cstdio>
#include<algorithm>
using namespace std;
inline int read()
{
    int x;char c;
    while((c=getchar())<\'0\'||c>\'9\');
    for(x=c-\'0\';(c=getchar())>=\'0\'&&c<=\'9\';)x=(x<<3)+(x<<1)+c-\'0\';
    return x;
}
#define MN 200000
#define INF 0x7FFFFFFF
struct edge{int nx,t,w;}e[MN*2+5];
int h[MN+5],en,f[MN+5][2],z[MN+5][2],mx[MN+5],mx2[MN+5],ans;
inline void ins(int x,int y,int w)
{
    e[++en]=(edge){h[x],y,w};h[x]=en;
    e[++en]=(edge){h[y],x,w};h[y]=en;
}
void dp(int x,int fa)
{
    mx[x]=mx2[x]=-INF;
    for(int i=h[x],y;i;i=e[i].nx)if((y=e[i].t)!=fa)
    {
        dp(y,x);
        z[y][0]=max(f[y][0],f[y][1]+e[i].w);
        z[y][1]=f[y][0]+e[i].w-z[y][0];
        f[x][0]+=z[y][0];
        if(z[y][1]>mx[x])mx2[x]=mx[x],mx[x]=z[y][1];
        else if(z[y][1]>mx2[x])mx2[x]=z[y][1];
    }
    f[x][1]=f[x][0]+mx[x];
}
void dfs(int x,int fa)
{
    ans=max(ans,f[x][0]);
    for(int i=h[x],y;i;i=e[i].nx)if((y=e[i].t)!=fa)
    {
        f[x][0]-=z[y][0];f[x][1]-=z[y][0];
        if(z[y][1]==mx[x])f[x][1]-=mx[x]-mx2[x];
        int z0=max(f[x][0],f[x][1]+e[i].w),z1=f[x][0]+e[i].w-z0;
        f[y][0]+=z0;f[y][1]+=z0;
        if(z1>mx[y])f[y][1]+=z1-mx[y],mx2[y]=mx[y],mx[y]=z1;
        else if(z1>mx2[y])mx2[y]=z1;
        dfs(y,x);
        f[x][0]+=z[y][0];f[x][1]+=z[y][0];
        if(z[y][1]==mx[x])f[x][1]+=mx[x]-mx2[x];
    }
}
int main()
{
    freopen("beads.in","r",stdin);
    freopen("beads.out","w",stdout);
    int n=read(),i,x,y;
    for(i=1;i<n;++i)x=read(),y=read(),ins(x,y,read());
    dp(1,0);dfs(1,0);
    printf("%d",ans);
    fclose(stdin);fclose(stdout);return 0;
}

 

以上是关于APIO 2014的主要内容,如果未能解决你的问题,请参考以下文章

Apio2014 回文串

BZOJ 3675 [Apio2014]序列分割

BZOJ3676: [Apio2014]回文串

P3649 [APIO2014]回文串(回文自动机)

[Apio2014]序列分割

P3649 [APIO2014]回文串