• P6881 [JOI 2020 Final] 火事 题解


    Luogu

    Description.

    给定一个序列,初始为 \(\{a_i\}\)
    每时刻 \(\forall i\in(1,n],a_i\leftarrow\max(a_{i-1},a_i)\)
    问第 \(t\) 时刻 \(\sum_{l=1}^ra_i\)

    Solution.

    想了一年,要么就是建树然后暴跳,要么就是分段然后合并。
    最后无一例外假了,因为没有优化到本质。
    本质的优化其实是前缀和,然后平移坐标。

    首先,我们考虑每一次一段极长递减序列会向右移动,并把左边一个留下。
    然后每次极长连续段是可能合并的,而且合并的时间戳维护起来很困难。
    考虑脑补一下图大概长什么样,连续段在向右吞噬的同时左边被蚕食。
    所以应该是一些斜率为 \(1\) 的斜线和竖线组成的图形,例如样例是下图

    9 3 2 6 5
    9 9 3 6 6
    9 9 9 6 6
    9 9 9 9 6
    9 9 9 9 9
    .
    .
    .
    

    刚开始想用 set 维护极长不增段,发现每法搞。
    但是如果是斜线的话,可以考虑开另一个数据结构,支持“坐标平移”和单点修改。
    首先想到一件事,就是如果从左往右扫描答案是无法更新的,只能上下扫描。
    所以现在相当于有一些平行四边形,恰好覆盖了整个平面,问区间和。
    平行四边形可以通过差分变成三角形,然后对于一个三角形,它由一条斜边和一条竖边组成。
    然后直接开两个树状数组维护单点修改和查询前缀和的前缀和即可。
    发现在坐标平移的情况下,前缀和的前缀和不变,直接平移即可。

    Coding.

    点击查看 /dk 代码
    //是啊,你就是那只鬼了,所以被你碰到以后,就轮到我变成鬼了{{{
    #include<bits/stdc++.h>
    using namespace std;typedef long long ll;
    template<typename T>inline void read(T &x)
    {
    	x=0;char c=getchar(),f=0;
    	for(;c<48||c>57;c=getchar()) if(!(c^45)) f=1;
    	for(;c>=48&&c<=57;c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    	f?x=-x:x;
    }
    template<typename T,typename...L>inline void read(T &x,L&...l) {read(x),read(l...);}//}}}
    const int N=200005;int n,m,a[N],st[N],tp,ls[N],nx[N];ll rs[N];
    struct segm
    {
    	ll T1[400015],T2[400015];
    	inline void add(int x,int w)
    	{
    		x+=200003;int v=x;
    		for(;x<=400010;x+=x&(-x)) T1[x]+=w,T2[x]+=1ll*v*w;
    	}
    	inline ll qry(int x)
    	{
    		x+=200003;ll r1=0,r2=0;int v=x;
    		for(;x;x-=x&(-x)) r1+=T1[x],r2+=T2[x];
    		return r1*(v+1)-r2;
    	}
    	inline ll qry(int l,int r) {return qry(r)-qry(l-1);}
    	inline ll deb(int x) {x+=200003;ll r=0;for(;x;x-=x&(-x)) r+=T1[x];return r;}
    }T1,T2;vector<pair<int,int> >v1[N<<1],v2[N<<1];//1正2斜
    inline void addt(int l,int r,int v)
    {
    	T1.add(r+1,-v),v1[r-l+1].push_back(make_pair(r+1,v));
    	T2.add(l,v),v2[r-l+1].push_back(make_pair(l,-v));
    }
    struct que{int l,r,id;};vector<que>q[N];
    int main()
    {
    	read(n,m);for(int i=1;i<=n;i++) read(a[i]);
    	st[tp=1]=0;for(int i=1;i<=n;i++)
    	{
    		while(tp&&a[st[tp]]<a[i]) tp--;
    		ls[i]=st[tp],st[++tp]=i;
    	}a[st[tp=1]=n+1]=1e9+5;
    	for(int i=n;i>=1;i--)
    	{
    		while(tp&&a[st[tp]]<=a[i]) tp--;
    		nx[i]=st[tp],st[++tp]=i;
    	}
    	for(int i=1;i<=n;i++)
    	{
    		int hei=ls[i]?i-ls[i]:n+1,wei=nx[i]-i;
    		addt(i-hei+1,i+wei-1,a[i]),addt(i-hei+1,i-1,-a[i]),addt(i+1,i+wei-1,-a[i]);
    	}
    	for(int i=1,t,l,r;i<=m;i++) read(t,l,r),q[t].push_back((que){l,r,i});
    	for(int t=0;t<=n;t++)
    	{
    		for(auto x:v1[t]) T1.add(x.first,x.second);
    		for(auto x:v2[t]) T2.add(x.first,x.second);
    		//for(int i=1;i<=n;i++) printf("%lld%c",T1.deb(i)+T2.deb(i-t),i==n?'\n':' ');
    		for(auto x:q[t]) rs[x.id]=T1.qry(x.l,x.r)+T2.qry(x.l-t,x.r-t);
    	}
    	for(int i=1;i<=m;i++) printf("%lld\n",rs[i]);
    	return 0;
    }
    
  • 相关阅读:
    poj 3928 树状数组
    poj 2528 线段树+离散化
    poj 3468 成段增减
    hdu 2795 线段树(纵向)
    2016年江西理工大学C语言程序设计竞赛(高级组)
    2016年江西理工大学C语言程序设计竞赛(初级组)
    2015年江西理工大学C语言程序设计竞赛(高级组)
    江西理工大学南昌校区2016年新生赛
    2014江西理工大学C语言程序竞赛高级组
    2014江西理工大学C语言程序竞赛初级组
  • 原文地址:https://www.cnblogs.com/pealfrog/p/15229253.html
Copyright © 2020-2023  润新知