SPOJ - FTOUR2 (点分治+树状数组)
Posted lis-
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SPOJ - FTOUR2 (点分治+树状数组)相关的知识,希望对你有一定的参考价值。
题目:https://vjudge.net/contest/307753#problem/I
题意:有一颗树,上面有白色黑色点,每个点上有一个权值,权值可以为负,现在我要求一条路径,权值和最大,这条路径满足 白色可以随便经过多少个,黑色点的个数必须<=m
思路:首先又是树上路径题,必然点分治,其实这个题我们在考虑当前子树路径时,我们找前面子树出现过并且黑色出现数<=m-当前黑色节点数 里面出现的最大值这样的一条路径。
举个栗子: 限制路径黑色数:5
当前子树下的黑色数 :2 权值 5
(前面子树出现数)
黑色数 0 1 2 3 4 5
权值 1 6 2 4 7 3
我们就应该在 0-3黑色数里面寻找最大值 ,我们找到了6,所以可以更新最大值为 6+5
还有我们对于这条路径黑色数已经超过要求的可以不再搜下去,因为这本身就不符合要求了,下面子树的肯定也不符合。
所以现在我们的问题转化为如何在前面子树固定黑色数中找到最大值,我们还要记得把当前子树访问完后与前面子树合并,因为一个固定黑色数我们保留最大值即可,其实我们可以把黑色数转化为一个区间,然后区间找最大值,所以我们可以用到树状数组,但是我们非常非常要注意的是->
!!!注意:因为我们找重心已经是O(n)了,然后我们在重心里面的操作不能是O(n)的,但是我们要清空树状数组要O(n),就成了O(n^2)了,我们要怎么优化呢,>_<,之前我就是没想清楚这里的时间复杂度然后超时了,这里我们只能也是log的,怎么办呢,我们记录下来我们之前操作了哪些点,然后再置0即可,但是树状数组操作的点是以二进制的,这又是第二次犯蠢的地方,我最开始直接把那个路径出现的点去置0了,但是因为树状数组要向上界更新最大值,所以不止更改了这些点,所以要怎么搞呢>_<, 其实就是把记录改的点放在树状数组update操作里面即可,我真的傻了>_<
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<iostream> #include<vector> #include<queue> #define maxn 200005 #define mod 0x3f3f3f3f using namespace std; typedef long long ll; ll da; vector<pair<ll,ll> > mp[maxn],xx[maxn];//存下图 pair<ll,ll> e[maxn]; ll e2[20*maxn]; bool vis[maxn];//标记曾经使用过的重心 ll maxsize[maxn],dis[maxn],d[maxn],flag[maxn],yj[maxn];//maxsize 当前节点的最大子树 ll siz[maxn],xd[maxn];// dis 到重心的距离 d 出现过的距离 ll n,m,k,rt,sum,qe,qe2,ans1,ans2; // siz 当前节点的子树个数 e 出现的距离 rt代表当前重心 void find(ll x,ll f)//找出重心 siz[x]=1; maxsize[x]=0; for(int i=0;i<mp[x].size();i++) pair<ll,ll> q=mp[x][i]; if(q.first==f||vis[q.first]) continue;//vis数组标记曾经使用过的重心 find(q.first,x); siz[x]+=siz[q.first]; maxsize[x]=max(maxsize[x],siz[q.first]); maxsize[x]=max(maxsize[x],sum-siz[x]);//节点总数减去当前的子树数=以当前节点为根的父亲点子树数 if(maxsize[x]<maxsize[rt]) rt=x; void query(ll z,ll sm) if(z>ans1) ans1=z; ans2=sm; else if(z==ans1) ans2+=sm; ll lowbit(ll x) return x&(-x); void update(ll x,ll y) while(x<=m) e2[qe2++]=x; flag[x]=max(flag[x],y); x+=lowbit(x); ll query(ll x) ll ans=0; while(x) ans=max(ans,flag[x]); x-=lowbit(x); return ans; void get_dis(ll x,ll f,ll len,ll yjs,ll root) if(yjs<=m) ans1=max(ans1,len); else return; ll t=m-yjs; if(yj[root]) t++; ans1=max(ans1,len+query(t+1)); e[qe].first=yjs; e[qe].second=len; qe++; for(int i=0;i<mp[x].size();i++) pair<ll,ll> q=mp[x][i]; if(q.first==f||vis[q.first]) continue; //dis[q.first]=(dis[x]+len)%3; get_dis(q.first,x,len+q.second,yjs+yj[q.first],root); void divide(ll x) vis[x]=1; //printf("rt=%lld ans1=%lld\n",x,ans1); for(int i=0;i<mp[x].size();i++) pair<ll,ll> q=mp[x][i]; qe=0; if(vis[q.first]) continue; //dis[x]=q.second; get_dis(q.first,x,q.second,yj[x]+yj[q.first],x); for(int j=0;j<qe;j++) update(e[j].first+1,e[j].second); for(int i=0;i<qe2;i++) flag[e2[i]]=0; qe2=0; for(int i=0;i<mp[x].size();i++) pair<ll,ll> q=mp[x][i]; if(vis[q.first]) continue; //if(da>0) break; sum=siz[q.first]; rt=0; maxsize[rt]=mod; find(q.first,x); divide(rt); // vis[x]=0; void init() ans1=0;ans2=0; for(int i=0;i<=n+1;i++) mp[i].clear(); for(int i=0;i<=n+1;i++) vis[i]=0; for(int i=0;i<=n+1;i++) flag[i]=0; for(int i=0;i<=n+1;i++) yj[i]=0; int main() ll t; while(scanf("%lld%lld%lld",&n,&m,&k)!=EOF) ll a,b,c; init(); ll xx; for(int i=1;i<=k;i++) scanf("%lld",&xx); yj[xx]=1; for(int i=1;i<=n-1;i++) scanf("%lld%lld%lld",&a,&b,&c); mp[a].push_back(make_pair(b,c)); mp[b].push_back(make_pair(a,c)); sum=n;//当前节点数 rt=0; maxsize[0]=mod;//置初值 find(1,0); divide(rt); printf("%lld\n",ans1);
以上是关于SPOJ - FTOUR2 (点分治+树状数组)的主要内容,如果未能解决你的问题,请参考以下文章
SPOJ1825/FTOUR2:Free tour II——包看得懂/看不懂题解