牛客暑假多校训练7 F xay loves trees

这篇博客介绍了如何利用树状数组和滑动窗口算法解决给定两棵树中找寻特定子链的问题。首先预处理第二棵树的DFS序和后代DFS序范围,接着在第一棵树中进行DFS,使用滑动窗口处理每条链,通过区间和判断新加入的点是否符合条件。在回溯过程中恢复状态,最终找到最长满足条件的子链。理论时间复杂度为O(nlogn)。
摘要由CSDN通过智能技术生成

题意:https://ac.nowcoder.com/acm/contest/11258/F 给出两树S1, S2, 求一个点集,满足这个点集上的点都在S1上的一条链上(且联通),并且在S2中,这个点集任意两点没有祖先关系。

首先, 我们可以先预处理S2, 求出S2中每个点的dfs序,并记录这个点及其的后代的dfs序的范围,这样一来,每个点的dfs 序就都是一个连续的区间,且该点的后代的dfs序区间包含在它的祖先的区间里,这样操作的好处在于如果两个节点没有祖先关系,那他们的dfs序的区间就不会互相包含。

接下来,我们可以 dfs S1,对每条链进行处理,注意到在S1中, 这个集合的点是相连的, 那么也就是说这些点在一条链上的位置是相邻的,然后要找出的是这个区间长度的最大值,由于我说不清楚,我们可以想到用滑动窗口来处理。只要能够判断出新进的点能够满足与窗口里的点没有互相包含的区间,那么这个区间的右端就可以延申下去,否则,我们就得将窗口的左端慢慢移到右端,并进行判断,直到满足条件,即为一个合理的区间。

那么问题来了,怎么判断呢? 我们可以想到, 当一个区间出现过时,我们可以将这个区间里所有数都加1,并维护起来。当dfs到另一个点时, 我们也对这个新进的点的区间里所有数加1,然后查询这个区间和,如果这个区间和刚好等于区间长度,那么这个区间就是没被包含过也没包含过其他区间,否则,窗口的左端就得左移,并对移出来的点的区间减1,直到刚刚处理的新进来的点的区间和等于其区间长度,然后维护区间最大值。
这里要注意, 在每走一步的回溯时,我们要把窗口和维护区间的数据结构还原到这个新进的点没处理前的样子,这一步结合代码应该很好理解。
对于区间修改, 区间查询我们可以用线段树,树状数组等数据结构实现,下面的代码给出的是一个树状数组的方法。

理论复杂度 nlogn

跑的飞快

#include <bits/stdc++.h>
#pragma GCC optimize(2)
#define pb push_back
#define endl '\n'

using namespace std;
using ll = long long;

const int maxn = 3e5 + 10;

vector <int> tree1[maxn], tree2[maxn];  
int L[maxn], R[maxn];//第二颗树的dfs序的范围
//树状数组维护区间和
int tree[maxn];
int sum[maxn];
//
int kk; 
int ans = 0;
void dfs(int num, int last) //求dfs序
{
    L[num] = ++kk;
    for (int i : tree2[num])
    {
        if (i == last) continue;
        dfs(i, num);
    }
    R[num] = kk;
}

int lef = 0, rig = -1, n;
int p[maxn];

//树状数组处理过程
void update(int id, int val)
{
    int y = id;
    while (id < n + 2)
    {
        tree[id] += val;
        sum[id] += val * y;
        id += (id & (-id));
    }
}
int query(int num)
{
    int a = 0, y = num;
    while (num > 0)
    {
        a += (y + 1) * tree[num] - sum[num];
        num -= (num & (-num));
    }
    return a;
}

//对第一棵树的每一条链进行滑动窗口处理
void dfs1(int num, int last)
{
    int l = lef, r = rig;
    p[++rig] = num;
    update(L[num], 1);
    update(R[num] + 1, -1);
    while (query(R[num]) - query(L[num] - 1) > R[num] - L[num] + 1)
    {
        int t = p[lef];
        update(L[t], -1);
        update(R[t] + 1, 1);
        lef++;
    }
    ans = max(ans, rig - lef + 1);
    for (int i : tree1[num])
    {
        if (i == last) continue;
        dfs1(i, num);
    }
	//还原到该点没有处理前的样子
    update(L[num], -1);
    update(R[num] + 1, 1);
    rig--;
    while (lef > l)
    {
        lef--;
        update(L[p[lef]], 1);
        update(R[p[lef]] + 1, -1);
    }
}

void solve()
{
    int i, j, u, v;
    cin >> n;
    lef = 0, rig = -1;
    kk = 0; ans = 0;
    for (i = 1; i <= n; i++) tree1[i].clear(), tree2[i].clear();
    memset(tree, 0, sizeof(int) * (n + 10));
    memset(sum, 0, sizeof(int) * (n + 10));
    memset(L, 0, sizeof(int) * (n + 10));
    memset(R, 0, sizeof(int) * (n + 10));
    for (i = 1; i < n; i++)
    {
        cin >> u >> v;
        tree1[u].pb(v);
        tree1[v].pb(u);
    }
    for (i = 1; i < n; i++)
    {
        cin >> u >> v;
        tree2[u].pb(v);
        tree2[v].pb(u);
    }
    dfs(1, -1);
    dfs1(1, -1);
    cout << ans << endl;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);

    int T;
    cin >> T;
    while (T--)
        solve();
    return 0;
}


/*

1
5
2 1
2 5
5 3
2 4
2 1
1 5
1 3
3 4

*/

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值