[HNOI2015]接水果(整体二分)

其实我真的不想写这道题,然而迫于有一个技巧,不得不写一写(主要是为了把技巧放进博客)
保证一个月内不写代码超过100行的。

[HNOI2015]接水果

Description

风见幽香非常喜欢玩一个叫做 osu!的游戏,其中她最喜欢玩的模式就是接水果。
由于她已经DT FC 了The big black, 她觉得这个游戏太简单了,于是发明了一个更
加难的版本。首先有一个地图,是一棵由 n n 个顶点、n1 条边组成的树(例如图 1
给出的树包含 8 个顶点、7 条边)。这颗树上有 P P 个盘子,每个盘子实际上是一条
路径(例如图 1 中顶点 6 到顶点 8 的路径),并且每个盘子还有一个权值。第 i
盘子就是顶点 ai a i 到顶点 bi b i 的路径(由于是树,所以从 ai a i bi b i 的路径是唯一的),权值为 ci c i 。接下来依次会有 Q Q 个水果掉下来,每个水果本质上也是一条路径,第i 个水果是从顶点 ui u i 到顶点 vi v i 的路径。幽香每次需要选择一个盘子去接当前的水果:一个盘子能接住一个水果,当且仅当盘子的路径是水果的路径的子路径(例如图1中从 3到7 的路径是从1到8的路径的子路径)。这里规定:从 a a 到b的路径与从b a a 的路径是同一条路径。当然为了提高难度,对于第 i 个水果,你需要选择能接住它的所有盘子中,权值第 ki k i 小的那个盘子,每个盘子可重复使用(没有使用次数的上限:一个盘子接完一个水果后,后面还可继续接其他水果,只要它是水果路径的子路径)。幽香认为这个游戏很难,你能轻松解决给她看吗?
这里写图片描述

Input

第一行三个数 n n P Q Q ,表示树的大小和盘子的个数和水果的个数。

接下来n1 行,每行两个数 a a b,表示树上的 a a b 之间有一条边。树中顶点按1到 n n 标号。 接下来 P 行,每行三个数 a a b c c ,表示路径为 a b b 、权值为 c 的盘子,其中 0c109 0 ≤ c ≤ 10 9 a a 不等于b
接下来 Q Q 行,每行三个数 u v v k,表示路径为 u u v的水果,其中 u u 不等于v,你需要选择第 k k 小的盘子,
k 小一定存在。

Output

对于每个果子,输出一行表示选择的盘子的权值。

Sample Input
10 10 10 
1 2 
2 3 
3 4 
4 5 
5 6 
6 7 
7 8 
8 9 
9 10 
3 2 217394434 
10 7 13022269 
6 7 283254485 
6 8 333042360 
4 6 442139372 
8 3 225045590 
10 4 922205209 
10 8 808296330 
9 2 486331361 
4 9 551176338 
1 8 5 
3 8 3 
3 8 4 
1 8 3 
4 8 1 
2 3 1 
2 3 1 
2 3 1 
2 4 1 
1 4 1 
Sample Output
442139372 
333042360 
442139372 
283254485 
283254485 
217394434 
217394434 
217394434 
217394434 
217394434 

HINT

N,P,Q<=40000 N , P , Q <= 40000












解:

这道题首先需要解决的问题是如何判断水果是否出现在盘子上。而且,我们对于一个盘子,要很快地求出有多少个水果经过它。
这里搬一个结论,还是蒟蒻太菜了,根本不会。

搬运
首先得到每个节点的dfs序和以这个节点为根的子树中 dfs d f s 序最大值,分别记作 dfn[u] d f n [ u ] last[u] l a s t [ u ]

显然,A路径覆盖B路径当且仅当B路径的两个端点都在A路径上

对于每一个盘子 (u,v) ( u , v ) (假设 dfn[u]<dfn[v] d f n [ u ] < d f n [ v ] ),有以下两种情况

1. lca(u,v)!=u l c a ( u , v ) ! = u 时,如图

这里写图片描述

(虚线表示有一条路径,实线表示一条边,下同)

显然这个盘子能接到的水果 (a,b) ( a , b ) (假设 dfn[a]<dfn[b] d f n [ a ] < d f n [ b ] ,下同)满足 dfn[u]<=dfn[a]<=last[u] d f n [ u ] <= d f n [ a ] <= l a s t [ u ] && dfn[v]<=dfn[b]<=last[v] d f n [ v ] <= d f n [ b ] <= l a s t [ v ]

2.当 lca(u,v)==u l c a ( u , v ) == u 时,如图

这里写图片描述

w w u (u,v) ( u , v ) 这条路径上的儿子节点

则这个盘子能接到的水果 (a,b) ( a , b ) 满足 ((1<=dfn[a]<=dfn[w]1)||(last[w]+1<=dfn[a]<=n)) ( ( 1 <= d f n [ a ] <= d f n [ w ] − 1 ) | | ( l a s t [ w ] + 1 <= d f n [ a ] <= n ) ) && (dfn[v]<=dfn[b]<=last[v]) ( d f n [ v ] <= d f n [ b ] <= l a s t [ v ] )

我们可以将水果 (a,b) ( a , b ) 看成平面上的一个点 (dfn[a],dfn[b]) ( d f n [ a ] , d f n [ b ] ) ,将盘子看成一个或两个矩形
那么问题就转化为对于平面上的一个点,求覆盖它的第 k k 小的矩形
这是一个整体二分的经典题,用扫描线+树状数组搞定
我们先将矩形按权值从小到大排序
然后对于一个点,如果[l,mid]中能覆盖这个点的矩形数不小于k,则说明答案在[l,mid]中
否则在[mid+1,r],同时 k k <script type="math/tex" id="MathJax-Element-295">k</script>减去覆盖的矩形数

大佬说的很清楚啦~
发现整体二分似乎并不能先把矩阵拆成扫描线,要在二分中间拆成扫描线。不知道是不是姿势不对?似乎两个边界会被分到两边,对答案有问题?
我就只好把矩形二分,然后要算的时候就直接拆成扫描线。
唉,怕不是写假了,别人都只写了100行多一点。不知道是树剖长了还是整体二分长了。怕不是都长了。
反正都180行了,根本不想卡常,sort算了。-_-
code:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define lowbit(x) ((x)&(-x))
using namespace std;
int key;
struct lxy{
    int next,to;
}eg[80005];
struct lxy2{
    int xl,xr,yl,yr,k;
    bool operator < (const lxy2 &QAQ)const{
      if(key==0) return k<QAQ.k;
    }
}d[200005];
struct lxy3{
    int l,r,k,tim,ans;
    bool operator < (const lxy3 &QAQ)const{
      if(key==0) return tim<QAQ.tim;
      if(key==1) return l<QAQ.l; 
    }
}f[40005],tax[40005];
struct lxy4{
    int x,yl,yr,type;
    bool operator < (const lxy4 &QAQ)const{
      if(key==1) return x<QAQ.x;
    }
}s[200005];
int n,p,q,x,y,k,cnt,tim,kl;
int head[40005],tp[40005],fa[40005],dep[40005],size[40005],wson[40005],dfn[40005],last[40005];
bool vis[40005];
int b[40005];

void add(int op,int ed)
{
    eg[++cnt].next=head[op];
    eg[cnt].to=ed;
    head[op]=cnt;
}

void dfs1(int u,int dp)
{
    vis[u]=1;dep[u]=dp;size[u]=1;dfn[u]=++tim;
    int t=0;
    for(int i=head[u];i!=-1;i=eg[i].next)
      if(vis[eg[i].to]==0)
      {
         fa[eg[i].to]=u;
         dfs1(eg[i].to,dp+1);
         size[u]+=size[eg[i].to];
         if(size[eg[i].to]>t){
            t=size[eg[i].to];
            wson[u]=eg[i].to;
         }
      }
      vis[u]=0;
}

int dfs2(int u,int las)
{
    tp[u]=las;vis[u]=1;last[u]=dfn[u];
    if(wson[u]!=0) last[u]=max(last[u],dfs2(wson[u],las));
    for(int i=head[u];i!=-1;i=eg[i].next)
      if(vis[eg[i].to]==0&&eg[i].to!=wson[u])
         last[u]=max(last[u],dfs2(eg[i].to,eg[i].to));
    vis[u]=0;
    return last[u];
}

bool lca(int w,int v)
{
    int fro=v;
    while(tp[w]!=tp[v]&&v!=0){
        fro=tp[v],v=fa[tp[v]];
    }
    if(v==0||dep[v]<dep[w]) return true;
    if(v==w) kl=fro;
    else kl=wson[w];
    return false;
}

void modify(int u,int r){
    while(u<=n){
        b[u]+=r;
        u+=lowbit(u);
    }
}

int query(int u){
    int ret=0;
    while(u>0){
        ret+=b[u];
        u-=lowbit(u);
    }
    return ret;
}

void CDQ(int ml,int mr,int ql,int qr)
{
    if(ml==mr){
        for(int i=ql;i<=qr;i++)
          f[i].ans=d[ml].k;
        return;
    }
    int mid=(ml+mr)>>1,cnt_=0;
    for(int i=ml;i<=mid;i++){
        s[++cnt_].x=d[i].xl;s[cnt_].yl=d[i].yl;s[cnt_].yr=d[i].yr;s[cnt_].type=1;
        s[++cnt_].x=d[i].xr;s[cnt_].yl=d[i].yl;s[cnt_].yr=d[i].yr;s[cnt_].type=-1;
    }
    int t1=1,t2=ql,l=ql-1,r=qr+1;
    key=1,sort(s+1,s+cnt_+1),sort(f+ql,f+qr+1);
    while(t2<=qr){
        if(t1<=cnt_&&s[t1].x<=f[t2].l){
          modify(s[t1].yl,s[t1].type);
          modify(s[t1].yr+1,-s[t1].type);
          t1++;
        }
        else{
            int z=query(f[t2].r);
            if(z<f[t2].k) f[t2].k-=z,tax[--r]=f[t2];
            else tax[++l]=f[t2];
            t2++;
        }
    }
    for(int i=1;i<t1;i++){
        modify(s[i].yl,-s[i].type);
        modify(s[i].yr+1,s[i].type);
    }
    for(int i=ql;i<=qr;i++)
      f[i]=tax[i];
    CDQ(ml,mid,ql,l);
    CDQ(mid+1,mr,r,qr);
}

int main()
{
    memset(head,-1,sizeof(head));
    scanf("%d%d%d",&n,&p,&q);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&x,&y);
        add(x,y);add(y,x);
    }
    dfs1(1,1);dfs2(1,1);
    cnt=0;
    for(int i=1;i<=p;i++){
      scanf("%d%d%d",&x,&y,&k);
      if(dfn[x]>dfn[y]) swap(x,y);
      if(lca(x,y)){
        d[++cnt].xl=dfn[x];d[cnt].xr=last[x]+1;d[cnt].yl=dfn[y];d[cnt].yr=last[y];d[cnt].k=k;
      }
      else{
        d[++cnt].xl=1;d[cnt].xr=dfn[kl];d[cnt].yl=dfn[y];d[cnt].yr=last[y];d[cnt].k=k;
        d[++cnt].xl=dfn[y];d[cnt].xr=last[y]+1;d[cnt].yl=last[kl]+1;d[cnt].yr=n;d[cnt].k=k;
      }
    }
    for(int i=1;i<=q;i++){
      scanf("%d%d%d",&x,&y,&f[i].k);
      f[i].tim=i;
      f[i].l=min(dfn[x],dfn[y]);
      f[i].r=max(dfn[x],dfn[y]);
    }
    key=0,sort(d+1,d+1+cnt);
    CDQ(1,cnt,1,q);
    key=0,sort(f+1,f+q+1);
    for(int i=1;i<=q;i++)
      printf("%d\n",f[i].ans);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
根据引用\[1\]和引用\[2\]的描述,题目中的影魔拥有n个灵魂,每个灵魂有一个战斗力ki。对于任意一对灵魂对i,j (i<j),如果不存在ks (i<s<j)大于ki或者kj,则会为影魔提供p1的攻击力。另一种情况是,如果存在一个位置k,满足ki<c<kj或者kj<c<ki,则会为影魔提供p2的攻击力。其他情况下的灵魂对不会为影魔提供攻击力。 根据引用\[3\]的描述,我们可以从左到右进行枚举。对于情况1,当扫到r\[i\]时,更新l\[i\]的贡献。对于情况2.1,当扫到l\[i\]时,更新区间\[i+1,r\[i\]-1\]的贡献。对于情况2.2,当扫到r\[i\]时,更新区间\[l\[i\]+1,i-1\]的贡献。 因此,对于给定的区间\[l,r\],我们可以根据上述方法计算出区间内所有下标二元组i,j (l<=i<j<=r)的贡献之和。 #### 引用[.reference_title] - *1* *3* [P3722 [AH2017/HNOI2017]影魔(树状数组)](https://blog.csdn.net/li_wen_zhuo/article/details/115446022)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [洛谷3722 AH2017/HNOI2017 影魔 线段树 单调栈](https://blog.csdn.net/forever_shi/article/details/119649910)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值