【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 }