Brackets

序言

可以先参考题目: 最长括号匹配_NOI导刊2009提高(1)

正文

首先, 从最简单的入手, 也就是一条链.

这里要求的是 "括号串中有多少个互不相同的子串是合法括号串", 首先, 可以借鉴一下序言中那道题目的思路.

用 dp_length[i] 表示 "括号串以第 i 位结尾的最长合法序列长度", 那么可以得到:

if (bracket[i] == ')')    //')'
{
    if (bracket[i - 1 - dp_length[i - 1]] == '(')
        dp_length[i] = dp_length[i - 1] + 2 + dp_length[i - 2 - dp_length[i - 1]];
}

Why? 若 A, B 是括号匹配的字符串, 那么对于串 "A(B)"(当前位为 ')'), dp_length[i] = dp_length[B] + dp_length[A] + 2.

图片说明

接下来, 看看 "有多少个互不相同的子串是合法括号串".

用 dp_sum[i] 表示 "括号串以第 i 位结尾的字串中有多少个互不相同的子串是合法括号串", 那么, 不难得到:

if (bracket[i] == ')')    //')'
{
    if (bracket[i - 1 - dp_length[i - 1]] == '(')
    {
        dp_length[i] = dp_length[i - 1] + 2 + dp_length[i - 2 - dp_length[i - 1]];
        dp_count[i] = dp_count[i] + 1 + dp_count[i - 2 - dp_length[i - 1]];
    }
}
dp_sum[i] = dp_sum[i - 1] + dp_count[i];

dp_count[] 的推导也是同理.

至此, 我们已经解决了链的情况. 于是, 我们发现, 通过 dfs 可以将树转化成链!

然而...Time Limit Exceeded...

But why? 原因在于每找到一条链, 都要从根节点开始求解, 而各条链又有很大一部分是重复的.

比如这棵树:

图片说明

在我们计算了 {1, 2, 3, 5} 这条链后, 根据 dfs 序, 下一条链为 {1, 2, 3, 6}, 其中, 链 {1, 2, 3} 是重复的. 所以只用从 {6} 开始计算即可.


完整代码

注意, 这里dp_sum[], dp_count[], temp[] 都要用 long long 类型, 否则最后 4 个测试点过不去.

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int VNUM = 5e5 + 5;
const int ENUM = 5e5 + 5;

struct Edge
{
    int end;
    int next;
};

Edge edge[ENUM << 1];
int head[VNUM];
int edgestamp;

int dp_length[VNUM];
long long dp_count[VNUM];
long long dp_sum[VNUM];
int id[VNUM];   //dfs链第 i 位为节点 id[i]
char sequence[VNUM];    //dfs链
char bracket[VNUM]; //原括号串
long long temp[VNUM];   //si 中有多少个互不相同的子串是合法括号串
long long ans;
int lower;  //记录重复位置


void addEdge(int u, int v);
void dfs(int depth, int u, int father); //当前深度, 当前节点, 父节点
void dynamic(int upper);
int main()
{
    int n;

    memset(bracket, '#', sizeof(bracket));

    scanf("%d", &n);
    scanf("%s", bracket + 1);
    for (register int i = 2; i <= n; i++)
    {   
        int u;

        scanf("%d", &u);
        addEdge(u, i);
        addEdge(i, u);
    }
    lower = 0;  //开始时, 没有重复
    dfs(1, 1, 0);
    ans = 0;
    for (register int i = 1;  i <= n; i++)  //异或和
        ans ^= (temp[i] * i);
    printf("%lld\n", ans);

    return 0;
}
void addEdge(int u, int v)
{
    edge[++edgestamp].end = v;
    edge[edgestamp].next = head[u];
    head[u] = edgestamp;
}
void dfs(int depth, int u, int father)
{
    bool isLeaf = true;

    sequence[depth] = bracket[u];
    id[depth] = u;
    for (register int i = head[u]; i != 0; i = edge[i].next)
    {
        int v = edge[i].end;

        if (v == father)
            continue;
        isLeaf = false;
        dfs(depth + 1, v, u);
    }
    if (isLeaf) //是叶节点, 说明找到了一条链
        dynamic(depth);
    lower--;    //回溯, 重复位置上移
}
void dynamic(int upper)
{
    for (register int i = lower + 1; i <= upper; i++)   //重复部分保留
        dp_length[i] = dp_count[i] = dp_sum[i] = 0;
    for (register int i = lower + 1; i <= upper; i++)
    {
        if (sequence[i] == ')') //')'
        {
            if (sequence[i - 1 - dp_length[i - 1]] == '(')
            {
                dp_length[i] = dp_length[i - 1] + 2 + dp_length[i - 2 - dp_length[i - 1]];
                dp_count[i] = dp_count[i] + 1 + dp_count[i - 2 - dp_length[i - 1]];
            }
        }
        dp_sum[i] = dp_sum[i - 1] + dp_count[i];
        temp[id[i]] = dp_sum[i];    //一个点只会被访问一次
    }
    lower = upper;  //修改重复位置
}