日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

2018CCF-CSP 5.二次求和(点分治)

發(fā)布時間:2023/12/3 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 2018CCF-CSP 5.二次求和(点分治) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

5.二次求和

暴力
首先觀察詢問,樹上鏈u→vu\to vuv點權(quán)加,顯然可以用樹上差分LOJ dfs序4 O(1)O(1)O(1)完成此操作,然后考慮對這些權(quán)值對答案的影響?
設(shè)經(jīng)過某點uuu符合條件的路徑條數(shù)為pathu\text{path}_upathu?
當(dāng)uuu點權(quán)+c+c+c后,那么它對答案的貢獻則是pathu×c\text{path}_u×cpathu?×c,而對于u→vu\to vuv這條路徑來說,點權(quán)全部+c+c+c后對答案的貢獻是

∑i∈u→vpathi×c\sum_{i \in u\to v} \text{path}_i×ciuv?pathi?×c

如果完成pathu\text{path}_upathu?預(yù)處理后以及最開始的答案,之后每個操作對答案的貢獻可根據(jù)上述方法求出。

觀察題目數(shù)據(jù)范圍發(fā)現(xiàn)前50pts50\text{pts}50ptsn≤2000n\leq 2000n2000,這就很容易了,暴力枚舉每個點為起點,把所有路徑都拿出來然后記錄一下每條路徑,當(dāng)一條路徑符號題意時維護pathu\text{path}_upathu?即可。

然后觀察到60pts60\text{pts}60pts的點是一條鏈,可以亂搞一下在騙10pts10\text{pts}10pts

#include<queue> #include<cstring> #include<iostream> #include<algorithm> using namespace std; using ll=long long; constexpr int N=2010; constexpr ll mod=1e9+7; int h[N],e[2*N],ne[2*N],idx; void add(int a,int b){e[idx]=b,ne[idx]=h[a],h[a]=idx++;} int n,m,L,R; int a[N],d[N]; bool vis[N]; ll b[N],ans,path[N]; int pre[N]; void bfs(int S) {memset(d,0,sizeof(int)*(n+1));memset(pre,0,sizeof(int)*(n+1));memset(b,0,sizeof(ll)*(n+1));memset(vis,0,sizeof(bool)*(n+1));queue<int> q;q.push(S); d[S]=0;b[S]=a[S];vis[S]=1;while(q.size()){int u=q.front();q.pop();for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(!vis[v]){d[v]=d[u]+1;b[v]=(b[u]+a[v])%mod;pre[v]=u;//記錄路徑q.push(v);vis[v]=1;}}}for(int i=1;i<=n;i++)if(L<=d[i]&&d[i]<=R) {ans=(ans+b[i])%mod;int u=i;while(u) //維護路徑path{path[u]++;u=pre[u];}} } int sz[N],fa[N],dep[N],son[N]; ll s[N]; void dfs1(int u) {dep[u]=dep[fa[u]]+1;sz[u]=1;s[u]=path[u]+s[fa[u]];// 預(yù)處理s數(shù)組for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa[u]) continue;fa[v]=u;dfs1(v);sz[u]+=sz[v];if(sz[son[u]]<sz[v]) son[u]=v;} } int dfn[N],timestamp,top[N]; void dfs2(int u,int t) {dfn[u]=++timestamp;top[u]=t;if(son[u]) dfs2(son[u],t);for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa[u]||v==son[u]) continue;dfs2(v,v);} } int lca(int u,int v) {while(top[u]!=top[v]){if(dep[top[u]]>=dep[top[v]]) u=fa[top[u]];else v=fa[top[v]];}return dep[u]>=dep[v]?v:u; } void init(int n) {memset(h,-1,sizeof(int)*(n+1));memset(s,0,sizeof(ll)*(n+1));memset(path,0,sizeof(ll)*(n+1));memset(dfn,0,sizeof(int)*(n+1));memset(son,0,sizeof(int)*(n+1));memset(top,0,sizeof(int)*(n+1));memset(sz,0,sizeof(int)*(n+1));timestamp=idx=0;ans=0; } int main() {int T;cin>>T;while(T--){cin>>n>>m>>L>>R;init(n);--L,--R;for(int i=1;i<=n;i++) cin>>a[i];for(int i=2;i<=n;i++){int p;cin>>p;add(i,p),add(p,i);}for(int i=1;i<=n;i++) bfs(i);ans=1ll*ans*500000004%mod;for(int i=1;i<=n;i++) path[i]/=2;dfs1(1);dfs2(1,1);while(m--){int a,b,c;cin>>a>>b>>c;int anc=lca(a,b);ans=(ans+(s[a]+s[b]-s[anc]-s[fa[anc]])*c%mod)%mod;cout<<ans<<'\n';}} }

點分治

學(xué)過點分治的一看就知道是個點分治的題,但是看到之后就懶得寫代碼又臭又長

考慮如何通過點分治求出最初的答案以及維護出數(shù)組pathu\text{path}_upathu?

首先對于最初的答案顯然就是個點分治模板題這里不在贅述,而對于pathu\text{path}_upathu?顯然不能像暴力一下記錄路徑然后加,這樣就沒必要分治了~~

點分治的技巧是花費log的代價把任意路徑變成通過當(dāng)前根節(jié)點的路徑,也就是目前考慮的路徑都會穿過當(dāng)前根節(jié)點

既然穿過根節(jié)點,我們只需要在每條路徑的端點記錄一下,顯然如果當(dāng)前點是一個路徑的端點,那么它的祖先節(jié)點也需要被這條路徑覆蓋,只需要一遍dfs把子節(jié)點“回收”一下就能夠?qū)崿F(xiàn)將此路徑覆蓋的點都標(biāo)記。

不過一條路徑是有兩個端點的,但是點分治的過程(枚舉當(dāng)前子樹的端點,在前面的子樹中查找符合條件的另一個端點)只能知道當(dāng)前的一個端點,而另一個端點我們是不知道的。

比如當(dāng)前子樹vvv的一個端點bbb,而前面的子樹有一個端點aaa,也就是a→rt→ba\to rt\to bartb這條路徑符合條件,點分治的過程中我們只知道當(dāng)前子樹vvv的端點bbb

這里我采用先從前向后遍歷子樹(找到端點bbb),然后再從后往前遍歷子樹(找到端點aaa),不難發(fā)現(xiàn)這樣枚舉都會枚舉到a→rt→ba\to rt\to bartb這條路徑,并且一次是端點bbb,另一次是端點aaa(注意rt可能會多算需要減去)

對于直接到當(dāng)前根節(jié)點的路徑我們特殊處理即可

時間復(fù)雜度O(αnlog?2n+mlog?n)O(\alpha n\log^2 n+m\log n)O(αnlog2n+mlogn)

#pragma GCC optimize(2) #include<cstring> #include<iostream> #include<algorithm> using namespace std; using ll=long long; constexpr int N=100010; constexpr ll mod=1e9+7; int h[N],h2[N],e[4*N],ne[4*N],idx; void add(int h[],int a,int b){e[idx]=b,ne[idx]=h[a],h[a]=idx++;} int n,m,L,R; int a[N],p[N]; int sz[N],fa[N],dep[N],son[N]; int dfn[N],timestamp,top[N]; ll s[N]; ll path[N],ans; int rt; ll fw[2][N]; int lowbit(int x){return x&-x;} void update(int i,int k,ll x){if(k<=0) return;for(;k<=n;k+=lowbit(k)) fw[i][k]=(fw[i][k]+x)%mod;} ll query(int i,int k){if(k<=0) return 0;ll res=0;for(;k;k-=lowbit(k)) res=(res+fw[i][k])%mod;return res;} struct node {int d,u;ll w; }d[N]; ll b[N];int cnt; bool del[N]; void dfs_rt(int u,int fa,int tot)//找重心 {sz[u]=1;int mx=0;for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa||del[v]) continue;dfs_rt(v,u,tot);sz[u]+=sz[v];mx=max(mx,sz[v]);}mx=max(mx,tot-sz[u]);if(2*mx<=tot) rt=u; } void dfs_sz(int u,int fa)//處理sz {sz[u]=1;for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa||del[v]) continue;dfs_sz(v,u);sz[u]+=sz[v];} } void dfs_dist(int u,int fa,int dist,ll w)//找路徑 {w%=mod;d[++cnt]={dist,u,w};for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa||del[v]) continue;dfs_dist(v,u,dist+1,w+a[v]);} } void dfs_fw(int u,int fa,int dist,ll w)//清空樹狀數(shù)組 {w%=mod;update(0,dist,-w);update(1,dist,-1);for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa||del[v]) continue;dfs_fw(v,u,dist+1,w+a[v]);} } void dfs_add(int u,int fa) {for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa||del[v]) continue;dfs_add(v,u);b[u]+=b[v];b[u]%=mod;}path[u]+=b[u];path[u]%=mod; } void dfs_b(int u,int fa)//清空b數(shù)組 {b[u]=0;for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa||del[v]) continue;dfs_b(v,u);} } void dfs_calc(int u,int fa,int dist,ll w)//到當(dāng)前根節(jié)點特殊路徑 {w%=mod;if(L<=dist&&dist<=R) b[u]++,ans=(ans+w)%mod;for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa||del[v]) continue;dfs_calc(v,u,dist+1,w+a[v]);} } void work(int u,int tot) {dfs_rt(u,0,tot);u=rt;dfs_sz(u,0);del[u]=1;//順著子樹for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(del[v]) continue;cnt=0;// 指針dfs_dist(v,u,1,a[v]);for(int k=1;k<=cnt;k++) ans=(ans+query(0,R-d[k].d)-query(0,L-1-d[k].d))%mod;for(int k=1;k<=cnt;k++) {int tmp=query(1,R-d[k].d)-query(1,L-1-d[k].d);ans=(ans+1ll*d[k].w%mod*tmp%mod)%mod;b[u]-=tmp;//防止當(dāng)前根節(jié)點重復(fù)算b[d[k].u]+=tmp;}for(int k=1;k<=cnt;k++)update(0,d[k].d,d[k].w+a[u]),update(1,d[k].d,1);}dfs_fw(u,0,0,a[u]);//逆子樹for(int i=h2[u];i!=-1;i=ne[i]){int v=e[i];if(del[v]) continue;cnt=0;// 指針dfs_dist(v,u,1,a[v]);for(int k=1;k<=cnt;k++) {int tmp=query(1,R-d[k].d)-query(1,L-1-d[k].d);b[d[k].u]+=tmp;}for(int k=1;k<=cnt;k++)update(0,d[k].d,d[k].w+a[u]),update(1,d[k].d,1);}dfs_calc(u,0,0,a[u]);dfs_add(u,0);dfs_fw(u,0,0,a[u]); dfs_b(u,0);for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(del[v]) continue;work(v,sz[v]);}} //==================================================點分治 void dfs1(int u) {dep[u]=dep[fa[u]]+1;sz[u]=1;s[u]=(path[u]+s[fa[u]])%mod;for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa[u]) continue;fa[v]=u;dfs1(v);sz[u]+=sz[v];if(sz[son[u]]<sz[v]) son[u]=v;} } void dfs2(int u,int t) {dfn[u]=++timestamp;top[u]=t;if(son[u]) dfs2(son[u],t);for(int i=h[u];i!=-1;i=ne[i]){int v=e[i];if(v==fa[u]||v==son[u]) continue;dfs2(v,v);} } int lca(int u,int v) {while(top[u]!=top[v]){if(dep[top[u]]>=dep[top[v]]) u=fa[top[u]];else v=fa[top[v]];}return dep[u]>=dep[v]?v:u; } //================================================== 樹剖求lca void init(int n) {memset(h,-1,sizeof(int)*(n+1));memset(h2,-1,sizeof(int)*(n+1));memset(path,0,sizeof(ll)*(n+1));memset(s,0,sizeof(ll)*(n+1));memset(dfn,0,sizeof(int)*(n+1));memset(son,0,sizeof(int)*(n+1));memset(top,0,sizeof(int)*(n+1));memset(sz,0,sizeof(int)*(n+1));memset(del,0,sizeof(bool)*(n+1));timestamp=idx=0;ans=0; } int main() {int T;scanf("%d",&T);while(T--){scanf("%d%d%d%d",&n,&m,&L,&R);init(n);--L,--R;for(int i=1;i<=n;i++) scanf("%lld",&a[i]);for(int i=2;i<=n;i++) scanf("%lld",&p[i]);for(int i=2;i<=n;i++) add(h,i,p[i]),add(h,p[i],i);//順著子樹for(int i=n;i>=2;i--) add(h2,i,p[i]),add(h2,p[i],i);//逆著子樹work(1,n);dfs1(1);dfs2(1,1);while(m--){int a,b,c;scanf("%d%d%d",&a,&b,&c);int anc=lca(a,b);ans=(ans+(s[a]+s[b]-s[anc]-s[fa[anc]])*c%mod)%mod;ans=(ans+mod)%mod;printf("%lld\n",ans);}} }

這題真蛋疼,寫完還因為常數(shù)太大(點分治9次dfs)TLE了,吸氧才AC
AcWing 3261. 二次求和吸氧過了,官網(wǎng)還是TLE,80pts

要加油哦~

總結(jié)

以上是生活随笔為你收集整理的2018CCF-CSP 5.二次求和(点分治)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。