• BZOJ3784: 树上的路径


    III.BZOJ3784: 树上的路径

    思路1:

    淀粉质。用priority_queue维护前\(m\)长的路径的长度。用multiset维护点分治时,之前所有子树的路径长度,然后对于新子树中的每一条路径,在multiset中从大往小枚举另一半路径拼一起并尝试加入优先队列。如果加入失败,那么对于这个点,集合中再往前的数也不会成功,可以直接跳掉。

    这种方法非常显然,但会被长度递增的路径序列卡掉。

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    int n,m,head[50100],cnt,sz[50100],msz[50100],ROOT,SZ;
    priority_queue<int,vector<int>,greater<int> >q;
    bool vis[50100];
    multiset<int>s;
    vector<int>v;
    struct node{
    	int to,next,val;
    }edge[100100];
    void ae(int u,int v,int w){
    	edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
    	edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=w,head[v]=cnt++;
    }
    void getroot(int x,int fa){
    	sz[x]=1,msz[x]=0;
    	for(int i=head[x];i!=-1;i=edge[i].next)if(!vis[edge[i].to]&&edge[i].to!=fa)getroot(edge[i].to,x),sz[x]+=sz[edge[i].to],msz[x]=max(msz[x],sz[edge[i].to]);
    	msz[x]=max(msz[x],SZ-sz[x]);
    	if(msz[x]<msz[ROOT])ROOT=x;
    }
    void getdis(int x,int fa,int DIS){
    	v.push_back(DIS);
    	for(int i=head[x];i!=-1;i=edge[i].next)if(!vis[edge[i].to]&&edge[i].to!=fa)getdis(edge[i].to,x,DIS+edge[i].val);
    }
    bool ins(int x){
    	if(q.size()<m){q.push(x);return true;}
    	if(q.top()<x){q.pop(),q.push(x);return true;}
    	return false;
    }
    void getans(int x){
    	s.insert(0);
    	for(int i=head[x];i!=-1;i=edge[i].next){
    		if(vis[edge[i].to])continue;
    		getdis(edge[i].to,x,edge[i].val);
    		sort(v.begin(),v.end());
    		for(int j=v.size()-1;j>=0;j--)for(multiset<int>::reverse_iterator it=s.rbegin();it!=s.rend();it++)if(!ins(v[j]+*it))break;
    		for(int j=0;j<v.size();j++)s.insert(v[j]);
    		v.clear();
    	}
    	s.clear();
    }
    void solve(int x){
    	getans(x),vis[x]=true;
    	for(int i=head[x];i!=-1;i=edge[i].next){
    		if(vis[edge[i].to])continue;
    		ROOT=0,SZ=sz[edge[i].to],getroot(edge[i].to,x),solve(ROOT);
    	}
    }
    int main(){
    	scanf("%d%d",&n,&m),memset(head,-1,sizeof(head));
    	for(int i=1,x,y,z;i<n;i++)scanf("%d%d%d",&x,&y,&z),ae(x,y,z);
    	msz[0]=0x3f3f3f3f,SZ=n,getroot(1,0),solve(ROOT);
    	while(!q.empty())v.push_back(q.top()),q.pop();
    	for(int i=v.size()-1;i>=0;i--)printf("%d\n",v[i]);
    	return 0;
    }
    

    思路2:

    有一种东西叫做“淀粉徐点分序”,即淀粉质过程中所有访问过的节点所组成的序列,即所有分治树的dfs序的拼接。显然,它的长度是\(O(n\log n)\)的(即淀粉质常规复杂度)。那么它有什么用呢?

    我们考虑单次分治的分治树。则对于一个节点来说,一条经过分治树根的路径的另一端,必定存在于分治树中除了它所在的那颗子树外其他的位置。因为整棵分治树在淀粉质时是一段连续的序列,而它所在的子树也是一段连续序列(dfs序的性质),所以节点\(x\)的路径另一端所在位置可以这么表示:\((x,l,r)\),即一端是点分序中的第\(x\)个位置,另一端可以在点分序中区间\([l,r]\)内的任何位置。这就是[NOI2010]超级钢琴的内容。直接用堆+ST表即可在\(O(n\log n)\)时间内完成。详情可见该题题解,不再赘述。因为总序列长度是\(O(n\log n)\)的,因此总复杂度是\(O(n\log^2n)\)

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    int n,m,head[50100],cnt,sz[50100],msz[50100],ROOT,SZ,dfn[1001000],mx[1001000][20],tot,LG[1001000];
    int MAX(int x,int y){
    	return dfn[x]>dfn[y]?x:y;
    }
    int CALC(int L,int R){
    	int k=LG[R-L+1];
    	return MAX(mx[L][k],mx[R-(1<<k)+1][k]);
    }
    struct node{
    	int x,l,r,pla,val;
    	node(int u=0,int v=0,int w=0){
    		x=u,l=v,r=w;
    	}
    	node calc(){
    		pla=CALC(l,r);
    		val=dfn[x]+dfn[pla];
    		return *this;
    	}
    	friend bool operator <(const node &x,const node &y){
    		return x.val<y.val;
    	}
    };
    struct Edge{
    	int to,next,val;
    }edge[100100];
    void ae(int u,int v,int w){
    	edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
    	edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=w,head[v]=cnt++;
    }
    priority_queue<node>q;
    vector<pair<int,int> >v;
    vector<node>vv;
    bool vis[50100];
    void getroot(int x,int fa){
    	sz[x]=1,msz[x]=0;
    	for(int i=head[x];i!=-1;i=edge[i].next)if(!vis[edge[i].to]&&edge[i].to!=fa)getroot(edge[i].to,x),sz[x]+=sz[edge[i].to],msz[x]=max(msz[x],sz[edge[i].to]);
    	msz[x]=max(msz[x],SZ-sz[x]);
    	if(msz[x]<msz[ROOT])ROOT=x;
    }
    void getdis(int x,int fa,int DIS){
    	dfn[++tot]=DIS;
    	for(int i=head[x];i!=-1;i=edge[i].next)if(!vis[edge[i].to]&&edge[i].to!=fa)getdis(edge[i].to,x,DIS+edge[i].val);
    }
    void getans(int x){
    	int L=++tot;
    	dfn[tot]=0;
    	v.clear();
    	for(int i=head[x];i!=-1;i=edge[i].next){
    		if(vis[edge[i].to])continue;
    		pair<int,int>p;
    		p.first=tot;
    		getdis(edge[i].to,x,edge[i].val);
    		p.second=tot+1;
    		v.push_back(p);
    	}
    	int R=tot;
    	for(int j=0;j<v.size();j++){
    		pair<int,int> x=v[j];
    		for(int i=x.first+1;i<=x.second-1;i++)vv.push_back(node(i,L,x.first));
    	}
    }
    void solve(int x){
    	getans(x),vis[x]=true;
    	for(int i=head[x];i!=-1;i=edge[i].next){
    		if(vis[edge[i].to])continue;
    		ROOT=0,SZ=sz[edge[i].to],getroot(edge[i].to,x),solve(ROOT);
    	}
    }
    int main(){
    	scanf("%d%d",&n,&m),memset(head,-1,sizeof(head));
    	for(int i=1,x,y,z;i<n;i++)scanf("%d%d%d",&x,&y,&z),ae(x,y,z);
    	msz[0]=0x3f3f3f3f,SZ=n,getroot(1,0),solve(ROOT);
    //	for(int i=1;i<=tot;i++)printf("%d ",dfn[i]);puts("");
    	for(int i=2;i<=tot;i++)LG[i]=LG[i>>1]+1;
    	for(int i=1;i<=tot;i++)mx[i][0]=i;
    	for(int j=1;j<=LG[tot];j++)for(int i=1;i+(1<<j)-1<=tot;i++)mx[i][j]=MAX(mx[i][j-1],mx[i+(1<<(j-1))][j-1]);
    	for(int i=0;i<vv.size();i++)q.push(vv[i].calc());
    	while(m--){
    		node p=q.top();q.pop();
    		printf("%d\n",p.val);
    		if(p.pla!=p.l)q.push(node(p.x,p.l,p.pla-1).calc());
    		if(p.pla!=p.r)q.push(node(p.x,p.pla+1,p.r).calc());
    	}
    	return 0;
    }
    

  • 相关阅读:
    jQuery中.html(“xxx”)和.append("xxx")的区别和不同
    Jquery中对checkbox的各种“全选”或者“取消”功能实现(特别注意1.6+的一定不能使用attr来取属性了!用prop!)
    Jquery 中each循环嵌套的使用示例教程
    关于Jquery中的$.each获取各种返回类型数据的使用方法
    IMEI是什么? 怎样查手机串号IMEI
    linux useradd(adduser)命令参数及用法详解(linux创建新用户命令)
    linux 的useradd 命令的p选项
    Linux SSH远程文件/目录传输命令scp
    C++ 迭代器 基础介绍
    C++中map的一点疑惑...
  • 原文地址:https://www.cnblogs.com/Troverld/p/14605779.html
Copyright © 2020-2023  润新知