[XSY] 绿色(圆方树、树形DP、树上差分)
綠色
題意簡述
題解
首先,每次修改完點權后,重新考慮一遍所有路徑顯然是不現實的,所以我們考慮求出經過每個點的兩端同色的簡單路徑數,這樣權值和容易統計和修改。
接下來分析仙人掌上的簡單路徑性質。一條簡單路徑上的邊,可以分為橋邊和環上的邊。過橋只有一種方法,而過同一個環有兩個方向的環邊可選。
現在,我們選取一個關鍵點,強制簡單路徑必須通過該點。如果關鍵點在一個環上,且該簡單路徑在該環上的第一個和最后一個點均不是關鍵點,那么該環只有一個方向的環邊可選。
建立原仙人掌的圓方樹。
如果定義方點貢獻 2 種方案,圓點貢獻 1 種方案,一條路徑貢獻方案數為所有點貢獻方案數之積,那么原仙人掌上兩個同色點間的路徑數轉化為對應圓點間的簡單路徑方案數。
在所有的樹上簡單路徑中,
如果它通過關鍵點,則貢獻值為該路徑的方案數;
如果它不通過關鍵點,但是通過關鍵點的某相鄰方點,則貢獻該路徑方案數的一半。
考慮用何算法求出經過每個點的兩端同色的簡單路徑數(記過uuu的簡單路徑數為coe[u]coe[u]coe[u]):
法一:暴力枚舉
按題意枚舉所有同色點對,可解決n≤100n≤100n≤100的子任務。
法二:樹形DP
首先枚舉每一種顏色,每次只考慮當前顏色的同色點對的貢獻。
對于樹上任意一點uuu,我們可以求出:
dp[u]=dp[u]=dp[u]= uuu的子樹內當前色的圓點到uuu的路徑數
up[u]=up[u]=up[u]= uuu的子樹外當前色的圓點到uuu的路徑數
(換根DP)
那么過uuu的簡單路徑數coe[u]+=(dp[u]+up[u]+1)2?dp[u]2?dp[v]2?122coe[u]+=\frac{(dp[u]+up[u]+1)^2-dp[u]^2-dp[v]^2-1^2}{2}coe[u]+=2(dp[u]+up[u]+1)2?dp[u]2?dp[v]2?12?
這樣可以解決n≤105,k≤5n\leq10^5,k\leq5n≤105,k≤5的子任務
優化:建虛樹
處理樹上詢問點兩兩間路徑的并的問題,一個常見的套路是構建詢問點的虛樹,因為虛樹中同一條邊上的點受詢問點的影響一致,把這些點一起處理可大大提高效率
于是,我們構建同色點的虛樹,通過前綴和,預處理虛樹每條邊上壓縮的點貢獻的方案數,作為邊的方案數。
對于過虛樹節點的簡單路徑數,可以在樹形dp時直接統計出來;
對于過虛樹邊上壓縮掉的點的簡單路徑數,可以利用樹上差分與前綴和快速處理。
那么接下來僅需考慮 過關鍵點相鄰的方點的路徑總方案數 即可:
計算通過了每個相鄰方點的路徑總方案數,再減去通過了它們之間相鄰邊的方案數。
不難發現,上述樹上差分與前綴和的方法,已經預處理了通過各邊的路徑方案數。
此算法時間復雜度O(nlogn)O(n log n)O(nlogn)、空間復雜度O(n)O(n)O(n)
#include<bits/stdc++.h> using namespace std; const int mod=1e9+7; const int inv2=(mod+1)/2; namespace modular{int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}int dec(int a,int b){return a-b<0?a-b+mod:a-b;}int mul(int a,int b){return 1ll*a*b%mod;}void Add(int &a,int b){a=add(a,b);}void Dec(int &a,int b){a=dec(a,b);}void Mul(int &a,int b){a=mul(a,b);} } using namespace modular; inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f; } const int N=1e6+10; int n,m,k,pw[N]; int c[N],w[N]; vector<int> S[N];int cnt,head[N],to[N<<1],nxt[N<<1]; int coe[N],nd; vector<int> e[N]; int in[N],out[N],dfn,fr[N]; bool is[N]; void addedge(int u,int v){to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt; } void build(int u,int pre){//建圓方樹 in[u]=++dfn;for(int i=head[u];i;i=nxt[i]){if(!((i^1)^pre)) continue;//條件一定不能改 int v=to[i];if(!in[v]){fr[v]=u;build(v,i);//記來邊而不是來點 }else if(in[v]<in[u]){++nd;e[nd].push_back(v);e[v].push_back(nd);for(int x=u;x!=v;x=fr[x]){is[x]=1;e[nd].push_back(x);e[x].push_back(nd);}}} }int ct[N],fa[N],sz[N],son[N],dep[N],top[N]; void sub_dfs(int u,int f){in[u]=++dfn;sz[u]=1;fa[u]=f;dep[u]=dep[f]+1;for(int i=0;i<e[u].size();i++){int v=e[u][i];if(v==f) continue;ct[v]+=ct[u]+(u>n);sub_dfs(v,u);sz[u]+=sz[v];if(sz[v]>sz[son[u]]) son[u]=v;}out[u]=dfn; } void tp_dfs(int u,int tp){top[u]=tp;if(!son[u]) return;tp_dfs(son[u],tp);for(int i=0;i<e[u].size();i++){int v=e[u][i];if(v!=fa[u]&&v!=son[u]) tp_dfs(v,v);} } int LCA(int x,int y){while(top[x]!=top[y]){if(dep[top[x]]<dep[top[y]]) swap(x,y);x=fa[top[x]];}return dep[x]<dep[y]?x:y; } bool sub(int x,int y){return in[x]<=in[y]&&in[y]<=out[x]; }int ta[N],dot[N]; //ta[u]記錄過u,且過邊(u,fa[u])的路徑數(用樹上差分求出,所以work時的ta是差分數組,u子樹內的ta和才是實際路徑數) //dot[u]記錄過u,但不過邊(u,fa[u])的路徑數 vector<int> G[N]; int dp[N],up[N]; //dp[u]記錄u子樹內當前色的圓點到u的路徑數 //up[u]記錄u子樹外當前色的圓點到虛樹上u的父親的路徑數 bool o[N]; bool cmp(int x,int y){return in[x]<in[y]; } void dfs1(int u){dp[u]=o[u];for(int i=0;i<G[u].size();i++){int v=G[u][i];dfs1(v);Add(dp[u],mul(pw[ct[v]-ct[u]],dp[v]));} } void dfs2(int u,int f){int c;if(f){c=mul(up[u],dp[u]);Mul(c,pw[ct[u]-ct[f]-(f>n)]);Add(ta[u],c);Dec(ta[f],c);Dec(dot[u],c);}c=mul(pw[ct[u]-ct[f]+(u>n)-(f>n)],up[u]);int S=mul(c,c)+o[u], all=add(dp[u],c);for(int i=0;i<G[u].size();i++){int v=G[u][i];c=mul(pw[ct[v]-ct[u]],dp[v]);Add(S,mul(c,c));}S=dec(mul(all,all),S);Mul(S,inv2);if(u>n) Mul(S,inv2);Add(dot[u],S);for(int i=0;i<G[u].size();i++){int v=G[u][i];Add(up[v],dp[u]);Add(up[v],mul(pw[ct[u]-ct[f]+(u>n)-(f>n)],up[u]));Dec(up[v],mul(pw[ct[v]-ct[u]],dp[v]));dfs2(v,u);} } void work(vector<int> &p){//對每種顏色的點建虛樹,求解 sort(p.begin(),p.end(),cmp);static int ex[N],tim;tim++;for(int i=0;i<p.size();i++){int x=p[i];ex[x]=tim;o[x]=1;}int n=p.size(),x,y,z;for(int i=0;i+1<n;i++){int x=LCA(p[i],p[i+1]);if(ex[x]!=tim) ex[x]=tim,p.push_back(x);}sort(p.begin(),p.end(),cmp);static int s[N];int top=0;for(int i=0;i<p.size();i++){x=p[i];while(top>1&&!sub(s[top],x)){y=s[top],z=s[top-1];G[z].push_back(y);top--;} s[++top] = x; }while(top>1){y=s[top],z=s[top-1];G[z].push_back(y);top--;}dfs1(p[0]);dfs2(p[0],0);for(int i=0;i<p.size();i++){int x=p[i];up[x]=o[x]=0;G[x].clear();}p.clear(); } void dfs(int u,int f){for(int i=0;i<e[u].size();i++){int v=e[u][i];if(v==f) continue;dfs(v,u);Add(ta[u],ta[v]);}coe[u]=mul(2,add(ta[u],dot[u]));for(int i=0;i<e[u].size();i++){int v=e[u][i];if(v!=f&&v>n) Add(coe[u],dot[v]);} } int main(){n=read();m=read();k=read();pw[0]=1;for(int i=1;i<=n;i++) pw[i]=add(pw[i-1],pw[i-1]);nd=n;for(int i=1;i<=n;i++){c[i]=read();S[c[i]].push_back(i);}for(int i=1;i<=n;i++) w[i]=read();cnt=1;for(int i=1;i<=m;i++){int u=read(),v=read();addedge(u,v);addedge(v,u);}build(1,0);for(int i=2;i<=n;i++){if(!is[i]){e[i].push_back(fr[i]);e[fr[i]].push_back(i);} }dfn=0; sub_dfs(1,0);tp_dfs(1,1);for(int i=1;i<=k;i++) work(S[i]);dfs(1,0);for(int i=2;i<=n;i++){int x=fa[i];if(x>n){int c=add(ta[x],dot[x]);Dec(c,ta[i]);Add(coe[i],c);}}for(int i=1;i<=n;i++) Mul(coe[i],inv2);//trick:前面先計算系數的2倍,最后再/2 int ans=0;for(int i=1;i<=n;i++)Add(ans,mul(w[i],coe[i]));printf("%d\n",ans);int Q=read();for(int i=1;i<=Q;i++){int x=read(),v=read();Add(ans,mul(v,coe[x]));printf("%d\n",ans);}return 0; } /* 4 5 2 1 2 1 2 1 2 3 4 1 2 1 2 2 3 2 4 3 4 1 3 1 */總結
以上是生活随笔為你收集整理的[XSY] 绿色(圆方树、树形DP、树上差分)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何免费备份照片和视频免费的备份照片视频
- 下一篇: 抖音加码本地生活 将投入5亿元扶持优质探