zoukankan      html  css  js  c++  java
  • 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;
    }
  • 相关阅读:
    [JS]手写动画最小时间间隔设置
    [CSS3]chrome浏览器中支持汉字的最小像素是12px,如何让显示更小的字体
    [HTML,CSS]div+css垂直水平居中
    promise经典题目
    HTML5新兴API
    使用MessageChannel(消息通道)进行深拷贝
    原生js手写Promise
    github图片显示不出来-已解决
    前端原生js加密解密
    vue-cli3前端工程静态文件下载
  • 原文地址:https://www.cnblogs.com/ditoly/p/APIO-2014.html
Copyright © 2011-2022 走看看