1. 程式人生 > >hdu-4358:Boring counting(優美演算法之樹上啟發式合併)

hdu-4358:Boring counting(優美演算法之樹上啟發式合併)

通過這道題了解到了神奇的樹上dsu演算法,起因是最近想再學習一波線段樹,然後就找了一套線段樹總結的題目。在那個總結裡面看到了這道題,感覺很有意思。然後晚上回了宿舍就和室友在討論,然後室友告訴我樹上啟發式合併隨便做???

當時一臉懵逼,樹上啟發式合併是什麼鬼,其實個人對啟發式合併還是有一點了解的。之前做了一道可持久化並查集,那裡面並查集的合併沒有用正常的路徑壓縮,是用判斷兩個並查集大小的方式進行合併,最後保證複雜度維持在nlogn。

但是樹上啟發式合併之前完全沒有聽說過。室友信誓旦旦的說這做法可以解決大部分的子樹查詢問題。表示懷疑,因為我最開始想到的解法其實是dfs序之後直接莫隊。線段樹我們都不懂怎麼寫= = 然後一起研究了一下線段樹的寫法,感覺確實太麻煩了,室友說他第二天要用dsu解決這道題。我第二天用了莫隊A了,他用dsuA了。。。於是我就把他的資料拿過來研究了一下,發現樹上dsu是真的神奇,搞懂以後就感覺是個純暴力但是稍加修改後它的複雜度是nlogn!!!

但是這個演算法的侷限性也比較大,首先好像不能插入修改操作,其次的話只能應對子樹的查詢問題。而且怎麼說呢,如果要查詢的東西比較複雜的話感覺也比較難搞。

不過就算如此,也是一個非常優秀的演算法了,在應對子樹查詢類問題的時候可以優先考慮一下。而且,技多不壓身啊。。。

貼一下樹上dsu的學習資料:

原文連結:CF原文

題目大意:

給你一顆樹,每個結點有自己的權值,求權值恰好出現次數為k的個數。

解題思路:

dfs序後直接莫隊或者線段樹都可,這裡用樹上dsu來寫 = = 

Ac程式碼:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
int n,k,m,res,a[maxn],sum[maxn],sz[maxn],son[maxn],q[maxn],ans[maxn];
bool vis[maxn];
vector<int> v[maxn],rv;
int getid(int x) { return lower_bound(rv.begin(),rv.end(),x)-rv.begin()+1; }
void init()
{
    rv.clear();
    for(int i=0;i<=n;i++) v[i].clear();
    for(int i=0;i<=n;i++) sum[i]=son[i]=vis[i]=0;
}
void dfs1(int x,int fa) //求出每個結點的重兒子
{
    sz[x]=1;
    for(int i=0;i<v[x].size();i++)
    {
        int u=v[x][i];
        if(u==fa) continue;
        dfs1(u,x); sz[x]+=sz[u];
        if(sz[u]>sz[son[x]]) son[x]=u;
    }
}
void edit(int x,int fa,int sp)  //更新每個點的貢獻
{
    if(sum[a[x]]==k) res--;
    sum[a[x]]+=sp;
    if(sum[a[x]]==k) res++;
    for(int i=0;i<v[x].size();i++)
    {
        int u=v[x][i];
        if(u==fa||vis[u]) continue;
        edit(u,x,sp);
    }
}
void dfs(int x,int fa,int sp)   //sp表示貢獻是否被清空
{
    for(int i=0;i<v[x].size();i++)  //遍歷所有非重兒子的結點
    {
        int u=v[x][i];
        if(u==fa||u==son[x]) continue;
        dfs(u,x,0); //sp傳0
    }
    if(son[x]) dfs(son[x],x,1),vis[son[x]]=1;   //sp傳1
    edit(x,fa,1);   //遍歷x的所有結點 新增重兒子以外的貢獻
    ans[x]=res; //記錄答案
    if(son[x]) vis[son[x]]=0;   //重兒子的標記取消
    if(!sp) edit(x,fa,-1);  //貢獻取消
}
int main()
{
    int QAQ,kase=0,flag=0;
    scanf("%d",&QAQ);
    while(QAQ--)
    {
        if(flag) printf("\n");
        scanf("%d%d",&n,&k);
        init();
        for(int i=1;i<=n;i++)   //這裡把權值離散一下
            scanf("%d",&a[i]),rv.push_back(a[i]);
        sort(rv.begin(),rv.end()),rv.erase(unique(rv.begin(),rv.end()),rv.end());
        for(int i=1;i<=n;i++) a[i]=getid(a[i]);
        for(int i=1;i<n;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            v[x].push_back(y);
            v[y].push_back(x);
        }
        scanf("%d",&m);
        for(int i=1;i<=m;i++) scanf("%d",&q[i]);    //儲存詢問
        dfs1(1,0);dfs(1,0,0);   //遍歷跑出結果
        printf("Case #%d:\n",++kase);
        for(int i=1;i<=m;i++)
            printf("%d\n",ans[q[i]]);
        flag=1;
    }
}

相關推薦

hdu-4358:Boring counting(優美演算法樹上啟發式合併)

通過這道題了解到了神奇的樹上dsu演算法,起因是最近想再學習一波線段樹,然後就找了一套線段樹總結的題目。在那個總結裡面看到了這道題,感覺很有意思。然後晚上回了宿舍就和室友在討論,然後室友告訴我樹上啟發式合併隨便做??? 當時一臉懵逼,樹上啟發式合併是什麼鬼,其實個人對啟

HDU 4358 Boring counting 莫隊演算法

題目大意: 就是現在給出一個有N個結點的樹(N <= 100000)編號從1到N, 樹的根節點為1, 給定K, 每個點都有一個權值, 權值0 <= wi <= 10^9, 現在有Q次詢問, 對於每次詢問給出一個x代表詢問在編號x的結點及其子樹中, 出現恰好

HDU - 4358 Boring counting (樹上啟發式合並/線段樹合並)

ems print onclick space pri 分享圖片 click dde 題目 題目鏈接 題意:統計樹上每個結點中恰好出現了k次的顏色數。 dsu on tree/線段樹合並裸題。 啟發式合並1:(748ms) 1 #include<bi

HDU 4358 Boring counting(線段樹)

用C++交會棧溢位,而G++不會。 更新和查詢我用的是線段樹,1500+ms,用樹狀陣列應該會快一些。 將樹形結構轉換成線性結構後,等價於求指定區間內恰好出現k次的數有多少個。 #include <iostream> #include <cstring&

[HDU]3518——Boring counting

break ble tin col figure take in fine each face zhan.jiang.ou now faced a tough problem,his english teacher quan.hong.chun gives him a st

HDU 3518 Boring counting(字尾陣列入門題)

Boring counting Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 2617    Accep

【基本操作】樹上啟發式合併の詳解

樹上啟發式合併是某些神仙題目的常見操作。 這裡有一個講得詳細一點的,不過為了深刻記憶,我還是再給自己講一遍吧!   DSU(Disjoint Set Union),別看英文名挺高階,其實它就是並查集…… DSU on tree,也就是樹上的啟發式合併(眾所周知,並查集最重要的優化就是啟發式合

樹上啟發式合併總結

前言 某一天發現一道樹上啟發式合併裸題,但我不會寫…… 學習並刷了兩天的題,是時候來寫個總結了 正文 樹上啟發式合併(DSU on Tree),是一個在 O

dsu on tree 樹上啟發式合併 學習筆記

近幾天跟著dreagonm大佬學習了\(dsu\ on\ tree\),來總結一下: \(dsu\ on\ tree\),也就是樹上啟發式合併,是用來處理一類離線的樹上詢問問題(比如子樹內的顏色種數)的不二法寶。它不僅好想好寫,還有著\(O(nlogn)\)的優秀時間複雜度(劃重點)。 結合一道例題來講吧:

Codeforces 600E Lomsat gelral 樹上啟發式合併

題意 題解 我要吐槽一下. 為什麼這題又看不了AC程式碼,又看不了資料,洛谷上面交還UKE? 題意 給一棵1為根的樹,每個點有個顏色,求每一個點的子樹裡出現最多的顏色的和. 題解 我們用兩組nn個mapcnt和summapcnt和s

[CF600E]Lomsat gelral[dsu on tree/樹上啟發式合併]

題意:求每個節點子樹眾數和(比如3和5都是眾數 答案是8) 樹上啟發式合併可以解決一些無修改的子樹詢問 先solve輕兒子,後solve重兒子,如果該節點是輕兒子,然後重新統計輕兒子的貢獻,更新該節點的答案,如果該節點是輕兒子,那麼將該節點的貢獻刪除,回溯(其實就是保留了重兒子的答案) 由於輕重鏈剖分一

Lomsat gelral(樹上啟發式合併

例題 輕重鏈剖分暴力。主要是skip陣列有點蒙。 對於一個點A,一定是先把A的輕兒子先全部搜完然後搜A的重兒子。 輕兒子搜完之後記錄都清空了。最後搜重兒子的時候搜完了要保留。if(son[u]) dfs(son[u],v,1),skip[son[u]]=1;通過skip保留。 然後是

CF741D-Arpa's letter-marked tree and Mehrdad's Dokhtar-kosh paths【樹上啟發式合併

正題 評測記錄:https://www.luogu.org/recordnew/lists?uid=52918&pid=CF741D 題目大意 一棵根為 1

學習總結:Dsu on tree 樹上啟發式合併

(RT,這只是一篇小小的總結,以便將來的回顧,並不詳細講) 以前也學習過啟發式合併,大概就是像樹形dp一樣在dfs上,將兒子的資訊向父親轉移,容器是map,將兒子的資訊邊轉移邊更新答案,轉移之後便將兒子的容器清空,防止空間超限。不過對於本人而言,雖然思路較為簡

F. Dominant Indices Educational Codeforces Round 47 (Rated for Div. 2)(樹上啟發式合併

F. Dominant Indices time limit per test 4.5 seconds memory limit per test 512 megabytes input standard input output standar

Codeforces 600E. Lomsat gelral(樹上啟發式合併)

題意: n個點的有根樹,以1為根,每個點有一種顏色。我們稱一種顏色佔領了一個子樹當且僅當沒有其他顏色在這個子樹中出現得比它多。求佔領每個子樹的所有顏色之和。 題解: #include<bi

hdu6430樹上啟發式合併

題解:因為這題我們只查詢不做修改,那麼在樹上做啟發式合併就會很方便並且時間複雜度做到查詢萬每課子樹最後的時間複雜度是nlogn就可以查詢每個結點為根的子樹資訊,每次標記一下0表示資訊不保留,1表示資訊保留,找最大公約數,我們用一個標記陣列把每個結點的因子都加一,因為因子個數最

【CF 600 E】Lomsat gelral——樹上啟發式合併dsu on tree入門

一.前言: 這次講解的dsu on tree,其實是一種優美的暴力,它的時間複雜度分析與樹鏈剖分類似,也需要用到重兒子這一概念(不會樹剖的點這裡). 這種演算法適用於一類樹上查詢子樹的問題,不過它只能處理有子樹查詢的問題,不支援修改,也不支援鏈查詢. 二.例題:

[BZOJ2599][IOI2011]Race-樹上啟發式合併(dsu on tree)

Race Description 給一棵樹,每條邊有權.求一條簡單路徑,權值和等於K,且邊的數量最小.N <= 200000, K <= 1000000 Input 第一行 兩個整數 n, k 第二..n行 每行三個整數 表示一條無

樹上啟發式合併 DSU On Tree

前言 這個演算法是退役之後才知道的…… 大概作用是支援靜態的樹上的一些對子樹資訊的離線詢問。 和樹上莫隊很像,也是按照一個順序來處理詢問,要求在外部能較快速地維護當前選入點的資訊。 和樹鏈剖分很像,也是先找出重兒子和輕兒子,兩種按一定次序處理,也因此保證