【解题报告】洛谷P5658 括号树
【解题报告】洛谷P5658 括号树
题目链接
https://www.luogu.com.cn/problem/P5658
思路
看到这道题目,我们有必要了解一下括号序列
然后不会打正解?
好,打部分分
-
的部分
-
这一部分的分数我们可以直接打暴力,然后三个循环 再加上一个 的判断被嵌套在这三个循环里面,总共是 ,可以过掉这一部分的数据
期望得分 20pts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
using namespace std;
const int maxn=500005;
int n,fa[maxn],ans;
bool flag=true;
char s[maxn];
struct edge{
int e,next;
}ed[maxn<<1];
int en,first[maxn];
void add_edge(int s,int e)
{
en++;
ed[en].next=first[s];
first[s]=en;
ed[en].e=e;
}
bool check(int l,int r)
{
int cnt=0;
for(int i=l;i<=r;i++)
{
if(s[i]=='(') cnt++;
else if(s[i]==')') cnt--;
if(cnt<0) return false;
}
if(cnt==0) return true;
return false;
}
int main()
{
cin>>n;
scanf("%s",s+1);
for(int i=1;i<=n-1;i++)
{
int x;
cin>>x;
fa[i+1]=x;
if(fa[i+1]!=i) flag=false;
add_edge(i+1,x);
add_edge(x,i+1);
}
if(flag)
{
for(int i=1;i<=n;i++)
{
int cnt=0;
for(int l=1;l<=i;l++)
{
for(int r=l+1;r<=i;r++)
{
if(check(l,r))
cnt++;
}
}
ans=(ans^(cnt*i));
}
cout<<ans<<'\n';
return 0;
}
return 0;
} -
的部分
我们可以发现,实际上我们的瓶颈在于我们有一个 的美剧左端点,枚举右端点和一次判断,我们要完成 的数据,至少要达到 的时间复杂度,所以我们要继续优化
根据大佬的提示,我们可以知道下面一段话
我们发现,一个后括号如果能匹配一个前括号,假设这个前括号的前1位同样有一个已经匹配了的后括号,那么我们势必可以把当前的匹配和之前的匹配序列合并,当前的这个后括号的贡献值,其实就等于前面那个后括号的贡献值+1!
然后我们就可以按照这个写出代码,这个结论要用手模一下
期望得分 55pts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
using namespace std;
const int maxn=500005;
int n,fa[maxn],ans;
int st[maxn],don[maxn],sum[maxn],size;
bool flag=true;
char s[maxn];
struct edge{
int e,next;
}ed[maxn<<1];
int en,first[maxn];
void add_edge(int s,int e)
{
en++;
ed[en].next=first[s];
first[s]=en;
ed[en].e=e;
}
bool check(int l,int r)
{
int cnt=0;
for(int i=l;i<=r;i++)
{
if(s[i]=='(') cnt++;
else if(s[i]==')') cnt--;
if(cnt<0) return false;
}
if(cnt==0) return true;
return false;
}
signed main()
{
cin>>n;
scanf("%s",s+1);
for(int i=1;i<=n-1;i++)
{
int x;
cin>>x;
fa[i+1]=x;
if(fa[i+1]!=i) flag=false;
add_edge(i+1,x);
add_edge(x,i+1);
}
if(flag)
{
for(int i=1;i<=n;i++)
{
if(s[i]==')')
{
if(size==0) continue;
int t=st[size];
don[i]=don[t-1]+1;
size--;
}
else if(s[i]=='(')
st[++size]=i;
}
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+don[i];
for(int i=1;i<=n;i++)
ans=(ans^(i*sum[i]));
cout<<ans<<'\n';
return 0;
}
return 0;
}
-
-
无特殊性质
这种情况,我们要做的是把序列上的问题转化到树上,每个结点到根的简单路径有且仅有一条,所以如果单单是对于这一条链的话,策略跟上面一样,但是我们的前一个结点就变了,上一个左括号的,也就是上面的代码中的 我们要改成 这样,然后计算每个结点对应的合法序列的个数也要改变
那我们压进栈里面的数字怎么办啊
我们设置了一个中间值,记录上一个左括号的位置,如果这个中间值为 的话,说明这是一个左括号,那么在栈里面加入它,如果不是 的话,判断一下栈的大小是否为 ,如果不是 的话,我们就可以发现一个事情,在之前的递归中,我们一定压入了一个数,但是这个数会影响后面的答案,所以我们将这个值弹出,相当于回溯一下子
期望得分100pts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
using namespace std;
const int maxn=500005;
int n,fa[maxn],ans;
int st[maxn],don[maxn],sum[maxn],size;
bool flag=true;
char s[maxn];
struct edge{
int e,next;
}ed[maxn<<1];
int en,first[maxn];
void add_edge(int s,int e)
{
en++;
ed[en].next=first[s];
first[s]=en;
ed[en].e=e;
}
void dfs(int x)
{
int tmp=0;
if(s[x]==')')
{
if(size!=0)
{
tmp=st[size];
don[x]=don[fa[tmp]]+1;
size--;
}
}
else if(s[x]=='(')
st[++size]=x;
sum[x]=sum[fa[x]]+don[x];
for(int i=first[x];i;i=ed[i].next)
{
int e=ed[i].e;
dfs(e);
}
if(tmp!=0)
st[++size]=tmp;
else if(size)
size--;
}
bool check(int l,int r)
{
int cnt=0;
for(int i=l;i<=r;i++)
{
if(s[i]=='(') cnt++;
else if(s[i]==')') cnt--;
if(cnt<0) return false;
}
if(cnt==0) return true;
return false;
}
signed main()
{
cin>>n;
scanf("%s",s+1);
for(int i=2;i<=n;i++)
{
int x;
cin>>x;
add_edge(x,i);
fa[i]=x;
}
dfs(1);
for(int i=1;i<=n;i++)
ans=(ans^(i*sum[i]));
cout<<ans<<'\n';
return 0;
}
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 wweiyiのblog!
评论
