题目大意:

给定一棵树,每一条边有$L,R$两种权值,求有多少条路径满足$\max(L)\leq\min(R)$。

解法$1-$点分治$+$二维数点

统计树上的路径应首先想到点分治,我们很显然可以搜出过从分治重心出发的每一条路径,对应着当前重心的每一棵子树存在的若干个区间$[L_i,R_i]$,若两个不同的子树内的区间产生贡献,即这两个点形成的路径符合要求,当且仅当$[L_1,R_1]\cap[L_2,R_2]\ne\emptyset$,换言之,当两个点形成的路径没有贡献,当且仅当$R_1<L_2$或$R_2<L_1$,那么考虑先离散化,按顺序处理每一棵子树时,用树状数组维护已插入的点中$R$值和$L$值。这样,枚举到当子树时,用之前枚举的所子树中点的数量,减去$L$值过大和$R$值过小的数量,就得到了这个点对最终答案的影响。

每一条符合路径的路径仅会被路径上等级最高的分治重心所统计。如果最高等级的分治重心有两个,那么他们之间一定会有一个等级更高的分治重心。

最终复杂度为$O(N\log^2N)$,所以本机跑了$1.7s$考场上拿到了$91$分$hhh$。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define LL long long
#define M 200004
using namespace std;
int read(){
int nm=0,fh=1; char cw=getchar();
for(;!isdigit(cw);cw=getchar()) if(cw=='-') fh=-fh;
for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-'0');
return nm*fh;
}
int n,m,c[M<<2][2],u[M],v[M],L[M<<1],R[M<<1],G[M],cnt;
int tmp,fs[M],nt[M<<1],to[M<<1],U[M<<1],D[M<<1];
int id[M<<1],od[M<<1],sum,rt,sz[M],mxs[M];
void add(int k,int x,int kd){for(;k<=m;k+=(k&-k)) c[k][kd]+=x;}
int qry(int k,int kd){int tt=0;for(;k;k-=(k&-k)) tt+=c[k][kd]; return tt;}
LL ans;
bool cmp(int x,int y){return (od[x]+od[y]==0)?od[x]<od[y]:abs(od[x])<abs(od[y]);}
bool vis[M];
void fdrt(int x,int last){
sz[x]=1,mxs[x]=0;
for(int i=fs[x];~i;i=nt[i]){
if(to[i]==last||vis[to[i]]) continue;
fdrt(to[i],x),sz[x]+=sz[to[i]];
mxs[x]=max(mxs[x],sz[to[i]]);
}
mxs[x]=max(mxs[x],sum-sz[x]);
if(mxs[x]<mxs[rt]) rt=x;
}
void dfs(int x,int last,int minn,int maxn){
if(minn>maxn) return; sz[x]=1;
ans+=cnt-qry(m-maxn,0)-qry(minn-1,1);
for(int i=fs[x];i!=-1;i=nt[i]){
if(vis[to[i]]||to[i]==last) continue;
dfs(to[i],x,max(minn,L[i]),min(maxn,R[i]));
sz[x]+=sz[to[i]];
}
}
void ins(int x,int last,int minn,int maxn){
if(minn>maxn) return; cnt++;
add(m-minn+1,1,0),add(maxn,1,1);
for(int i=fs[x];i!=-1;i=nt[i]){
if(vis[to[i]]||to[i]==last) continue;
ins(to[i],x,max(minn,L[i]),min(maxn,R[i]));
}
}
void del(int x,int last,int minn,int maxn){
if(minn>maxn) return;
add(m-minn+1,-1,0),add(maxn,-1,1);
for(int i=fs[x];i!=-1;i=nt[i]){
if(vis[to[i]]||to[i]==last) continue;
del(to[i],x,max(minn,L[i]),min(maxn,R[i]));
}
}
void solve(int x){
vis[x]=true,cnt=1;
for(int i=fs[x];i!=-1;i=nt[i]) if(!vis[to[i]]) dfs(to[i],x,L[i],R[i]),ins(to[i],x,L[i],R[i]);
for(int i=fs[x];i!=-1;i=nt[i]) if(!vis[to[i]]) del(to[i],x,L[i],R[i]);
for(int i=fs[x];i!=-1;i=nt[i]) if(!vis[to[i]]) sum=sz[to[i]],rt=0,fdrt(to[i],x),solve(rt);
}
int main(){
n=read(),sum=n,m=(n<<1)-2,memset(fs,-1,sizeof(fs)),mxs[0]=n+1;
for(int i=1;i<n;i++){
u[i]=read(),v[i]=read(),D[i]=read(),U[i]=read();
id[(i<<1)-1]=(i<<1)-1,id[i<<1]=(i<<1);
od[(i<<1)-1]=-D[i],od[i<<1]=U[i];
}
sort(id+1,id+m+1,cmp);
for(int i=1;i<=(n<<1);i++){
if(id[i]&1) D[(id[i]+1)>>1]=i;
else U[id[i]>>1]=i;
}
for(int i=1;i<n;++i){
nt[tmp]=fs[u[i]],fs[u[i]]=tmp,to[tmp]=v[i],L[tmp]=D[i],R[tmp++]=U[i];
nt[tmp]=fs[v[i]],fs[v[i]]=tmp,to[tmp]=u[i],L[tmp]=D[i],R[tmp++]=U[i];
}
fdrt(1,0),solve(rt),printf("%lld\n",ans); return 0;
}

解法$2-$动态树

动态树的解法非常巧妙,把每一条$(u,v,L,R)$的边看做一次$(u,v,L)$的$Link$和$(u,v,R)$的$Cut$,这样,我们先把每一条边拆成两条,按照权值对操作排序,贡献值和恰好就是每次$Link(u,v)$时$u,v$所在的两个连通块点数的乘积,每条合法的路径一定会被路径上的$\max(L)$的$Link$操作所统计。

复杂度为常规$LCT$的$O(N\log N)$

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define LL long long
#define M 200020
#define ls c[x][0]
#define rs c[x][1]
using namespace std;
int read(){
int nm=0,fh=1; char cw=getchar();
for(;!isdigit(cw);cw=getchar()) if(cw=='-') fh=-fh;
for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-'0');
return nm*fh;
}
LL ans;
int n,m,fa[M],c[M][2],sz[M],tk[M],rev[M],vm[M],S[M],top;
void pushdown(int x){if(rev[x]) swap(ls,rs),rev[ls]^=1,rev[rs]^=1,rev[x]=0;}
void pushup(int x){sz[x]=sz[ls]+sz[rs]+vm[x]+1;}
bool isroot(int x){return c[fa[x]][0]!=x&&c[fa[x]][1]!=x;}
void rotate(int x){
int tp=fa[x],dtp=fa[fa[x]],ms,ds;
if(!isroot(tp)){if(c[dtp][0]==tp) c[dtp][0]=x;else c[dtp][1]=x;}
if(c[tp][0]==x) ds=1,ms=0; else ds=0,ms=1;
fa[x]=dtp,fa[tp]=x,fa[c[x][ds]]=tp;
c[tp][ms]=c[x][ds],c[x][ds]=tp,pushup(tp),pushup(x);
}
void splay(int x){
S[top=1]=x;
for(int y=x;!isroot(y);y=fa[y]) S[++top]=fa[y];
while(top) pushdown(S[top]),top--;
while(!isroot(x)){
int tp=fa[x];
if(isroot(tp)) return rotate(x);
else if(c[c[fa[tp]][0]][0]==x) rotate(tp);
else if(c[c[fa[tp]][1]][1]==x) rotate(tp);
else rotate(x);
}
}
void access(int x){for(int y=0;x;y=x,x=fa[x]) splay(x),vm[x]+=sz[rs]-sz[y],rs=y,pushup(x);}
void chroot(int x){access(x),splay(x),rev[x]^=1;}
void cut(int x,int y){chroot(y),access(x),splay(x),ls=fa[y]=0,pushup(x);}
void link(int x,int y){chroot(x),chroot(y),ans+=(LL)sz[x]*(LL)sz[y],fa[x]=y,vm[y]+=sz[x],pushup(y);}
struct opt{
int Typ,X,Y,G; opt(){}
opt(int _Typ,int _X,int _Y,int _G){Typ=_Typ,X=_X,Y=_Y,G=_G;}
void ect(){if(Typ==0) link(X,Y); else cut(X,Y);}
}q[M<<1];
bool cmp(opt i,opt j){return i.G==j.G?i.Typ<j.Typ:i.G<j.G;}
int main(){
n=read();
for(int i=1;i<n;i++){
int u=read(),v=read(),D=read(),U=read();
m++,q[m]=opt(0,u,v,D),m++,q[m]=opt(1,u,v,U);
} sort(q+1,q+m+1,cmp);
for(int i=1;i<=n;i++) sz[i]=1;
for(int i=1;i<=m;i++) q[i].ect();
printf("%lld\n",ans);return 0;
}

股神小D的更多相关文章

  1. 股神小L 2016Vijos省选集训 day1

    股神小L (stock.c/pas/cpp)============================ 小L厌倦了算法竞赛,希望到股市里一展身手.他凭借自己还行的计算机功底和可以的智商,成功建立一个模型 ...

  2. 2016vijos 1-2 股神小L(堆)

    维护前i天的最优解,那么在后面可能会对前面几天的买卖情况进行调整 如果前面买入,买入的这个在后面一定不会卖出 如果前面卖出,卖出的这个可能会在后面变成买入,因为买这个,卖后面的会获得更多的收益 用一个 ...

  3. 股神小L

    题解 贪心 若当前手中还持有股,则一定会卖出去. 否则,考虑之前卖出的最便宜的股,若售价比当前的股高,就买下这个股,否则我们就把之前卖出的最便宜的股改为买入,这样一定会有股,然后再把这个股卖出即可. ...

  4. [2016北京集训试题14]股神小D-[LCT]

    Description Solution 将(u,v,l,r)换为(1,u,v,l)和(2,u,v,r).进行排序(第4个数为第一关键字,第1个数为第二关键字).用LCT维护联通块的合并和断开.(维护 ...

  5. 股神小D [点分治 or LCT]

    题面 思路 点分治非常$naive$,不讲了,基本思路就是记录路径最小最大值.....然后没了 重点讲一下LCT的做法(好写不卡常)(点分一堆人被卡到飞起hhhh) 首先,这个路径限制由边限制决定,而 ...

  6. 股神小L [贪心]

    题面 思路 股票题肯定是贪心或者$dp$啊 这个题比较$naive$,可以看出来你这里买股票的过程一定是能不买就不买,能卖就拣最贵的日子卖,而且时间不能倒流(废话= =||) 所以我们按照时间从前往后 ...

  7. 2016北京集训测试赛(十四)Problem B: 股神小D

    Solution 正解是一个\(\log\)的link-cut tree. 将一条边拆成两个事件, 按照事件排序, link-cut tree维护联通块大小即可. link-cut tree维护子树大 ...

  8. 2016北京集训测试赛(十四)Problem A: 股神小L

    Solution 考虑怎么卖最赚钱: 肯定是只卖不买啊(笑) 虽然说上面的想法很扯淡, 但它确实能给我们提供一种思路, 我们能不买就不买; 要买的时候就买最便宜的. 我们用一个优先队列来维护股票的价格 ...

  9. (2016北京集训十四)【xsy1556】股神小D - LCT

    题解: 题解居然是LCT……受教了 把所有区间按照端点排序,动态维护目前有重叠的区间,用LCT维护即可. 代码: #include<algorithm> #include<iostr ...

随机推荐

  1. 转载 OS js oc相互调用(JavaScriptCore) ---js调用iOS ---js里面直接调用方法

    OS js oc相互调用(JavaScriptCore)   接着上节我们讲到的iOS调用js 下来我们使用js调用iOS js调用iOS分两种情况 一,js里面直接调用方法 二,js里面通过对象调用 ...

  2. TP5.0中的小知识总结

    2017年6月26日15:01:231.input    获取输入数据 支持默认值和过滤:接收用户在前台输入的数据,可以是get方式也可以是post方式.2.ThinkPHP5.0内置了分页实现,要给 ...

  3. stm32DMA通道 ADC通道

    DMA: 1.使用DAC的时候.将转化后得到的模拟信号通过IO口输出的时候.为什么还将IO口配置能输入模式 PS:stm32手冊上定义PA4和PA5分别和DAC1通道和DAC2通道相连  : DMA1 ...

  4. 给MDI窗体加背景,解释MakeObjectInstance和CallWindowProc的用法

    工程含有1个MDI主窗口和2个子窗口.唯一需要注意的是,每个窗体都有ClientHandle,但只有当自己是MDI主窗体的情况下才有值,否则等于0. unit Unit1; interface use ...

  5. Linux安装Nginx使用负载均衡

    1.实验准备准备三台计算机 nginx1 192.168.13.121 作为nginx负载均衡器nginx2 192.168.13.24  web服务,提供一个页面        nginx3 192 ...

  6. java面向对象入门之带参方法创建

    /* Name :创建带参的方法 Power by :Stuart Date:2015.4.25 */ //创建Way类 class Way{ //Way类成员的基本变量 int add1=123; ...

  7. Crontab使用详解

    第1列分钟1-59第2列小时1-23(0表示子夜)第3列日1-31第4列月1-12第5列星期0-6(0表示星期天)第6列要运行的命令 下面是crontab的格式:分 时 日 月 星期 要运行的命令 这 ...

  8. python基础21 ------python基础之socket编程

    一.C/S架构和B/S架构的简介 略 二.osi七层模型 略 三.socket层 1.如图所示: socket层是存在于应用层和传输层直接抽象出来的一层. 2.socket层是什么? Socket是应 ...

  9. python基础12 ---函数模块2

    函数模块 一.sys函数模块详解 1.sys.argv[x] 功能:从程序外部接受参数,接收的参数个数可以是多个,在程序内部sys.argv吧这些外部参数转换成元组的形式,然后以索引x的方式在内部取出 ...

  10. pygame躲敌人的游戏

    #first.py# coding=utf- import pygame from pygame.locals import * from sys import exit from util impo ...