Codeforces 815D Karen and Cards 线段树

题意

给你n张卡,每张卡有三个权值a,b,c。定义若卡1能战胜卡2当且仅当卡1至少有两个权值严格大于卡2。问有多少张卡满足三个权值都在给定范围内且可以战胜给出的n张卡。
n,a,b,c<=500000n,a,b,c<=500000

分析

考虑枚举第一维x,对于所有第一维不小于x的卡片,则答案卡片的后两维必然都要比这些卡片的后两维大。
对于所有第一维小于x的卡片,我们把它的后两维看成平面上的一个点,那么被这个点限制的位置就是以该点为右上角原点为左下角的矩形部分。
那么现在要求的就是一个以某个点为左下角的极大矩形中有多少个点没有被限制。
直接求显然不好求,考虑转变成求有多少个点被限制了。
如果我们把第二维看成下标第三维看成元素,那么现在就是要求区间中所有不小于某个数的元素和,并资瓷区间取max。
直接上吉司机线段树就好了。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<vector>
#define MAX(x,y) x=max(x,y)
using namespace std;

typedef long long LL;

const int N=500005;
const int inf=1000000000;

int n,p,q,r,mx1[N],mx2[N];
struct data{int x,y;}a[N];
vector<int> vec[N];
struct tree{int len,mx,mn1,s1,mn2,s2,tag;LL s;}t[N*4];

void updata(int d)
{
    int l=d*2,r=d*2+1;
    t[d].s=t[l].s+t[r].s;
    t[d].mx=max(t[l].mx,t[r].mx);
    t[d].mn1=t[l].mn1;t[d].s1=t[l].s1;t[d].mn2=t[l].mn2;t[d].s2=t[l].s2;
    if (t[r].mn1<t[d].mn1) t[d].mn2=t[d].mn1,t[d].s2=t[d].s1,t[d].mn1=t[r].mn1,t[d].s1=t[r].s1;
    else if (t[r].mn1==t[d].mn1) t[d].s1+=t[r].s1;
    else if (t[r].mn1<t[d].mn2) t[d].mn2=t[r].mn1,t[d].s2=t[r].s1;
    if (t[r].mn2<t[d].mn2) t[d].mn2=t[r].mn2,t[d].s2=t[r].s2;
    else if (t[r].mn2==t[d].mn2) t[d].s2+=t[r].s2;
}

void mark(int d,int w)
{
    if (w<=t[d].mn1) return;
    t[d].s+=(LL)t[d].s1*(w-t[d].mn1);
    t[d].mn1=w;MAX(t[d].tag,w);
    MAX(t[d].mx,w);
}

void pushdown(int d)
{
    if (!t[d].tag) return;
    int w=t[d].tag;t[d].tag=0;
    mark(d*2,w);mark(d*2+1,w);
}

void build(int d,int l,int r)
{
    t[d].len=r-l+1;
    if (l==r) {t[d].s1=1;t[d].mn2=inf;return;}
    int mid=(l+r)/2;
    build(d*2,l,mid);build(d*2+1,mid+1,r);
    updata(d);
}

void ins(int d,int l,int r,int x,int y,int z)
{
    if (z<=t[d].mn1) return;
    if (l<r) pushdown(d);
    int mid=(l+r)/2;
    if (l==x&&r==y)
    {
        if (z<t[d].mn2) {mark(d,z);return;}
        ins(d*2,l,mid,x,mid,z);
        ins(d*2+1,mid+1,r,mid+1,y,z);
        updata(d);
        return;
    }
    if (x<=mid) ins(d*2,l,mid,x,min(y,mid),z);
    if (y>mid) ins(d*2+1,mid+1,r,max(x,mid+1),y,z);
    updata(d);
}

LL query(int d,int l,int r,int x,int y,int z)
{
    if (t[d].mx<z) return 0;
    if (l<r) pushdown(d);
    int mid=(l+r)/2;
    if (l==x&&r==y)
    {
        if (t[d].mn1>=z) return t[d].s-(LL)(z-1)*t[d].len;
        return query(d*2,l,mid,x,mid,z)+query(d*2+1,mid+1,r,mid+1,y,z);
    }
    LL ans=0;
    if (x<=mid) ans+=query(d*2,l,mid,x,min(y,mid),z);
    if (y>mid) ans+=query(d*2+1,mid+1,r,max(x,mid+1),y,z);
    return ans;
}

int main()
{
    scanf("%d%d%d%d",&n,&p,&q,&r);
    for (int i=1;i<=n;i++)
    {
        int x;scanf("%d%d%d",&x,&a[i].x,&a[i].y);
        vec[x].push_back(i);
        MAX(mx1[x],a[i].x);
        MAX(mx2[x],a[i].y);
    }
    for (int i=p;i>=1;i--) MAX(mx1[i],mx1[i+1]),MAX(mx2[i],mx2[i+1]);
    LL ans=0;
    build(1,1,q);
    for (int i=1;i<=p;i++)
    {
        for (int j=0;j<vec[i].size();j++)
            ins(1,1,q,1,a[vec[i][j]].x,a[vec[i][j]].y);
        int lx=mx1[i]+1,ly=mx2[i]+1;
        ans+=(LL)(q-lx+1)*(r-ly+1)-query(1,1,q,lx,q,ly);
    }
    printf("%I64d",ans);
    return 0;
}
### 线段树优化建图的实现方法与应用 线段树优化建图是一种在图论中用于处理大规模区间连边问题的技术,尤其适用于最短路、网络流等场景。其核心思想是利用线段树的结构来减少节点和边的数量,从而降低时间和空间复杂度。 #### 实现方法 在线段树优化建图中,每个点通常被分为入点(in)和出点(out)。例如,对于一个点 $ u $,将其拆分为 $ u_{\text{in}} $ 和 $ u_{\text{out}} $。接下来,构建两棵线段树:**入树**(维护入点)和**出树**(维护出点)[^2]。 - **出树**中的非根节点向其父节点连一条权值为0的有向边。 - **入树**中的非叶子节点向其左右儿子连一条权值为0的有向边。 - 对于原图中的每个点,连接一条从出点到入点的无向边,以防止一些异常情况的发生。 当需要对某个区间进行连边时,可以通过线段树的结构快速定位相关节点并建立连接。例如: - 如果是从一个点向另一个点连边,则直接连接对应的两个叶子节点。 - 如果是从一个点向一个区间连边,则将该点的出点连接到入树中对应区间的节点。 - 如果是从一个区间向一个点连边,则将出树中对应区间的节点连接到该点的入点。 - 如果是从一个区间向另一个区间连边,则引入一个虚拟节点,分别从出树中的节点连接到虚拟节点,并从虚拟节点连接到入树中的节点[^4]。 这种方法避免了传统暴力建图中 $ O(MN^2) $ 的时间复杂度,大大提升了效率。 #### 应用场景 线段树优化建图广泛应用于以下场景: 1. **最短路径问题**:如 Codeforces Round #406 (Div. 1) B. Legacy 题目中,使用线段树优化建图可以高效地处理区间连边问题,从而求解最短路径[^4]。 2. **网络流问题**:在某些网络流模型中,尤其是在涉及大量区间操作的情况下,线段树优化建图能够显著减少图的规模,提高算法效率[^2]。 3. **2-SAT问题**:在某些复杂的2-SAT问题中,线段树优化建图可以帮助更高效地处理变量之间的约束关系,例如 ARC069F Flags 问题中就使用了线段树优化建图结合二分法求解[^5]。 #### 示例代码 以下是一个简单的线段树优化建图的伪代码示例,展示如何构建出树并连接边: ```python class SegmentTreeNode: def __init__(self, left, right): self.left = left self.right = right self.left_child = None self.right_child = None self.parent = None def build_segment_tree(l, r): node = SegmentTreeNode(l, r) if l == r: return node mid = (l + r) // 2 node.left_child = build_segment_tree(l, mid) node.right_child = build_segment_tree(mid + 1, r) node.left_child.parent = node node.right_child.parent = node # 出树中非根节点向父节点连边(权值为0) add_edge(node.left_child, node, 0) add_edge(node.right_child, node, 0) return node def add_edge(u, v, weight): # 添加从u到v的有向边,权值为weight pass ``` 上述代码仅展示了出树的构建过程,实际应用中还需要构建入树,并根据具体问题添加相应的边。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值