1. 程式人生 > 其它 >【點分治】括號(2022.5.28)

【點分治】括號(2022.5.28)

題目

2.1 題目描述

有一棵n 個點的無向樹,每個點上有一個標記,為'('或')',對於一個有序點對 (u,v),若
從 u 到 v (包含u,v)的有向路徑上的所有標記組成了一個合法的括號序列,則稱這是一
個完美點對,請求出有多少個完美點對。

2.2 輸入格式

第一行為一個整數 n,表示點數;
第二行為一個由’(‘和’)’組成的字串,表示每個點上的標記;
接下來n-1 行,每行兩個整數u, v,表示u,v 之間有一條無向邊。

2.3 輸出格式

輸出一行一個整數,表示完美點對的數目。

2.4 樣例輸入

4 
(()) 
1 2 
2 3 
3 

2.5 樣例輸出

2 

2.6 資料範圍與約定

對於前20%的資料 n≤100;
對於100%的資料,1≤n≤105。

解思

第一眼看感覺像是點分治,覺得應該不會考來著於是就先看第三題去了(最後打暴力寫炸了..)

沒想到真的是點分治

算是點分治的模板題吧,每層找重心然後統計過重心的路徑貢獻

注意此題括號序列是否成立的細節

上程式碼!

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int f=1,j=0;char w=getchar();
	while(w>'9'||w<'0'){
		if(w=='-')f=-1;
		w=getchar();
	}
	while(w>='0'&&w<='9'){
		j=(j<<3)+(j<<1)+w-'0';
		w=getchar();
	}
	return f*j;
}
const int N=100001,T=100000;
int head[N],to[N*2],front[N*2],tail;
int n,sum[N],cnt[N*3][2],size[N],mid[N*3][2];//cnt->0:'('starts   cnt->1:')'starts 
int root_sum,maxn,minn,maxn1,minn1;
long long ans;
bool walk[N];
char s[N];
void addline(int x,int y){
	to[++tail]=y;
	front[tail]=head[x];
	head[x]=tail;
	return ;
}
void getsize(int nown,int fa){
	size[nown]=1;
	for(int k=head[nown];k;k=front[k]){
		int x=to[k];
		if(x==fa||walk[x])continue;
		getsize(x,nown);
		size[nown]+=size[x];
	}
	return ;
}
int getcore(int nown,int fa,int all_size){
	for(int k=head[nown];k;k=front[k]){
		int x=to[k];
		if(walk[x]||x==fa)continue;
		if(size[x]>all_size/2)return getcore(x,nown,all_size);
	}
	return nown;
}
void getcnt(int nown,int fa,int nowsum,int ll,int rr){
	nowsum+=sum[nown];
	if(ll<0&&sum[nown]>0)ll++;
	if(sum[nown]<0)ll--;
	if(rr>0&&sum[nown]<0)rr--;
	if(sum[nown]>0)rr++; 
	if(nowsum<=0&&sum[nown]<0&&rr==0){
		ans+=cnt[T-nowsum][0];
		mid[T+nowsum-root_sum][1]++;
		minn1=min(minn1,T+nowsum-root_sum);
		maxn1=max(maxn1,T+nowsum-root_sum);
	}
	if(nowsum>=0&&sum[nown]>0&&ll==0){
		ans+=cnt[T-nowsum][1];
		mid[T+nowsum-root_sum][0]++;
		minn1=min(minn1,T+nowsum-root_sum);
		maxn1=max(maxn1,T+nowsum-root_sum);
	}
	for(int k=head[nown];k;k=front[k]){
		int x=to[k];
		if(x==fa||walk[x])continue;
		getcnt(x,nown,nowsum,ll,rr);
	}
	return ;
}
void work(int nown){
	getsize(nown,0);
	int core=getcore(nown,0,size[nown]);
	walk[core]=true;
	for(int k=head[core];k;k=front[k]){
		int x=to[k];
		if(walk[x])continue;
		work(x);
	}
	walk[core]=false;
	root_sum=sum[core];
	maxn=T;minn=T;
	cnt[T][0]=cnt[T][1]=1;
	for(int k=head[core];k;k=front[k]){
		int x=to[k];
		if(walk[x])continue;
		maxn1=T;minn1=T;
		getcnt(x,core,root_sum,0,0);
		for(int i=minn1;i<=maxn1;i++)cnt[i][0]+=mid[i][0],cnt[i][1]+=mid[i][1],mid[i][0]=mid[i][1]=0;
		minn=min(minn,minn1);
		maxn=max(maxn,maxn1);
	}
	for(int i=minn;i<=maxn;i++)cnt[i][0]=cnt[i][1]=0;
	return ;
}
signed main(){ 
	n=read();
	scanf("%s",s+1);
	for(int i=1;i<=n;i++){
		if(s[i]=='(')sum[i]=1;
		else sum[i]=-1;
	}
	for(int i=1;i<n;i++){
		int x=read(),y=read();
		addline(x,y);addline(y,x);
	}
	work(1);
	printf("%lld",ans);
	return 0;
}