静态点分治总结
點(diǎn)分治是世界上最好的算法QwQ
點(diǎn)分治可以解決各種樹(shù)上的邊權(quán)點(diǎn)權(quán)問(wèn)題,然后如果你發(fā)現(xiàn)這個(gè)題好像問(wèn)的特別玄學(xué),lca,樹(shù)差都做不了,樹(shù)上動(dòng)‘龜’更做不了,只能暴力時(shí),這個(gè)題大多數(shù)情況就是點(diǎn)分治了
點(diǎn)分治的思路,考慮指定p為根,對(duì)于p而言,樹(shù)上路徑分為兩類(lèi),過(guò)p的路徑,子樹(shù)內(nèi)的路徑,顯然對(duì)于子樹(shù)內(nèi)的路徑,只要讓他繼續(xù)遞歸下去就行了,然后我們現(xiàn)在要算的就是經(jīng)過(guò)p的路徑
算法過(guò)程
一,求出來(lái)重心
二,從重心開(kāi)始跑dfs維護(hù)出這一段權(quán)值(路徑長(zhǎng)度等)
三,運(yùn)行calc計(jì)算對(duì)ans貢獻(xiàn)
四,從子樹(shù)運(yùn)行一--三
點(diǎn)分治時(shí)間復(fù)雜度nlogn,證明被咕了
為什么非得是重心,首先復(fù)雜度與最大的子樹(shù)有關(guān),如果是直接往下搜y時(shí),遇到一個(gè)鏈會(huì)退化成$n^2 log n$,若從重心開(kāi)始搜,保證了子樹(shù)小于$\frac{n}{2}$
?
?
運(yùn)行點(diǎn)分治大約有兩種思路第一種是暴力計(jì)算然后容斥(為什么容斥被咕了),第二種是類(lèi)似樹(shù)形背包轉(zhuǎn)移,再加上各種數(shù)據(jù)結(jié)構(gòu)維護(hù)
兩種都比較常用容斥特別好打,背包適用廣
我們拿幾個(gè)例題看看點(diǎn)分治的思路
例題
聰聰可可
一顆n(n<=20000)個(gè)點(diǎn)的樹(shù)上,求長(zhǎng)度是3的倍數(shù)的路徑條數(shù)。
思路清真,我們統(tǒng)計(jì)出來(lái)各個(gè)子樹(shù)內(nèi)路徑,最后讓他們合并(長(zhǎng)度余2與長(zhǎng)度余1合并,長(zhǎng)度余3于長(zhǎng)度余3合并)最終就得到了解,我們維護(hù)出每一段路徑的余數(shù)
類(lèi)似于
void getdeep(ll x,ll fa){tt[deep[x]%3]++;for(ll i=head[x];i;i=nxt[i]){ll y=ver[i];if(vis[y]||y==fa) continue;deep[y]=deep[x]+edge[i];getdeep(y,x);} } ll calc(ll x,ll val){deep[x]=val;for(ll i=0;i<=2;i++)tt[i]=0;getdeep(x,0);return tt[1]*tt[2]*2+tt[0]*tt[0]; } #include<bits/stdc++.h> using namespace std; #define ll long long #define Inf 1008611555ll #define A 1000000 ll head[A],nxt[A],ver[A],edge[A],sz[A],vis[A],tt[A],deep[A]; ll size,toot,n,m,mx,ans,tot; void add(ll x,ll y,ll z){nxt[++tot]=head[x],head[x]=tot,ver[tot]=y,edge[tot]=z; } ll gcd(ll x,ll y){if(y==0) return x;return gcd(y,x%y); } void gettoot(ll x,ll fa){sz[x]=1;ll num=0;for(ll i=head[x];i;i=nxt[i]){ll y=ver[i];if(vis[y]||y==fa) continue;gettoot(y,x);sz[x]+=sz[y];num=max(num,sz[y]);}num=max(num,size-sz[x]);if(num<mx) mx=num,toot=x; } void getdeep(ll x,ll fa){tt[deep[x]%3]++;for(ll i=head[x];i;i=nxt[i]){ll y=ver[i];if(vis[y]||y==fa) continue;deep[y]=deep[x]+edge[i];getdeep(y,x);} } ll calc(ll x,ll val){deep[x]=val;for(ll i=0;i<=2;i++)tt[i]=0;getdeep(x,0);return tt[1]*tt[2]*2+tt[0]*tt[0]; } ll solve(ll x){ans+=calc(x,0); // printf("x=%lld t1=%lld t2=%lld t0=%lld ans=%lld\n",x,tt[1],tt[2],tt[0],ans);vis[x]=1;for(ll i=head[x];i;i=nxt[i]){ll y=ver[i];if(vis[y]) continue;ans-=calc(y,edge[i]); // printf("ans=%lld\n",ans);size=sz[y];mx=Inf;gettoot(y,0);solve(toot);} } int main(){scanf("%lld",&n);for(ll i=1;i<n;i++){ll x,y,z;scanf("%lld%lld%lld",&x,&y,&z);add(x,y,z);add(y,x,z);}mx=Inf;size=n;gettoot(1,0);solve(toot);ll g=gcd(ans,n*n);printf("%lld/%lld\n",ans/g,n*n/g); } View Codetree
給你一棵TREE,以及這棵樹(shù)上邊的距離.問(wèn)有多少對(duì)點(diǎn)它們兩者間的距離小于等于K
思路清真,我們統(tǒng)計(jì)出來(lái)子樹(shù)之間距離然后統(tǒng)計(jì)一波排序一波,點(diǎn)對(duì)一波就好了
類(lèi)似于
void getdis(ll x,ll fa){q[++r]=d[x];for(ll i=head[x];i;i=nxt[i]){ll y=ver[i];if(y==fa||vis[y]) continue;d[y]=d[x]+edge[i];getdis(y,x);} } ll calc(ll x,ll val){r=0;d[x]=val;getdis(x,0);ll sum=0;l=1;sort(q+1,q+r+1);while(l<r){if(q[l]+q[r]<=k) sum+=r-l,++l;else --r;}return sum;}
#include<bits/stdc++.h> #define ll long long #define A 1100000 #define Inf 1000000000ll using namespace std; ll head[A],nxt[A],ver[A],sz[A],q[A],d[A],sum[A],edge[A]; ll size,toot,mx,n,m,tot=0,ans,k,l,r; bool vis[A]; void add(ll x,ll y,ll z){ver[++tot]=y,nxt[tot]=head[x],head[x]=tot,edge[tot]=z; } void gettoot(ll x,ll fa){sz[x]=1;ll num=0;for(ll i=head[x];i;i=nxt[i]){ll y=ver[i];if(y==fa||vis[y]) continue;gettoot(y,x);sz[x]+=sz[y];num=max(num,sz[y]);}num=max(size-sz[x],num);if(num<mx) mx=num,toot=x; } void getdis(ll x,ll fa){q[++r]=d[x]; // printf(" x=%lld r=%lld\n",x,r);for(ll i=head[x];i;i=nxt[i]){ll y=ver[i];if(y==fa||vis[y]) continue;d[y]=d[x]+edge[i];getdis(y,x);} } ll calc(ll x,ll val){r=0;d[x]=val;getdis(x,0);ll sum=0;l=1; // printf("r=%lld l=%lld\n",l,r);sort(q+1,q+r+1);while(l<r){if(q[l]+q[r]<=k) sum+=r-l,++l;else --r;}return sum; } ll solve(ll x){ans+=calc(x,0);vis[x]=1;for(ll i=head[x];i;i=nxt[i]){ll y=ver[i];if(vis[y]) continue;ans-=calc(y,edge[i]);size=sz[y];mx=Inf;gettoot(y,0);solve(toot);} } int main(){scanf("%lld",&n);for(ll i=1;i<n;i++){ll x,y,z;scanf("%lld%lld%lld",&x,&y,&z);add(x,y,z);add(y,x,z);}scanf("%lld",&k);size=n;mx=Inf;gettoot(1,0);solve(toot);cout<<ans<<endl; } View Code
race
路徑和為k且路徑的邊數(shù)最少
我們開(kāi)一個(gè)桶,維護(hù)出路徑和為k時(shí)邊數(shù)最小值,記得清零
?類(lèi)似這樣
void getdiss(ll x,ll fa,ll dp){if(deep[x]<=k)ans=min(ans,dp+cnt[k-deep[x]]);for(ll i=head[x];i;i=nxt[i]){ll y=ver[i];if(vis[y]||y==fa) continue;deep[y]=deep[x]+edge[i];getdiss(y,x,dp+1);} } void uptoday(ll x,ll fa,ll dp,ll ooo){if(deep[x]<=k)ooo?(cnt[deep[x]]=min(cnt[deep[x]],dp)):cnt[deep[x]]=n;for(ll i=head[x];i;i=nxt[i]){ll y=ver[i];if(vis[y]||y==fa) continue;uptoday(y,x,dp+1,ooo);} } void solve(ll x){vis[x]=1;cnt[0]=0;for(ll i=head[x];i;i=nxt[i]){ll y=ver[i];if(vis[y]) continue;deep[y]=edge[i];getdiss(y,0,1);uptoday(y,0,1,1);}for(ll i=head[x];i;i=nxt[i]){ll y=ver[i];if(!vis[y])uptoday(y,0,1,0);}for(ll i=head[x];i;i=nxt[i]){ll y=ver[i];if(vis[y]) continue;mx=Inf;size=sz[y];gettoot(y,0);solve(toot);} } #include<bits/stdc++.h> using namespace std; #define ll int #define Inf 1e9 #define A 6100000 ll t[A+10],sz[A],nxt[A],head[A],ver[A],edge[A],cnt[A],deep[A]; ll mx,size,num,k,toot,ans=0,n,tot=0; bool vis[A]; void add(ll x,ll y,ll z){ver[++tot]=y,nxt[tot]=head[x],head[x]=tot,edge[tot]=z; } void gettoot(ll x,ll fa){sz[x]=1;ll num=0;for(ll i=head[x];i;i=nxt[i]){ll y=ver[i];if(vis[y]||y==fa) continue;gettoot(y,x);sz[x]+=sz[y];num=max(num,sz[y]);}num=max(num,size-sz[x]);if(num<mx) mx=num,toot=x; } void getdiss(ll x,ll fa,ll dp){if(deep[x]<=k)ans=min(ans,dp+cnt[k-deep[x]]);for(ll i=head[x];i;i=nxt[i]){ll y=ver[i];if(vis[y]||y==fa) continue;deep[y]=deep[x]+edge[i];getdiss(y,x,dp+1);} } void uptoday(ll x,ll fa,ll dp,ll ooo){if(deep[x]<=k)ooo?(cnt[deep[x]]=min(cnt[deep[x]],dp)):cnt[deep[x]]=n;for(ll i=head[x];i;i=nxt[i]){ll y=ver[i];if(vis[y]||y==fa) continue;uptoday(y,x,dp+1,ooo);} } void solve(ll x){vis[x]=1;cnt[0]=0;for(ll i=head[x];i;i=nxt[i]){ll y=ver[i];if(vis[y]) continue;deep[y]=edge[i];getdiss(y,0,1);uptoday(y,0,1,1);}for(ll i=head[x];i;i=nxt[i]){ll y=ver[i];if(!vis[y])uptoday(y,0,1,0);}for(ll i=head[x];i;i=nxt[i]){ll y=ver[i];if(vis[y]) continue;mx=Inf;size=sz[y];gettoot(y,0);solve(toot);} } int main(){cin>>n>>k;ans=n;ll x,y,z;for(ll i=1;i<=n-1;i++){cin>>x>>y>>z;x++,y++;add(x,y,z);add(y,x,z);}mx=Inf;size=n;for(ll i=1;i<=k;i++) cnt[i]=n;gettoot(1,0);solve(toot);if(ans==n)puts("-1");else printf("%d\n",ans); } View Code常見(jiàn)題目
路徑和等于或小于等于k的點(diǎn)對(duì)(路徑條數(shù))。例如tree
路徑和為某個(gè)數(shù)的倍數(shù)。沒(méi)遇到過(guò),但也應(yīng)該類(lèi)似于聰聰可可
路徑和為k且路徑的邊數(shù)最少。例如race,我們只要開(kāi)一個(gè)桶記錄一下路徑為k時(shí)最小路徑
路徑和mod M后為某個(gè)值。例如聰聰可可
路徑上經(jīng)過(guò)不允許點(diǎn)的個(gè)數(shù)不超過(guò)某個(gè)值,且路徑和最大。例如免費(fèi)旅行
大多數(shù)題開(kāi)一個(gè)桶i,表示距離為i的相關(guān)信息,有時(shí)我們也可以用一些高級(jí)數(shù)據(jù)結(jié)構(gòu)(例如樹(shù)狀數(shù)組)進(jìn)行一波維護(hù)。
?
轉(zhuǎn)載于:https://www.cnblogs.com/znsbc-13/p/11249423.html
總結(jié)
- 上一篇: 28线程
- 下一篇: mybatis插入数据后返回自增主键ID