[Noi 2015] 品酒大会

题目等价于求任意两对后缀的LCP的值小于等于1,2…n的个数,以及权值乘积的最大值。

求出后缀数组的height值,然后预处理出每个height值能够成为最小的区间。
考虑每个height的值对答案的贡献:如果height[i]能够成为最小的区间为[L,R],那么个数便是(L-i+1)*(R-i+1)。而乘积最大值则一定是max[L,i-1]*max[i,R]或者min[L,i-1]*min[i,R](有可能是负数),用rmq预处理一下就可以。
(当时全场200多个人过了这题,而我写了一个下午。。。。。跪跪跪)

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <deque>
#include <queue>
#include <map>
#include <set>
#include <ctime>
#define putarray(a,n) for (int i=1;i<=n-1;i++) printf("%d ",a[i]); printf("%d\n",a[n]);
using namespace std;
typedef long long ll;
const int MAXN=300005,INF=(1<<30);

int n,A[MAXN],L[MAXN],R[MAXN]; char str[MAXN];
ll ans[MAXN],cnt[MAXN];
ll gmax(ll a,ll b) { if (a>b) return a; return b; }

class RMQ
{
    int lim,mn[MAXN][20],mx[MAXN][20];
    public:

    void init(int *a)
      {
        lim=int(log2(n)+1);
        for (int i=1;i<=n;i++) mn[i][0]=mx[i][0]=a[i];
        for (int j=1;j<=lim;j++)
          for (int i=1;i<=n;i++)
            {
              int p=i+(1<<(j-1));
              if (i+(1<<j)-1>n)
                {
                  mn[i][j]=INF,mx[i][j]=-INF;
                  continue;
                }
              mn[i][j]=min(mn[i][j-1],mn[p][j-1]),mx[i][j]=max(mx[i][j-1],mx[p][j-1]);
            }
      }

    int Askmin(int L,int R)
      {
        int k=int(log2(R-L+1));
        return min(mn[L][k],mn[R-(1<<k)+1][k]);
      }

    int Askmax(int L,int R)
      {
        int k=int(log2(R-L+1));
        return max(mx[L][k],mx[R-(1<<k)+1][k]);
      }

}Rmq;

class Suffix_array
{ 
    int sa[MAXN],rank[MAXN],height[MAXN],C[MAXN];

    void build_sa(int m)
      {
        int *x=rank,*y=height;
        for (int i=0;i<=m;i++) C[i]=0;
        for (int i=1;i<=n;i++) x[i]=int(str[i]-96),C[x[i]]++;
        for (int i=1;i<=m;i++) C[i]+=C[i-1];
        for (int i=1;i<=n;i++) sa[C[x[i]]--]=i;
        for (int k=1;k<=n;k<<=1)
          {
            int p=0;
            for (int i=n-k+1;i<=n;i++) y[++p]=i;
            for (int i=1;i<=n;i++) if (sa[i]>k) y[++p]=sa[i]-k;
            for (int i=0;i<=m;i++) C[i]=0;
            for (int i=1;i<=n;i++) C[x[y[i]]]++;
            for (int i=1;i<=m;i++) C[i]+=C[i-1];
            for (int i=n;i>=1;i--) sa[C[x[y[i]]]--]=y[i];
            swap(x,y); p=1; x[sa[1]]=1;
            for (int i=2;i<=n;i++)
              if (y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k]) x[sa[i]]=p;else x[sa[i]]=++p;
            if (p==n) break;
            m=p;
          }
      }

    void build_height()
      { 
        int k=0;
        for (int i=1;i<=n;i++) rank[sa[i]]=i;
        for (int i=1;i<=n;i++)
          { 
            if (rank[i]==1) { k=height[rank[i]]=0; continue; }
            if (k) k--; int j=sa[rank[i]-1];
            while (str[i+k]==str[j+k]) k++;
            height[rank[i]]=k;
          }
      }

    public:

    void init()
      {
        for (int i=n;i;i--) str[i]=str[i-1]; str[n+1]='#';
        build_sa(26);
        build_height();
      }

    void solve()
      { 
        for (int i=0;i<=n;i++) C[rank[i]]=A[i],cnt[i]=0,ans[i]=-ll(INF)*ll(INF);
        Rmq.init(C);

        for (int i=1;i<=n;i++)
          {
            L[i]=i-1; 
            while (height[i]<height[L[i]] && L[i]) L[i]=L[L[i]]-1;
            L[i]++;
          } 
        for (int i=n;i>=1;i--)
        {
            R[i]=i+1;
            while (height[i]<=height[R[i]] && R[i]<=n) R[i]=R[R[i]]+1;
            R[i]--;
          }

        for (int i=2;i<=n;i++)
          {
            cnt[height[i]]+=ll(i-L[i]+1)*ll(R[i]-i+1);
            ans[height[i]]=gmax(ans[height[i]],gmax(ll(Rmq.Askmin(L[i]-1,i-1))*ll(Rmq.Askmin(i,R[i])),
            ll(Rmq.Askmax(L[i]-1,i-1))*ll(Rmq.Askmax(i,R[i]))));
          }
      }

}SA;

int Get()
{
    char ch; int v=0; bool f=false;
    while (!isdigit(ch=getchar())) if (ch=='-') f=true; v=ch-48;
    while (isdigit(ch=getchar())) v=v*10+ch-48;
    if (f) return -v;else return v; 
}

int main()
{
    //freopen("savour.in","r",stdin);
    //freopen("savour.out","w",stdout);
    n=Get(); scanf("%s",str);  
    for (int i=1;i<=n;i++) A[i]=Get();
    SA.init(); 
    SA.solve();
    for (int i=n-1;i>=0;i--) cnt[i]+=cnt[i+1],ans[i]=max(ans[i],ans[i+1]);

    for (int i=0;i<=n-1;i++) 
      {
        if (ans[i]==-ll(INF)*ll(INF)) ans[i]=0;
        printf("%lld %lld\n",cnt[i],ans[i]);
      }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值