写在前面
考试的时候,我智障的想了这道题大约两个半小时,导致后面的暴力分并没有拿满,值得反思。
Solution
本题其实和
J
S
O
I
JSOI
JSOI的一道题很像,题目叫做星球大战,所以说本题可以参考那道题的做法,逆向维护一个并查集,先计算出所有战争结束以后的状态,在逐步加入被摧毁的殖民地算回去。那么怎么算呢?
考虑我们已经算出一个连通块的文明价值为
s
u
m
1
sum_1
sum1,另一个连通块的文明价值为
s
u
m
2
sum_2
sum2,现在要加入的点价值为
w
w
w,那么由文明价值的定义以及乘法分配律可以知道,新的文明价值
s
u
m
′
=
s
u
m
′
+
s
u
m
1
∗
s
u
m
2
sum'=sum'+sum_1*sum_2
sum′=sum′+sum1∗sum2。那么我们就可以递推计算了。
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1000005;
typedef long long ll;
const int mod=1e9+7;
template <typename T>
void read(T &x){
x=0; int neg=1;
char c=getchar();
while(!isdigit(c)&&c!='-') c=getchar();
if(c=='-') neg=-1,c=getchar();
while(isdigit(c)) x=(x<<1)+(x<<3)+c-'0',c=getchar();
x*=neg;
}
struct Edge{
int u,v;
}e[maxn<<1];
int first[maxn],nxt[maxn<<1],cnt=0;
void AddEdge(int u,int v){
e[++cnt].u=u;e[cnt].v=v;
nxt[cnt]=first[u]; first[u]=cnt;
}
int fa[maxn],n,m;
ll w[maxn];
int getfather(int u){
return u==fa[u]?u:getfather(fa[u]);
}
vector<int> destroy[maxn];
ll isattack[maxn],ans[maxn];
int main(){
read(n); read(m);
for(int i=1;i<=n;i++) fa[i]=i,w[i]=i;
for(int i=1;i<n;i++){
int f; read(f);
AddEdge(i+1,f);
AddEdge(f,i+1);
}
for(int i=1;i<=m;i++){
int k; read(k);
for(int j=1;j<=k;j++){
int x; read(x);
destroy[i].push_back(x);
isattack[x]=1;
}
}
ll res=0;
for(int i=1;i<=cnt;i++){
int u=e[i].u,v=e[i].v;
if(isattack[u]||isattack[v]) continue;
int fu=getfather(u);
int fv=getfather(v);
if(fu!=fv){
fa[fu]=fv;
res=(res+w[fu]%mod*w[fv]%mod)%mod;
w[fv]=(w[fu]+w[fv])%mod;
}
}
ans[m]=res;
for(int i=m;i>=1;i--){
for(int j=0;j<destroy[i].size();j++){
int u=destroy[i][j];
isattack[u]=0;
for(int k=first[u];k;k=nxt[k]){
int v=e[k].v;
if(isattack[v]) continue;
int fu=getfather(u),fv=getfather(v);
if(fu!=fv){
fa[fu]=fv;
res=(res+w[fv]%mod*w[fu]%mod)%mod;
w[fv]=(w[fv]+w[fu])%mod;
}
}
}
ans[i-1]=res;
}
for(int i=0;i<=m;i++) printf("%lld\n",ans[i]);
return 0;
}