1. 程式人生 > 實用技巧 >第十一屆藍橋杯C/C++ J題網路分析(帶權並查集水題)

第十一屆藍橋杯C/C++ J題網路分析(帶權並查集水題)

題意:給你n個點,m次操作。操作1是合併a和b;操作2是在p及其連通塊內所有點上增加大小為t的值。為你m次操作之後,各個點的大小是多少?

評測用例及其得分:

對於 30% 的評測用例,1 ≤ n ≤ 20,1 ≤ m ≤ 100。
對於 50% 的評測用例,1 ≤ n ≤ 100,1 ≤ m ≤ 1000。
對於 70% 的評測用例,1 ≤ n ≤ 1000,1 ≤ m ≤ 10000。
對於所有評測用例,1 ≤ n ≤ 10000,1 ≤ m ≤ 100000,1 ≤ t ≤ 100。

個人想說的話:本來是不想寫題解的,因為沒必要,但是網上好像真沒有人寫出滿分正解目前為止,那我就寫寫吧。

解法:看到一道題就需要分析複雜度,做題千萬不要無腦去莽,很多人上來就是一個add邊+bfs暴搜修改權值,這當然可以,但是你只能得70%的分。(最壞複雜度:先1000次操作1建邊,最後建到1000個點的雙聯通圖;再9000次操作2bfs,複雜度是O(9000*1000+1000),大概是O(9e6)的複雜度,能過70%樣例。)

但是這種做法在ACM賽制裡還是TLE。

所以我們需要考慮優化,看到這種合併題,第一反應應該就是dsu啊?那我們考慮怎麼搞並查集,普通並查集顯然8太行(或者維護起來很麻煩),觀察性質可以知道,我們只需要維護當前點p及其當前點p的root的差值就可以了。那就好辦了啊,帶權並查集直接上啊,所以每個點最後的結果就是它與它祖先root的差值+root自身的值就可以了。

我們對於操作1,那就是非連通塊的話合併並且更新其d值,已經是連通塊就continue掉。

我們對於操作2,其實意思就是在他祖先點進行修改就可以了,因為我們要求的最終結果是祖先自己的值+該點與祖先的差值。

不知道你們懂了沒qwq,附上我自己寫的程式碼,歡迎Hack~

/*
    Author: Anonytt
    Date: 08/08/20 20:35
*/

#include<bits/stdc++.h>
#pragma GCC optimize(2)
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define
per(i,n,a) for(int i=n;i>=a;i--) #define endl '\n' #define eps 0.000000001 #define pb push_back #define mem(a,b) memset(a,b,sizeof(a)) #define IO ios::sync_with_stdio(false);cin.tie(0); using namespace std; const int INF=0x3f3f3f3f; const ll inf=0x3f3f3f3f3f3f3f3f; const int mod=1e9+7; const int maxn=1e4+5; int fa[maxn],d[maxn],now[maxn],n,m; int find(int x){ if(x!=fa[x]){ int temp=fa[x]; fa[x]=find(fa[x]); d[x]+=d[temp]; } return fa[x]; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) fa[i]=i; while(m--){ int op;scanf("%d",&op); if(op==1){ int a,b;scanf("%d%d",&a,&b); int eu=find(a),ev=find(b); if(eu!=ev){ fa[ev]=eu; d[ev]=now[ev]-now[eu]; } } else{ int p,t;scanf("%d%d",&p,&t); int rt=find(p); now[rt]+=t; } } rep(i,1,n){ printf("%d ",now[find(i)]+d[i]); } puts(""); } /* 4 8 1 1 2 2 1 10 2 3 5 1 4 1 2 2 2 1 1 2 1 2 4 2 2 1 */
View Code