【uoj131】 NOI2015—品酒大会

http://uoj.ac/problem/131 (题目链接)

题意

  给出一个字符串,每个后缀有一个权值${a_i}$,这些后缀两两之间存在公共前缀。问能够组成长度从0~n-1的公共前缀的后缀的方案数以及他们权值的最大乘积。

Solution

  听LCF说这是水题,就来做了。。

  lyp学长说SAM构出来后就两个东西:在自动机上dp,在后缀树上搞事情。

  于是我们很容易想到在后缀树上dp(感觉在自动机上dp没什么卵用),于是我们把串反过来见后缀自动机,然后建出后缀树,在上面树形dp就可以了。

  假设当前dfs到了节点x。我们用mx[x][0,1]记录当前子树中最大的两个后缀的权值,因为权值可能为负数,所以还要用mn[x][0,1]记录当前子树中最小的两个后缀的权值,sum[x]记录当前子树中后缀的个数。这样你发现这些信息就能够更新长度为0~len[x]的答案,其中len[x]表示其在后缀树中的“深度”(其实深度这个表示并不是特别妥当→_→),那么就可以做了。

细节

  细节多的抠脚,建议想清楚再写→_→

代码

// uoj131
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<cmath>
#define LL long long
#define inf 1ll<<60
#define Pi acos(-1.0)
#define free(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout);
using namespace std;

const int maxn=300010;
int n;
char s[maxn];
LL a[maxn],ans[maxn],cnts[maxn];

namespace SAM {
	int len[maxn<<1],par[maxn<<1],pos[maxn<<1],ch[maxn<<1][26];
	int last,Dargen,sz,cnt,head[maxn<<1];
	LL mx[maxn<<1][2],mn[maxn<<1][2],sum[maxn<<1];

	struct edge {int to,next;}e[maxn<<2];

	void link(int u,int v) {
		e[++cnt]=(edge){v,head[u]};head[u]=cnt;
	}
	void Extend(int c) {
		int np=++sz,p=last;last=np;
		len[np]=pos[np]=len[p]+1;
		for (;p && !ch[p][c];p=par[p]) ch[p][c]=np;
		if (!p) par[np]=Dargen;
		else {
			int q=ch[p][c];
			if (len[p]+1==len[q]) par[np]=q;
			else {
				int nq=++sz;len[nq]=len[p]+1;
				memcpy(ch[nq],ch[q],sizeof(ch[q]));
				par[nq]=par[q];
				par[np]=par[q]=nq;
				for (;p && ch[p][c]==q;p=par[p]) ch[p][c]=nq;
			}
		}
	}
	void build() {
		last=Dargen=sz=1;
		for (int i=1;i<=n>>1;i++) swap(s[i],s[n-i+1]),swap(a[i],a[n-i+1]);
		for (int i=1;i<=n;i++) Extend(s[i]-‘a‘);
	}
	void dfs(int x) {
		mx[x][0]=mx[x][1]=-inf;
		mn[x][0]=mn[x][1]=inf;
		if (pos[x]) mn[x][0]=mx[x][0]=a[pos[x]],sum[x]=1;
		for (int i=head[x];i;i=e[i].next) {
			dfs(e[i].to);
			cnts[len[x]]+=sum[x]*sum[e[i].to];
			sum[x]+=sum[e[i].to];
			if (mx[x][0]<mx[e[i].to][0]) {
				mx[x][1]=mx[x][0],mx[x][0]=mx[e[i].to][0];
			}
			else if (mx[x][1]<mx[e[i].to][0]) mx[x][1]=mx[e[i].to][0];
			if (mx[x][1]<mx[e[i].to][1]) mx[x][1]=mx[e[i].to][1];
			if (mn[x][0]>mn[e[i].to][0]) {
				mn[x][1]=mn[x][0],mn[x][0]=mn[e[i].to][0];
			}
			else if (mn[x][1]>mn[e[i].to][0]) mn[x][1]=mn[e[i].to][0];
			if (mn[x][1]>mn[e[i].to][1]) mn[x][1]=mn[e[i].to][1];
		}
		if (mx[x][0]!=-inf && mx[x][1]!=-inf) ans[len[x]]=max(ans[len[x]],mx[x][0]*mx[x][1]);
		if (mn[x][0]!=inf && mn[x][1]!=inf) ans[len[x]]=max(ans[len[x]],mn[x][0]*mn[x][1]);
	}
	void prepare() {
		for (int i=2;i<=sz;i++) link(par[i],i);
		for (int i=0;i<=n;i++) ans[i]=-inf;
		dfs(Dargen);
	}
}
using namespace SAM;

int main() {
	scanf("%d",&n);
	scanf("%s",s+1);
	for (int i=1;i<=n;i++) scanf("%lld",&a[i]);
	build();
	prepare();
	for (int i=n-1;i>=0;i--) {
		ans[i]=max(ans[i],ans[i+1]);
		cnts[i]+=cnts[i+1];
	}
	for (int i=0;i<n;i++) printf("%lld %lld\n",cnts[i],ans[i]==-inf ? 0 : ans[i]);
	return 0;
}

  

时间: 2024-10-13 04:19:30

【uoj131】 NOI2015—品酒大会的相关文章

【BZOJ4199】[Noi2015]品酒大会 后缀数组+并查集

[BZOJ4199][Noi2015]品酒大会 题面:http://www.lydsy.com/JudgeOnline/wttl/thread.php?tid=2144 题解:听说能用SAM?SA默默水过~ 本题的实现还是非常简单的,先求出height数组,然后两杯酒'r'相似就等价于二者中间的height都>=r,于是我们将height排序,从大到小扔进去,那么所有连续的区间中的点对就都是相似的了.维护连续区间可以用并查集.统计点对个数需要维护size,统计最大乘积需要维护最(次)大(小)值,

[UOJ#131][BZOJ4199][NOI2015]品酒大会 后缀数组 + 并查集

[UOJ#131][BZOJ4199][NOI2015]品酒大会 试题描述 一年一度的“幻影阁夏日品酒大会”隆重开幕了.大会包含品尝和趣味挑战两个环节,分别向优胜者颁发“首席品酒家”和“首席猎手”两个奖项,吸引了众多品酒师参加. 在大会的晚餐上,调酒师 Rainbow 调制了 n杯鸡尾酒.这 n杯鸡尾酒排成一行,其中第 i杯酒 (1≤i≤n ) 被贴上了一个标签 si ,每个标签都是 26 个小写英文字母之一.设 Str(l,r)表示第 l杯酒到第 r 杯酒的 r−l+1 个标签顺次连接构成的字

BZOJ 4199: [Noi2015]品酒大会( 后缀数组 + 并查集 )

求出后缀数组后, 对height排序, 从大到小来处理(r相似必定是0~r-1相似), 并查集维护. 复杂度O(NlogN + Nalpha(N)) ----------------------------------------------------------------------------------- #include<cstdio> #include<cstring> #include<algorithm> using namespace std; ty

字符串(后缀数组||SAM):NOI2015 品酒大会

这道题可以转化为计数类问题. 若使用后缀数组,那么答案就是所有位置二元组(i,j)的lcp对0~lcp答案段的贡献.然后发现若一个二元组有x的贡献,那么对x-1有同样的贡献,考虑先求出lcp(max)的答案,再传给lcp(max-1)等等,复杂度是O(N)的. 若用SAM,那么需要求的答案在x与fa[x]的转移之间,因为后缀自动机中每个点所代表的出现位置和出现次数是相等的,可以合并,事实上还是用到了后缀数组的叠加的原理,从叶子节点一路传上去…………………………………………… 还有要注意INF要足

BZOJ4199: [Noi2015]品酒大会

后缀数组height排序后并查集合并 也就是height较大的合并不影响较小的 num[i]=num[i+1]  ans[i]=ans[i+1] 合并时,num+=sz[x]*sz[y],ans=max(mn[x]*mn[y],mx[x]*mx[y],ans) 这种思路适应于求点对,还可以考虑启发式合并 后缀数组还有的常见思路就是二分,height分组 1 #include<bits/stdc++.h> 2 using namespace std; 3 #define N 300005 4 #

●UOJ 131 [NOI2015] 品酒大会

题链: http://uoj.ac/problem/131 题解: 网上大多数的方法都是用并查集维护.这里呢,给出另一种自己YY的解法(但实际上本质差不多吧): 后缀数组,RMQ,单调栈 1).预处理 1].首先对字符串后缀排序,得到 sa[i],rank[i],height[i]    2].然后维护出 L[i]:表示在后缀数组中,排名最小(记其排名为 L[i])的后缀与排名为 i的后缀的LCP>=hei[i]    同理 R[i]:表示在后缀数组中,排名最大(记其排名为 R[i])的后缀与排

BZOJ 4199 [Noi2015]品酒大会:后缀数组 + 并查集

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=4199 题意: 给你一个长度为n的字符串s,和一个长为n的数组v. 对于每个整数r∈[0,n-1]: (1)问你有多少对后缀(suffix(i), suffix(j)),满足LCP(suffix(i), suffix(j)) >= r (2)输出mul[r] = max(v[i]*v[j]),其中i,j满足(1)的条件 题解: 先考虑第(1)问. 由于LCP只受连续的一段height中最小

Bzoj4199:[NOI2015]品酒大会

题面 Bzoj4199 Sol 后缀数组 显然的暴力就是求\(LCP\)+差分 \(40\)分 # include <bits/stdc++.h> # define RG register # define IL inline # define Fill(a, b) memset(a, b, sizeof(a)) using namespace std; typedef long long ll; const int _(3e5 + 5); IL int Input(){ RG int x =

bzoj4199: [Noi2015]品酒大会 (并查集 &amp;&amp; 后缀数组)

据说用后缀自动机 + dp也能做 然而并不会 后缀数组的做法呢 就是先建个后缀数组,求出height值,此时如果直接找,复杂度是n ^ 2的,肯定会超时. 但是height大的值是不会对小的产生影响的,所以可以按height大小,从大到小合并两个区间,用并查集维护就可以了 代码如下 1 #include <cstdio> 2 #include <algorithm> 3 #include <cstring> 4 using namespace std; 5 const