zoukankan      html  css  js  c++  java
  • JZOJ 1495. 宝石

    题目大意

    用边长为 (k) 的正方形在平面内覆盖,求它能覆盖的最大点权和

    思路

    (60) 分:其实很容易想到按它们的横坐标先后排序,然后单调队列维护。复杂度 (O(n k log k))
    然而考试时我直接修改了队列,导致 (WA) 到了 (10) 分!改后又由于各种问题使它只 (TLE)(50)

    (100) 分:
    接下来讲解两种做法(本质上是一样的)

    一:我们圈出了横坐标差为 (k) 的一个区间,如图:

    考虑处理纵坐标
    很容易想到线段树维护纵坐标各处权值
    我们这 (t_i) 表示纵坐标为 ([i..{i+k}]) 上的权值和(不用考虑横坐标了)
    每插入一个点,它会对 (t)(i..{i-k}) 都产生贡献
    那么我们区间修改即可
    每次修改后,取全局最大值
    线段树同时维护最大值即可

    (Code)

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    
    const int N = 5e4 + 5;
    int m , n , k , ans;
    
    struct point{
    	int a , b , c;
    }a[N] , d[N];
    
    inline bool cmp(point x , point y){return x.a < y.a ? 1 : (x.a == y.a ? x.b < y.b : 0);}
    
    struct Segment_tree{
    	int tag[N << 2] , mx[N << 2];
    	void update(int k , int l , int r , int x , int y , int v)
    	{
    		if (x <= l && r <= y)
    		{
    			tag[k] += v;
    			return;
    		}
    		int mid = (l + r) >> 1;
    		if (x <= mid) update(k << 1 , l , mid , x , y , v);
    		if (y > mid) update(k << 1 | 1 , mid + 1 , r , x , y , v);
    		mx[k] = max(mx[k << 1] + tag[k << 1] , mx[k << 1 | 1] + tag[k << 1 | 1]);
    	}
    }seg;
    
    int main()
    {
    	scanf("%d%d%d" , &m , &n , &k);
    	for(register int i = 1; i <= n; i++) scanf("%d%d%d" , &a[i].a , &a[i].b , &a[i].c);
    	sort(a + 1 , a + n + 1 , cmp);
    	int h = 0 , t = 0;
    	for(register int i = 1; i <= n; i++)
    	{
    		while (a[i].a - d[h].a > k && h <= t) seg.update(1 , 1 , m , max(1 , d[h].b - k) , d[h].b , -d[h].c) , h++;
    		if (h == 0) h++;
    		d[++t] = a[i] , seg.update(1 , 1 , m , max(1 , a[i].b - k) , a[i].b , a[i].c);
    		ans = max(ans , seg.mx[1]);
    	}
    	printf("%d" , ans);
    }
    

    二:扫描线做法
    先转化成扫描线可做的模型
    我们把以一个点为左下角的矩形画出来,并给纵条边赋上边权为它的正(负)点权,表示它对这个矩形产生的贡献
    如图:

    那么在一个矩形内,含有的纵向线段(只要有一部分)权值之和就是可能的答案
    那么我们在扫描时就把纵向线段权值加入线段树中
    注意加入后它是一个区间,那么多个区间重叠的部分就是我们矩形的贡献
    于是我们维护每个矩形的贡献最大值就变成了维护最大值(一个矩形是我们能覆盖的最大的二维区间,所以只取每个矩形的贡献最大值)
    而矩形的靠右的纵向边是不能纳入本矩形的贡献,所以它要在 (X=x+k+1) 的直线上
    扫描线扫过靠右的纵向边后,靠左的纵向边就不能再产生贡献了,所以其值设为负的

    (Code)

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    typedef long long LL;
    
    const int N = 5e4 + 5;
    int m , n , k , ans;
    
    struct line{
    	int x , l , r , v;
    }l[N << 2];
    
    inline bool cmp(line x , line y){return x.x < y.x ? 1 : (x.x == y.x ? x.v < y.v : 0);}
    
    struct Segment_tree{
    	int tag[N << 2] , mx[N << 2];
    	
    	void update(int k , int l , int r , int x , int y , int v)
    	{
    		if (y < l || r < x) return;
    		if (x <= l && r <= y)
    		{
    			tag[k] += v;
    			return;
    		}
    		int mid = (l + r) >> 1;
    		if (x <= mid) update(k << 1 , l , mid , x , y , v);
    		if (y > mid) update(k << 1 | 1 , mid + 1 , r , x , y , v);
    		mx[k] = max(mx[k << 1] + tag[k << 1] , mx[k << 1 | 1] + tag[k << 1 | 1]);
    	}
    }seg;
    
    int main()
    {
    	scanf("%d%d%d" , &m , &n , &k);
    	int x , y , z;
    	for(register int i = 1; i <= n; i++) 
    	{
    		scanf("%d%d%d" , &x , &y , &z);
    		l[(i << 1) - 1] = (line){x , y , y + k , z};
    		l[i << 1] = (line){x + k + 1 , y , y + k , -z};
    	}
    	n <<= 1;
    	sort(l + 1 , l + n + 1 , cmp);
    	for(register int i = 1; i <= n; i++)
    	{
    		seg.update(1 , 1 , m , l[i].l , l[i].r , l[i].v);
    		ans = max(ans , seg.mx[1] + seg.tag[1]);
    	}
    	printf("%d" , ans);
    }
    

    其实两者的时间复杂度是几乎相同的(因为你发现两者代码也几乎相同)
    感性地理解并发现,后者速度似乎更快(常数小)
    所以我打的两个都差不多
    后者思考方式不一样
    建议两者都思考一下

  • 相关阅读:
    python中几种数据类型常用的方法
    WSGI
    从开学到初赛的一些个人总结
    CSP-S2020 浙江 游记
    CF1416D Graph and Queries
    单次期望 O(1) 的RMQ
    P3177 [HAOI2015]树上染色
    CF835F Roads in the Kingdom/P1399 [NOI2013]快餐店
    P4381 [IOI2008]Island
    P5655 基础数论函数练习题
  • 原文地址:https://www.cnblogs.com/leiyuanze/p/13476542.html
Copyright © 2011-2022 走看看