【noip模擬】Fantasia
Time Litmit: 1000ms Memory Limit: 256MB
Description
給定一張 $N$ 個點、$M$ 條邊的無向圖 $G$ 。每個點有個權值$W_i$。
我們定義$G_i$ 為圖 $G$ 中刪除第 ii 號頂點後的圖。我們想計算 $G_1,G_2,...,G_n$ 這 $N$ 張圖的權值。
對於任意一張圖 $G$ ,它的權值是這樣定義的:
1. 如果 $G$ 是聯通圖,那麽 $G$ 的權值為 $G$ 中所有頂點權值的乘積。
2. 如果 $G$ 是非聯通圖,那麽 $G$ 的權值為 $G$ 中所有聯通塊的權值之和。
$G$ 中的一個聯通塊指的是 $G$ 的一個子圖,並且這個子圖中的點兩兩相連(包括直接連接或者間接連接),並且不存在子圖外的點使得子圖內的點能與子圖外的點相連。
Input
第一行包含兩個整數 $n$ 和 $m (2≤n≤10^5,1≤m≤2×10^5)$,分別表示點數和邊數。
第二行包含 $n$ 個整數 $w_1,w_2,...,w_n (1≤w_i≤10^9)$ , 表示每個頂點的權值。
接下來 $m$ 行,每行兩個整數 $x_i$ 和 $y_i (1≤x_i,y_i≤n,x_i≠y_i)$, 表示一條無向邊。
Output
輸出只有一個整數: $S=(\sum\limits_{i=1}^{n} i⋅z_i) mod (10^9+7)$, 其中 $z_i$ 是圖 $G_i$ 的權值。
Sample Input
5 5
6 5 4 3 2
1 2
2 3
2 4
3 4
3 5
Sample Output
3216
HINT
【樣例解釋】
z1=120, z2=30, z3=92, z4=240, z5=360
【數據範圍及約定】
子任務1(5分): n≤10,m≤20
子任務2(10分): n≤1000,m≤2000
子任務3(20分): 該圖恰為一棵樹,m=n−1
子任務4(20分): 該圖為一幅聯通圖
子任務5(45分): 我們會拿最強的數據來評測你的程序 (要不要說的這麽直白。。。)
對於所有數據,$2≤n≤10^5,1≤m≤2×10^5$
[吐槽]
場上。。只搞了一個35。。
(在思考子任務3的時候居然想到了奇妙的特判的情況感天動地qwq)
想到tarjan然後懵掉系列。。我天感覺自己宛若一個智障qwq
[題解]
可以看部分分找找靈感
子任務3是可以搞一下事情的
具體做法的話就是用一個$val_i$記錄一下以$i$為根的子樹的權值乘積
然後刪點的時候用總的$val$除以$val_i$然後再加上$i$的各個子樹的$val$就好
所以想,能不能將這個東西強行變成一棵樹然後用樹d來搞呢?
考慮刪掉一個點會發生啥事
刪掉一個點,顯然會有兩種情況
1.不是割點
那麽其實刪掉了對圖的連通性並沒有什麽影響,直接在原來連通塊的權值乘積裏面去掉這個點的權值就好了
2.是割點
一個很簡單粗暴的想法就是把點雙跑出來,然後看這個割點會連到哪些點,然後將這個點所在的點雙的貢獻重新算一下
(也就是將這些點雙的貢獻從原來所屬的連通塊裏面去掉,然後再重新加到$ans$裏面去)
然而這就有問題了(如下圖)
有一種東西叫做割頂啊。。。一個點可能屬於多個點雙,這就很尷尬了
這時候仿佛有兩種不同的方法可以解決這個問題
一種方法
對於每一個點雙多搞一個代表點,然後讓這個點連到點雙裏面的其他點
由於點雙本身的性質,兩個點雙最多共用一個點,所以可以十分愉快地解決上面的問題,轉化成一棵樹
然後就可以愉快跑樹d啦
另一種方法(其實就是因為筆者比較傻所以想了一個繞了幾個彎子的奇妙搞法。。)
我們想要把這個圖轉成一棵樹,所以就直接考慮把這個圖當成一棵樹來跑,以其dfs序來建一棵樹
然後就會發現奇妙的事情
(為了方便描述,後面所講到的後繼指的是該節點在dfs樹中的兒子)
因為我們是按照dfs序建的樹,所以一個點$x$的各個後繼的子樹中的點在原圖(也就是左邊的圖)中不可能有邊相連
也就是說比如$x=4$, 那麽以$5$為根的子樹中的點(5,6,7)和以$8$為根的子樹中的點(8,9)在原圖中肯定沒有邊相連
因為如果有邊相連肯定不會出現在不同的後繼的子樹中
接著看因為刪掉了割點而變成一個新連通塊的點們
顯然應該是割點的每一個後繼的子樹裏面的點會變成一個新的連通塊
而由於之前所講到的性質,保證了這些新連通塊的權值可以用與處理樹上的情況一樣的方式來求出
詳細點說就是
對於一個後繼$x$,刪掉割點之後$x$所在連通塊的權值的乘積就應該是$val_x$
詳細的原因?
首先$x$的子樹中不會有邊連到“樹”中深度比割點小的點
同時也不會有邊連到$x$兄弟的子樹中
這題只需要考慮權值,跟邊沒有任何關系
所以根本不用管這些點在原圖中是怎麽連的,只要保證這個連通塊中所有點的權值都算進去了就行(爽快)
那就直接按照樹的方式統計,將連通塊中的點權全部乘到$x$上就好了
深度$<$ 割點?
顯然深度小於割點的這堆點在刪除割點之後只會變成一個連通塊
其權值其實就是原來整個連通塊的權值除去刪掉的點的權值再除去所有割點後繼的子樹的權值
實現起來就是將原來的整個連通塊的$val$一直除分裂出來的新連通塊的值,然後剩下的東西直接加到答案裏面就好啦
廢話了一堆終於就很愉快滴(個鬼qwq)解決了這個問題啦
[一些細節]
會發現如果說刪掉的點是樹根,答案會多1
因為是樹根就意味著深度比刪掉點小的那個連通塊是不存在的
但是因為我們是用除法處理的所以除出來是1
這裏就要特判一下啦
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #define ll long long 5 #define MOD 1000000007 6 using namespace std; 7 const int MAXN=1e5+10; 8 struct xxx 9 { 10 int y,next; 11 }a[MAXN*2*3]; 12 int h[MAXN],id[MAXN],low[MAXN],dfn[MAXN],h2[MAXN],sz[MAXN]; 13 bool st[MAXN],mark[MAXN]; 14 ll val[MAXN],v[MAXN],valian[MAXN]; 15 int n,m,tot,cnt,t,top; 16 ll ans1,ans,all; 17 int add(int x,int y,int *h); 18 int task1(); 19 ll ksm(ll x,int b); 20 ll ni(ll x); 21 int tarjan(int rt,int x); 22 23 int main() 24 { 25 // freopen("a.in","r",stdin); 26 // freopen("a.out","w",stdout); 27 28 scanf("%d%d",&n,&m); 29 for (int i=1;i<=n;++i) scanf("%lld",v+i); 30 memset(h,-1,sizeof(h)); 31 tot=0; 32 int x,y; 33 for (int i=1;i<=m;++i) 34 { 35 scanf("%d%d",&x,&y); 36 add(x,y,h); add(y,x,h); 37 } 38 task1(); 39 } 40 41 int add(int x,int y,int *h) 42 { 43 a[++tot].y=y; a[tot].next=h[x]; h[x]=tot; 44 } 45 46 int task1() 47 { 48 for (int i=1;i<=n;++i) dfn[i]=0,h2[i]=-1,mark[i]=false,st[i]=false; 49 ans=0; cnt=0; 50 for (int i=1;i<=n;++i) 51 if (dfn[i]==0) 52 { 53 ans1=1; ++cnt; st[i]=true; 54 sz[cnt]=0; 55 tarjan(0,i); 56 ans=(ans+ans1)%MOD; 57 valian[cnt]=ans1; 58 } 59 int num,u; 60 ll tmp,tmp1,ansl=0; 61 for (int i=1;i<=n;++i) 62 { 63 num=id[i]; 64 if (!mark[i]) 65 { 66 tmp=(valian[num]*ni(v[i]))%MOD; 67 tmp=(ans+MOD-valian[num]+tmp)%MOD; 68 if (sz[num]==1) --tmp; 69 } 70 else 71 { 72 tmp1=(valian[num]*ni(v[i]))%MOD; tmp=0; 73 for (int j=h2[i];j!=-1;j=a[j].next) 74 { 75 u=a[j].y; 76 if (low[u]<dfn[i]) continue; 77 tmp1=(tmp1*ni(val[u]))%MOD; 78 tmp=(tmp+val[u])%MOD; 79 } 80 tmp=(ans+MOD-valian[num]+tmp1+tmp)%MOD; 81 tmp=(tmp+MOD-st[i])%MOD; 82 } 83 // printf("%lld\n",tmp); 84 ansl=(ansl+tmp*i%MOD)%MOD; 85 } 86 printf("%lld\n",ansl); 87 } 88 89 ll ni(ll x) 90 { 91 return ksm(x,MOD-2); 92 } 93 94 ll ksm(ll x,int b) 95 { 96 ll ret=1,base=x; 97 while (b) 98 { 99 if (b&1) ret=(ret*base)%MOD; 100 base=(base*base)%MOD; 101 b>>=1; 102 } 103 return ret; 104 } 105 106 int tarjan(int pre,int x) 107 { 108 int u,son=0; 109 dfn[x]=low[x]=++t; 110 id[x]=cnt; ++sz[cnt]; 111 ans1=(ans1*v[x])%MOD; 112 val[x]=v[x]; 113 for (int i=h[x];i!=-1;i=a[i].next) 114 { 115 u=a[i].y; 116 if (u==pre) continue; 117 if (!dfn[u]) 118 { 119 ++son; 120 tarjan(x,u); 121 add(x,u,h2); 122 val[x]=(val[x]*val[u])%MOD; 123 low[x]=min(low[x],low[u]); 124 if ((pre==0&&son>1)||(pre&&low[u]>=dfn[x])) 125 mark[x]=true; 126 } 127 else if (u!=pre) 128 low[x]=min(low[x],dfn[u]); 129 } 130 }挫挫滴代碼
【noip模擬】Fantasia