zoukankan      html  css  js  c++  java
  • [NOI2016]品酒大会(后缀数组+并查集)

    [NOI2016]品酒大会(后缀数组+并查集)

    题面

    给出一个 长度为 n 的字符串,每一位有一个权值 val。定义两个位字符为 r 相似,是指分别从这两个字符开始,到后面的 r 个字符都相等。两个 r 相似的字符还有一个权值为这两个字符权值的乘积。问对于 (r in [0,n-1]),统计出有多少种方法可以选出 2 个“r 相似”的字符,并回答选择 2 个”r 相似”的字符可以得到的权值的最大值。

    分析

    先求出这个串的height数组。对于某个(r),height数组上值(geq r)的位置一定会形成若干个连续区间。这些区间里面任意选两个后缀的LCP都(geq r),即它们是"(r)相似的"。

    那么我们从大到小枚举(r),对于每个(r),我们把height值为r的位置与相邻位置合并。那么这些新合并的位置与原来位置的LCP都是r。我们可以用并查集维护集合大小,集合中后缀对应的最小值与最大值,合并的时候就可以更新方案数(加上集合大小乘积),以及乘积最大值(由于可能存在负数,所以要维护最小值,可能两个负的最小值之积更大)。

    细节见代码。

    代码

    //https://www.luogu.com.cn/problem/P2178
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<vector>
    #define maxs 128 
    #define maxn 300000
    using namespace std;
    typedef long long ll;
    void rsort(int *ans,int *fi,int *se,int n,int m){
    	static int buck[maxn+5];
    	for(int i=0;i<=m;i++) buck[i]=0;
    	for(int i=1;i<=n;i++) buck[fi[i]]++;
    	for(int i=1;i<=m;i++) buck[i]+=buck[i-1];
    	for(int i=n;i>=1;i--) ans[buck[fi[se[i]]]--]=se[i];
    }
    int sa[maxn+5],rk[maxn+5],height[maxn+5];
    void suffix_sort(char *s,int n,int m){
    	static int se[maxn+5];
    	for(int i=1;i<=n;i++){
    		rk[i]=s[i];
    		se[i]=i;
    	}
    	rsort(sa,rk,se,n,m);
    	for(int k=1;k<=n;k*=2){
    		int p=0;
    		for(int i=n-k+1;i<=n;i++) se[++p]=i;
    		for(int i=1;i<=n;i++) if(sa[i]>k) se[++p]=sa[i]-k;
    		rsort(sa,rk,se,n,m);
    		swap(rk,se);
    		rk[sa[1]]=1;
    		p=1;
    		for(int i=2;i<=n;i++){
    			if(se[sa[i-1]]==se[sa[i]]&&se[sa[i-1]+k]==se[sa[i]+k]) rk[sa[i]]=p;
    			else rk[sa[i]]=++p;
    		}
    		if(p==n) break;
    		m=p;
    	}
    }
    void get_height(char *s,int n,int m){
    	suffix_sort(s,n,m);
    	for(int i=1;i<=n;i++) rk[sa[i]]=i;
    	int k=0;
    	for(int i=1;i<=n;i++){
    		if(k) k--;
    		int j=sa[rk[i]-1];
    		while(s[i+k]==s[j+k]) k++;
    		height[rk[i]]=k;
    	}
    }
    
    int n;
    char str[maxn+5];
    int val[maxn+5];
    
    ll cnt[maxn+5];
    ll mul[maxn+5];//i相似的方案数和最大乘积 
    int fa[maxn+5];
    ll sz[maxn+5],maxv[maxn+5],minv[maxn+5];
    //集合大小,最大值,最小值 
    int find(int x){
    	if(fa[x]==x) return x;
    	else return fa[x]=find(fa[x]);
    } 
    void merge(int x,int y){
    	int fx=find(x),fy=find(y);
    	if(fx==fy) return;
    	int r=height[y];//按照height从大到小合并,lcp一定是当前height值
    	cnt[r]+=sz[fx]*sz[fy];
    	mul[r]=max(mul[r],max(maxv[fx]*maxv[fy],minv[fx]*minv[fy]));//可能有负数,所以两个最小值相乘也可以很大
    	maxv[fy]=max(maxv[fx],maxv[fy]);
    	minv[fy]=min(minv[fy],minv[fx]);
    	sz[fy]+=sz[fx];
    	fa[fx]=fy;
    }
    
    vector<int>pos[maxn+5];//每个height值在height数组上对应的下标(rank) 
    int main(){
    	scanf("%d",&n);
    	scanf("%s",str+1);
    	for(int i=1;i<=n;i++) scanf("%d",&val[i]); 
    	get_height(str,n,maxs);
    	for(int i=1;i<=n;i++){
    		fa[i]=i;
    		sz[i]=1;
    		maxv[i]=minv[i]=val[sa[i]];//按照height数组的顺序,即排名合并 
    	}
    	memset(cnt,0,sizeof(cnt));
    	memset(mul,0xcf,sizeof(mul));
    	for(int i=1;i<=n;i++) pos[height[i]].push_back(i);
    	for(int i=n-1;i>=0;i--){//按照height从大到小合并 
    		for(int j=0;j<(int)pos[i].size();j++){
    			int x=pos[i][j];
    			merge(x-1,x);//把height>=i的相邻值并成连续区间 
    		}
    	}
    	for(int i=n-2;i>=0;i--){
    		cnt[i]+=cnt[i+1];
    		mul[i]=max(mul[i],mul[i+1]);
    		//求后缀和 
    		//我们合并时求出的是lcp恰好为i的答案,而题目求的是lcp>=i的答案 
    	}
    	for(int i=0;i<=n-1;i++){
    		printf("%lld ",cnt[i]);
    		if(cnt[i]!=0) printf("%lld
    ",mul[i]);
    		else printf("0
    ");
    	}
    }
    
    
  • 相关阅读:
    python 生成白画布,黑画布和指定颜色画布(纯白色图片或黑色图片或纯色图片)(python generate white, black or pure color canva)
    tomcat启动出现乱码
    nicstat命令安装与使用
    sar命令详解【转】
    使用 virtualBox 让虚拟机连接外网
    跟面试官侃半小时MySQL事务,说完原子性、一致性、持久性的实现【转】
    jmeter的HTML Link Parser链路解析器的使用方法
    Jmeter导入badboy的jmx文件后,使用后置处理器的正则表达式提取器提取URL论坛板块ID失败
    Jforum中文版本不能发帖的问题
    部署JForum 2.1.9安装时遇到的问题:报错数据库问题
  • 原文地址:https://www.cnblogs.com/birchtree/p/12246419.html
Copyright © 2011-2022 走看看