2021国庆CSP/NOIP冲刺营 Contest02 D. 树
【题意】
有一棵n个节点的数,定义f(i,j)为只保留编号为i-j的点时连通块个数
求$\sum_{i=1}^{n}\sum_{j=i}^{n}f^k(i,j)$
对$1e9+7$取模
【分析】
首先我们需要发现,当图是一个树/森林时,连通块个数=点数-边数
所以我们需要计算的就是当前l-r编号内的边的数量
我们考虑按照r从1-n的顺序进行操作
考虑将线段树内的每个点记录当前点作为l的答案
每次给1-r区间+1表示多一个点加入
然后把 u 到 i 的边,其中 u<i 的边的贡献处理了,也就是 1-u 区间-1即可
然后题目求的可能是平方和,也算是线段树的常规操作了
【代码】
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=1e5+5; const int mod=1e9+9; vector <int> G[maxn]; ll ans,res; int n,k; struct seg { ll s[2],tag; }tr[maxn<<2]; void add(int now,ll val,int l,int r) { tr[now].s[1]+=tr[now].s[0]*2*val+1ll*(r-l+1)*val*val; tr[now].s[0]+=1ll*(r-l+1)*val; tr[now].tag+=val; } void pushdown(int now,int l,int r) { if(!tr[now].tag) return; int mid=(l+r)>>1; add(now<<1,tr[now].tag,l,mid); add(now<<1|1,tr[now].tag,mid+1,r); tr[now].tag=0; } void pushup(int now) { for(int i=0;i<2;i++) tr[now].s[i]=(tr[now<<1].s[i]+tr[now<<1|1].s[i]); } void update(int now,int l,int r,int L,int R,ll val) { if(l>=L && r<=R) { add(now,val,l,r); return; } pushdown(now,l,r); int mid=(l+r)>>1; if(L<=mid) update(now<<1,l,mid,L,R,val); if(mid<R) update(now<<1|1,mid+1,r,L,R,val); pushup(now); } void query(int now,int l,int r,int L,int R) { if(L<=l && R>=r) { ans+=tr[now].s[k]; return; } pushdown(now,l,r); int mid=(l+r)>>1; if(L<=mid) query(now<<1,l,mid,L,R); if(mid<R) query(now<<1|1,mid+1,r,L,R); pushup(now); } int main() { scanf("%d%d",&n,&k); k--; int x,y; for(int i=1;i<n;i++) { scanf("%d%d",&x,&y); if(x<y) swap(x,y); G[x].push_back(y); } for(int i=1;i<=n;i++) { update(1,1,n,1,i,1); for(int t=0;t<G[i].size();t++) { int tmp=G[i][t]; update(1,1,n,1,tmp,-1); } ans=0; query(1,1,n,1,i); res=(res+ans)%mod; } printf("%lld",res); return 0; }