zoukankan      html  css  js  c++  java
  • 洛谷P6623——[省选联考 2020 A 卷] 树

    传送门:QAQQAQ

    题意:自己看

    思路:正解应该是线段树/trie树合并? 但是本蒟蒻啥也不会,就用了树上二次差分

    (思路来源于https://www.luogu.com.cn/blog/dengyaotriangle/solution-p6623)


    首先我们企图树形DP,但是发现每一个元素往上推一格都会+1,所以我们对于二进制每一位考虑贡献。

    顶点u对他祖先的二进制第k位贡献,可能是0可能是1,但不断+1时变化是一个混循环,刨掉最开始的,后面都是规则的循环,2^k个0,2^k个1。所以我们可以对有影响的1进行第一次差分

    但是因为有多个区间,尤其是$k=0$时复杂度到达O(n2),所以我们再用一次差分:考虑到差分每次赋值的上下两个点分别对于2^k同余,所以我们对于余数再建一个数组,改的区间一定是连续的,就又可以优化成O(1)了

    总复杂度$O(nlog(n))$

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N=530000;
    int a[N],f[N],n,val[N],dp[21][N],dep[N];//(dep+a[])%2^k==i
    int base[21];
    ll ans=0;
    vector<int> v[N];
    
    int dfs(int u)
    {
        int ret=a[u];
        for(int i=0;i<=20;i++) dp[i][(dep[u]+a[u])%base[i]]^=base[i];
        for(int i=0;i<=20;i++) ret^=dp[i][dep[u]%base[i]]; 
        for(int i=0;i<v[u].size();i++)
        {
            int to=v[u][i];
            dep[to]=dep[u]+1;
            ret^=dfs(to);
        }
        for(int i=0;i<=20;i++) ret^=dp[i][dep[u]%base[i]]; 
        //上下两个相同的操作是为了异或掉不是u子树的贡献 
        ans+=1LL*ret;
        return ret;
    }
    
    int main()
    {
        base[0]=1;
        for(int i=1;i<=20;i++) base[i]=base[i-1]*2;
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        for(int i=2;i<=n;i++)
        {
            scanf("%d",&f[i]);
            v[f[i]].push_back(i);
        }
        dfs(1);
        cout<<ans<<endl;
        return 0;
    }

    这份代码让刚才那个思路更好实现:假如u下面的子树已经算好,那么所有元素+1后再去考虑二进制每一位的贡献,当且仅当该位0->1或1->0时才要异或,即在循环节中间或末尾,即$a[v]+dep[v]$与$dep[u]$关于2^k同余(2^(k+1)为循环节)

    (留坑更新线段树合并做法)

  • 相关阅读:
    MFC Slider控件 去掉边上的虚线
    VC學習網址
    全局程序集缓存工具 (Gacutil.exe)
    滚动条集合
    调用 DialogBox 会失败解决方法
    全局程序集缓存GAC”是什么概念
    UltraVNC:超实用的远程控制工具(图)
    VC程序员之无法选择的命运
    C++类
    角色权限批量设置,随点!
  • 原文地址:https://www.cnblogs.com/Forever-666/p/13216694.html
Copyright © 2011-2022 走看看