1. 程式人生 > >4199. [NOI2015]品酒大會【後綴數組+並查集】

4199. [NOI2015]品酒大會【後綴數組+並查集】

算法 等級 要求 rank += HA ems 集中 sample

Description

Sandy和Sue的熱衷於收集幹脆面中的卡片。然而,Sue收集卡片是因為卡片上漂亮的人物形象,而Sandy則是為了積 攢卡片兌換超炫的人物模型。每一張卡片都由一些數字進行標記,第i張卡片的序列長度為Mi,要想兌換人物模型 ,首先必須要集夠N張卡片,對於這N張卡片,如果他們都有一個相同的子串長度為k,則可以兌換一個等級為k的人 物模型。相同的定義為:兩個子串長度相同且一個串的全部元素加上一個數就會變成另一個串。Sandy的卡片數遠 遠小於要求的N,於是Sue決定在Sandy的生日將自己的卡片送給Sandy,在Sue的幫助下,Sandy終於集夠了N張卡片 ,但是,Sandy並不清楚他可以兌換到哪個等級的人物模型,現在,請你幫助Sandy和Sue,看看他們最高能夠得到 哪個等級的人物模型。

Input

第一行為一個數N,表示可以兌換人物模型最少需要的卡片數,即Sandy現在有的卡片數 第i+1行到第i+N行每行第一個數為第i張卡片序列的長度Mi,之後j+1到j+1+Mi個數,用空格分隔,分別表示序列中 的第j個數 n<=1000,M<=1000,2<=Mi<=101

Output

一個數k,表示可以獲得的最高等級。

Sample Input

2
2 1 2
3 4 5 9

Sample Output

2 罕見的抄了一發題解……畢竟NOI原題哪有那麽容易寫出來的道理
並沒有什麽罕見的算法,不過思路還是很巧妙的
一開始我的想法是用單調棧,而且好像的確有這種算法的std,不過我亂搞了一下午樣例都沒過於是只好作罷
改為大眾並查集做法。
首先很容易發現,對於任意一對r相似,它一定是k(0<k<r)相似的
所以求出height數組後按其中的值排序,然後從大到小做
當前需要處理的串為i和i-1,設前綴長度為k
易知若將兩個並查集合並,則當前的前綴在並查集中一定是最小的,所以Ans[k][0]+=兩棵樹size的乘積(因為任意兩兩前綴都是k相似的,可以配對)
除了並查集的size,還維護一下並查集的max和min值,
則Ans[k][1]=max(Ans[k][1],Max1*Max2,Min1*Min2)
維護min值是為了防止有很小的復數這種情況(負負得正)
最後因為Ans[i]也是滿足Ans[i+1]的,所以做個前綴和合並一下答案就好

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define MAXN (300000+100)
#define LL long long
using namespace std;
LL wt[MAXN],wa[MAXN],wb[MAXN];
LL SA[MAXN],Rank[MAXN],Height[MAXN];
char r[MAXN];
LL a[MAXN],cnt,Ans[MAXN][2];
LL ID[MAXN],INF;
LL Father[MAXN],Size[MAXN],Max[MAXN],Min[MAXN];
LL n,m=130;

bool cmp(LL *y,LL a,LL b,LL k)
{
	LL arank1=y[a];
	LL brank1=y[b];
	LL arank2=a+k>=n?-1:y[a+k];
	LL brank2=b+k>=n?-1:y[b+k];
	return arank1==brank1 && arank2==brank2;
} 

void Build_SA()
{
	LL *x=wa,*y=wb;
	for (LL i=0;i<m;++i) wt[i]=0;
	for (LL i=0;i<n;++i) wt[x[i]=r[i]]++;
	for (LL i=1;i<m;++i) wt[i]+=wt[i-1];
	for (LL i=n-1;i>=0;--i) SA[--wt[x[i]]]=i;
	
	for (LL j=1;j<=n;j<<=1)
	{
		LL p=0;
		for (LL i=n-j;i<n;++i) y[p++]=i;
		for (LL i=0;i<n;++i) if (SA[i]>=j) y[p++]=SA[i]-j;
		
		for (LL i=0;i<m;++i) wt[i]=0;
		for (LL i=0;i<n;++i) wt[x[y[i]]]++;
		for (LL i=1;i<m;++i) wt[i]+=wt[i-1];
		for (LL i=n-1;i>=0;--i) SA[--wt[x[y[i]]]]=y[i];
		
		m=1;swap(x,y);
		x[SA[0]]=0;
		for (LL i=1;i<n;++i)
			x[SA[i]]=cmp(y,SA[i],SA[i-1],j)?m-1:m++;
		if (m>=n) break;
	}
}

void Build_Height()
{
	for (LL i=0;i<n;++i) Rank[SA[i]]=i;
	LL k=0;
	Height[0]=0;
	for (LL i=0;i<n;++i)
	{
		if (!Rank[i]) continue;
		if (k) k--;
		LL j=SA[Rank[i]-1];
		while (r[i+k]==r[j+k]) k++;
		Height[Rank[i]]=k;
	}
}

LL Find (LL x) {return Father[x]==x?x:Father[x]=Find(Father[x]);}
void Merge (LL x,LL y)
{
	LL f1=Find(x),f2=Find(y);
	
	
	LL k=Height[x];
	Ans[k][0]+=Size[f2]*Size[f1];
	Ans[k][1]=max(max(Max[f2]*Max[f1],Min[f2]*Min[f1]),Ans[k][1]);
	
	Min[f1]=min(Min[f1],Min[f2]);
	Max[f1]=max(Max[f1],Max[f2]);
	Father[f2]=f1;
	Size[f1]+=Size[f2];
}

void Solve()
{
	for (LL i=0;i<n;++i) 
	{
		Father[i]=i;
		Size[i]=1;
		Max[i]=Min[i]=a[SA[i]];
	}
	for (LL i=0;i<=n-1;++i)
		if (Find(ID[i])!=Find(ID[i]-1))
			Merge(ID[i],ID[i]-1);
}

bool cmp1(LL x,LL y)
{
	return Height[x]>Height[y];
}

int main()
{
	memset(&INF,0x7f,sizeof(INF));
	scanf("%lld",&n);
	scanf("%s",r);
	for (LL i=0;i<n;++i) 
		scanf("%lld",&a[i]);
	Build_SA();
	Build_Height();
	for (LL i=0;i<n;++i)
	{
		ID[i]=i;
		Ans[i][0]=0;
		Ans[i][1]=-INF;	
	}
	sort(ID,ID+n,cmp1);
	Solve();
	for (LL i=n-2;i>=0;--i)
	{
		Ans[i][0]+=Ans[i+1][0];
		Ans[i][1]=max(Ans[i][1],Ans[i+1][1]);
	}
	for (LL i=0;i<n;++i)
		printf("%lld %lld\n",Ans[i][0],Ans[i][0]==0?0:Ans[i][1]);
}

4199. [NOI2015]品酒大會【後綴數組+並查集】