Luogu P5658 括号树|搜索+递推

CSP2019 S组D1T2

题目链接

题目大意:一棵树上每个节点有(或),根到每个节点的路径可形成一括号串,求根到每个节点形成的括号串的合法括号字串个数,取异或和。

解析:该题为CSP2019提高组容易拿分的一道。选择写链是个不错选择(然后我就这里打挂了 70pts),正解其实在考场上也能想出。

子串说明要连续,而我们接好一个括号后它显然可以接上前面连续的括号序列。

即:设一个节点\(x\)的答案为\(ans_x\),设此次接的左括号位置\(y\),则\(ans_x+=ans_y+1\)(因为自己单独也算一个)。而一个节点的父亲节点的答案,也会在该节点中原封不动的展现,故应加上节点的所有父亲节点之答案。

我们采用\(dfs\)算法遍历整棵树,遇到节点为左括号进栈,否则在栈中匹配一个还没有匹配的左括号,成功匹配,则按上面累加答案。

这里有一个问题,即从\(dfs\)退出时,我们在这里的东西需销毁,这同时牵涉到上面左括号的匹配。因此,这里采用\(vis\)来维护上述。若该节点为左括号,则结束搜索时将该节点退出栈。否则取消该节点匹配的左括号的已匹配状态,然后退出搜索。

具体解析见代码。

#include<bits/stdc++.h>
using namespace std;
int cc,to[600000],net[600000],fr[600000],fa[600000],zhan[600000],t,n,nt[600000];
long long s[600000];
char ch[600000];string st;bool vis[600000];
void addedge(int u,int v)
{
	cc++;
	to[cc]=v;net[cc]=fr[u];fr[u]=cc;
}
void dfs1(int x)//匹配括号
{
	if (ch[x]=='(') 
	{
		zhan[++t]=x;
	}//压栈
	else
	{
		nt[x]=t;//备份,不与全局冲突,后边操作用
		while (vis[zhan[nt[x]]]&&nt[x])
		{
			nt[x]--;
		}
		if (nt[x])
		{
			vis[zhan[nt[x]]]=true;
			s[x]=s[x]+1+s[fa[zhan[nt[x]]]];
		}//匹配左括号
	}
	for (int i=fr[x];i;i=net[i])
	{
		dfs1(to[i]);
	}//往下继续找
	if (ch[x]=='(')
		 t--;
	else vis[zhan[nt[x]]]=false;//还原数组
}
void dfs2(int x)//起累加父亲节点答案作用
{
	for (int i=fr[x];i;i=net[i])
	{
		s[to[i]]+=s[x];
		dfs2(to[i]);
	}
}
int main()
{
//	freopen("brackets.in","r",stdin);
//	freopen("brackets.out","w",stdout);
	cin>>n;
	cin>>st;
	for (int i=1;i<=n;i++)
	{
		ch[i]=st[i-1];
	}
	bool lian=true;
	for (int i=2;i<=n;i++)
	{
		cin>>fa[i];
		if (fa[i]!=i-1) lian=false;
		addedge(fa[i],i);
	}	//建树
	{
		dfs1(1);
		dfs2(1);
	}//到这里原始答案已全部求出
	long long ans=s[1];
	for (int i=2;i<=n;i++)
	{
		ans^=s[i]*i;
	}
	cout<<ans<<endl;
	return 0;
}

附:链的部分分代码(其实与正解中统计答案相似)

	if (lian)
	{
		for (int i=1;i<=n;i++)
		{
			if (ch[i]=='(') 
			{
				zhan[++t]=i;
			}
			else 
			{
				if (!t) continue; 
				s[i]=1+s[fa[zhan[t]]];
				t--;
			}
		}
		for (int i=1;i<=n;i++)
		{
			s[i]+=s[i-1];
		}
	}
posted @ 2020-01-18 23:22  fmj_123  阅读(149)  评论(0编辑  收藏  举报