【点分治】括号(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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通