題目
Source
http://acm.hdu.edu.cn/showproblem.php?pid=5739
Description
Professor Zhang has an undirected graph G with n vertices and m edges. Each vertex is attached with a weight wi. Let Gi be the graph after deleting the i-th vertex from graph G. Professor Zhang wants to find the weight of G1,G2,...,Gn.
The weight of a graph G is defined as follows:
1. If G is connected, then the weight of G is the product of the weight of each vertex in G.
2. Otherwise, the weight of G is the sum of the weight of all the connected components of G.
A connected component of an undirected graph G is a subgraph in which any two vertices are connected to each other by paths, and which is connected to no additional vertices in G.
Input
There are multiple test cases. The first line of input contains an integer T, indicating the number of test cases. For each test case:
The first line contains two integers n and m (2≤n≤105,1≤m≤2×105) -- the number of vertices and the number of edges.
The second line contains n integers w1,w2,...,wn (1≤wi≤109), denoting the weight of each vertex.
In the next m lines, each contains two integers xi and yi (1≤xi,yi≤n,xi≠yi), denoting an undirected edge.
There are at most 1000 test cases and ∑n,∑m≤1.5×106.
Output
For each test case, output an integer $S = (\sum\limits_{i=1}^{n}i\cdot z_i) \text{ mod } (10^9 + 7)$, where zi is the weight of Gi.
Sample Input
1
3 2
1 2 3
1 2
2 3
Sample Output
20
分析
題目大概說給一張無向點帶有權無向圖。定義連通圖的權值為圖中各點權的乘積,圖的權值為其包含的各連通圖的權和。設$z_i$為刪除i點后圖的權值,求$S = (\sum\limits_{i=1}^{n}i\cdot z_i) \text{ mod } (10^9 + 7)$。
官方題解這么說的:
顯然, 只要刪掉關鍵點才會使圖不聯通. 對於其他點, 權值很容易計算.
首先求出所有的點雙聯通分量, 對於每一個點雙聯通分量$S$, 新建一個節點$s$, 向$S$中每個節點$v$連邊. 這樣一來, 新增的點和原來圖中的點會構成一個森林(據說這個有個名字, block forest data structure). 很容易觀察到, 葉子節點肯定都是非關鍵點, 內部節點要么是關鍵點, 要么是新增的節點.
對於這個森林$F$, 刪掉一個關鍵點或者一個葉子$i$之后, 會得到一個新森林$F_i$, 這個$F_i$對應的連通塊集合和$G_i$對應的連通塊集合其實是一樣的(不考慮那些新增的點). 顯然$G_i$的權值和$F_i$的權值也是一樣的, $F_i$的權值我們很容易通過樹形dp算出來, 那么$G_i$的權值也隨之而出.
可以在網上搜到關於用那個BF在線性時間計算所有關節點的影響的論文。。里面有這么一張圖:
這樣就好理解了。
設新加圓形結點的權為1,在那棵構造出來的樹中用dp求出各個結點的兩個信息:
- pro[u]表示u為根的子樹內各個結點權值的乘積
- sum[u]表示Σpro[v](u為原本圖中的結點,v為u的孩子結點)
最后通過枚舉要刪除的各個點,再加上乘法逆元搞搞,就能直接通過這兩個信息很快地求出刪除某結點后新的總權值。
另外。。有個地方空間開太小WA了好久。。
代碼
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define MAXN 111111 #define MAXM 222222 struct Edge{ int v,flag,next; }edge[MAXM<<1]; int NE,head[MAXN]; void addEdge(int u,int v){ edge[NE].v=v; edge[NE].flag=0; edge[NE].next=head[u]; head[u]=NE++; } struct TEdge{ int v,next; }tEdge[MAXM<<4]; int tNE,tHead[MAXN<<1]; void addEdge(int u,int v,int nothing){ tEdge[tNE].v=v; tEdge[tNE].next=tHead[u]; tHead[u]=tNE++; } int dn,dfn[MAXN],low[MAXN]; int stack[MAXM],top; int root[MAXN],rn; void tarjan(int u,int rt){ dfn[u]=low[u]=++dn; for(int i=head[u]; i!=-1; i=edge[i].next){ if(edge[i].flag) continue; edge[i].flag=edge[i^1].flag=1; stack[++top]=i; int v=edge[i].v; if(dfn[v]){ low[u]=min(low[u],dfn[v]); continue; } tarjan(v,rt); low[u]=min(low[u],low[v]); if(low[v]>=dfn[u]){ ++rn; int k; do{ k=stack[top--]; root[edge[k].v]=rt; root[edge[k^1].v]=rt; addEdge(rn,edge[k].v,0); addEdge(edge[k].v,rn,0); addEdge(rn,edge[k^1].v,0); addEdge(edge[k^1].v,rn,0); }while(edge[k^1].v!=u); } } } int n,weight[MAXN]; bool vis[MAXN<<1]; long long sum[MAXN<<1],pro[MAXN<<1]; void dfs(int u){ vis[u]=1; sum[u]=0; pro[u]=(u<=n) ? weight[u] : 1; for(int i=tHead[u]; i!=-1; i=tEdge[i].next){ int v=tEdge[i].v; if(vis[v]) continue; dfs(v); if(u<=n){ sum[u]+=pro[v]; sum[u]%=1000000007; } pro[u]*=pro[v]; pro[u]%=1000000007; } } long long ine(long long x){ long long res=1; int n=1000000007-2; while(n){ if(n&1){ res*=x; res%=1000000007; } x*=x; x%=1000000007; n>>=1; } return res; } int main(){ int t,m; scanf("%d",&t); while(t--){ scanf("%d%d",&n,&m); for(int i=1; i<=n; ++i){ scanf("%d",weight+i); } NE=0; memset(head,-1,sizeof(head)); int a,b; while(m--){ scanf("%d%d",&a,&b); addEdge(a,b); addEdge(b,a); } dn=0; memset(dfn,0,sizeof(dfn)); rn=n; memset(root,0,sizeof(root)); top=0; tNE=0; memset(tHead,-1,sizeof(tHead)); for(int i=1; i<=n; ++i){ if(dfn[i]==0) tarjan(i,rn+1); } long long tot=0; memset(vis,0,sizeof(vis)); for(int i=1; i<=n; ++i){ if(vis[i]) continue; if(root[i]){ dfs(root[i]); tot+=pro[root[i]]; tot%=1000000007; }else{ tot+=weight[i]; tot%=1000000007; } } long long ans=0; for(int i=1; i<=n; ++i){ if(root[i]){ ans+=(tot-pro[root[i]]+pro[root[i]]*ine(pro[i])%1000000007+sum[i])%1000000007*i; ans%=1000000007; }else{ ans+=(tot-weight[i])*i; ans%=1000000007; } } if(ans<0) ans+=1000000007; printf("%lld\n",ans); } return 0; }