BZOJ4009 [HNOI2015]接水果

本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作。

 

 

本文作者:ljh2000
作者博客:http://www.cnblogs.com/ljh2000-jump/
转载请注明出处,侵权必究,保留最终解释权!

 

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=4009

 

正解:整体二分+扫描线+树状数组

解题报告:

  考虑哪些水果会被当前盘子接到,显然只能是一端位于x子树中,另一端位于y子树中的水果。

  如果把两个dfs序区间看成横纵坐标,这种情况对应的就是一个矩阵。

  如果x是y的祖先,那么就是两个矩阵(拆分一下)。

  问题转化为,求覆盖每个水果(矩阵中的点)的盘子(一个矩阵)中的第k小权值。

 

  所以我们就可以整体二分辣!

  我开始想到整体二分之后就卡住了,因为不知道怎么维护一个大矩阵的统计。

  事实上,只要按x排序,然后树状数组统计y,把一个矩阵拆成4个点事件,2个+1,2个-1,就可以用扫描线从上往下扫一遍得到答案。

  这种方法比较巧妙,结合整体二分,常数也挺小的。

  我开始WA了很久,因为整体二分那里,我没有考虑x坐标在每个区间内的相对关系必须始终保持!我是从r往前存丢到右边的询问,所以肯定会WA!这一点在做整体二分的时候一定要注意!

  

//It is made by ljh2000
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <ctime>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <string>
#include <complex>
using namespace std;
typedef long long LL;
const int MAXN = 40011;
const int MAXM = 80011;
int n,m,Q,ecnt,first[MAXN],to[MAXM],next[MAXM],f[MAXN][17],dfn[MAXN],last[MAXN],deep[MAXN];
int Mcnt,ans[MAXN],c[MAXN<<1];
struct Matrix{ int lx,rx,ly,ry,val;}b[MAXN<<2];//矩形的左上角和右下角
struct ask{ int x,y,k,id; }q[MAXN],tem[MAXN<<1],tem2[MAXN<<1];
struct Thing{ int x,y,val; }wyh[MAXN<<3];
inline bool cmp(Matrix pp,Matrix pq){ return pp.val<pq.val; }
inline bool cmp2(ask pp,ask pq){ return pp.x<pq.x; }
inline bool cmp3(Thing pp,Thing pq){ return pp.x<pq.x; }

inline void link(int x,int y){ next[++ecnt]=first[x]; first[x]=ecnt; to[ecnt]=y; }
inline void add(int x,int val){ while(x<=n) { c[x]+=val; x+=x&(-x); } }
inline int query(int x){ int tot=0; while(x>0) { tot+=c[x]; x-=x&(-x); } return tot; }

inline int getint(){
    int w=0,q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar();
    if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w;
}

inline void dfs(int x,int fa){//预处理dfs序和倍增
	dfn[x]=++ecnt;
	for(int i=first[x];i;i=next[i]) {
		int v=to[i]; if(v==fa) continue;
		deep[v]=deep[x]+1; f[v][0]=x; dfs(v,x);
	}
	last[x]=ecnt;
}

inline int lca(int x,int y){
	if(deep[x]<deep[y]) swap(x,y);/*!!!*/ int t=0; while((1<<t)<=deep[x]) t++; t--;
	for(int i=t;i>=0;i--) if(deep[x]-(1<<i)>=deep[y]) x=f[x][i]; if(x==y) return x;
	for(int i=t;i>=0;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; return f[x][0];
}

inline void solve(int l,int r,int L,int R){//询问处理区间为[l,r],矩阵区间为[L,R]
	if(l>r) return ;
	if(L==R) {
		for(int i=l;i<=r;i++) ans[q[i].id]=b[L].val;
		return ;
	}
	int cnt=0,now=0,nowk,nowl=0,nowr=0,mid=(L+R)>>1;
	//把每个矩形拆分成四个点事件,从上往下执行扫描线,树状数组维护区间sum
	//在图上画一画能够发现,这样从上往下做可以统计经过这个点的矩阵个数
	//并且只要从头做到尾就相当于是执行了撤销操作,因为-1操作全都被执行了
	for(int i=L;i<=mid;i++) {
		wyh[++cnt]=(Thing){b[i].lx,b[i].ly,1};
		wyh[++cnt]=(Thing){b[i].lx,b[i].ry+1,-1};//不能因为越界就不处理!
		wyh[++cnt]=(Thing){b[i].rx+1,b[i].ly,-1};
		wyh[++cnt]=(Thing){b[i].rx+1,b[i].ry+1,1};
	}
	sort(wyh+1,wyh+cnt+1,cmp3);
	for(int i=l;i<=r;i++) {
		while(now<cnt && wyh[now+1].x<=q[i].x) now++,add(wyh[now].y,wyh[now].val);
		nowk=query(q[i].y);
		if(q[i].k<=nowk) tem[nowl++]=q[i];
		else { q[i].k-=nowk; tem2[nowr++]=q[i]; }//不能从r放起!x顺序会失去!!!
	}
	for(;now<cnt;) now++,add(wyh[now].y,wyh[now].val);
	for(int i=0;i<nowl;i++) q[i+l]=tem[i];
	for(int i=0;i<nowr;i++) q[l+nowl+i]=tem2[i];
	solve(l,l+nowl-1,L,mid);
	solve(l+nowl,r,mid+1,R);
}

inline void work(){
	n=getint(); m=getint(); Q=getint(); int x,y,z,LCA,yfa;
	for(int i=1;i<n;i++) { x=getint(); y=getint(); link(x,y); link(y,x); }
	ecnt=0; dfs(1,0); for(int j=1;j<=16;j++) for(int i=1;i<=n;i++) f[i][j]=f[f[i][j-1]][j-1];
	for(int i=1;i<=m;i++) {//将每个盘子的影响范围考虑成一个或者两个矩形
		x=getint(); y=getint(); z=getint(); LCA=lca(x,y);
		if(dfn[x]>dfn[y]) swap(x,y);
		if(LCA==x) {//考虑拆成两个矩形
			yfa=y; for(int j=16;j>=0;j--) if(deep[yfa]-(1<<j)>deep[x]) yfa=f[yfa][j];
			//b[++Mcnt]=(Matrix){1,dfn[yfa]-1,dfn[y],last[y],z};//直接拆分成两个矩形,显然不会有交集,所以可以分开计算贡献
			//b[++Mcnt]=(Matrix){dfn[y],last[y],last[yfa]+1,n,z};//注意只统计x<y的情况!
			if(dfn[yfa]>1) b[++Mcnt]=(Matrix){1,dfn[yfa]-1,dfn[y],last[y],z};//直接拆分成两个矩形,显然不会有交集,所以可以分开计算贡献
			if(last[yfa]<n) b[++Mcnt]=(Matrix){dfn[y],last[y],last[yfa]+1,n,z};//注意只统计x<y的情况!
		}
		else b[++Mcnt]=(Matrix){dfn[x],last[x],dfn[y],last[y],z};
	}

	for(int i=1;i<=Q;i++) {
		q[i].x=dfn[getint()]; q[i].y=dfn[getint()]; q[i].k=getint(); q[i].id=i;
		if(q[i].x>q[i].y) swap(q[i].x,q[i].y);
	}

	sort(b+1,b+Mcnt+1,cmp);
	sort(q+1,q+Q+1,cmp2);

	solve(1,Q,1,Mcnt);
	for(int i=1;i<=Q;i++) printf("%d\n",ans[i]);
}

int main()
{
    work();
    return 0;
}

  

 

转载于:https://www.cnblogs.com/ljh2000-jump/p/6427604.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值