zoukankan      html  css  js  c++  java
  • 【洛谷4800】[CEOI2015 Day2] 核能国度(差分细节题)

    点此看题面

    • 给定一个(W imes H)的网格图,其中有(n)个点上的核电站核泄漏了。
    • 每个核电站有两个参数(a_i,b_i),表示距离它契比雪夫距离为(d)的点将会受到(max{a_i-b_i imes d,0})单位的核辐射。
    • (q)次询问,每次求一个子矩阵中所有点受到的平均辐射量。
    • (W imes Hle2.5 imes10^6,n,qle10^5)

    影响范围不越界时的直接差分

    考虑对于一个核电站,它的贡献是一圈一圈向外逐渐减少的。

    其实可以看作先给以它为中心,边长为(2 imeslfloorfrac ba floor+1)的正方形区域都造成((a\%b)-b)单位的核辐射,然后给所有以它为中心,边长为(2 imes i+1(iin[0,lfloorfrac ab floor]))的正方形各自都造成(b)点核辐射。

    其中第一部分是会对范围内的每个格子造成同样的伤害,直接普通二维差分一下就好了。

    但第二部分就复杂许多,因此我们考虑一种对角线差分的方式

    绿色的正方形表示影响区域,黑点为中心。则我们的差分方式是给所有红点(包括黑点)打上差分标记(b),给所有蓝点打上差分标记(-b)

    此时只要做一次二维前缀和就能实现一次修改了。

    但我们显然不可能去枚举对角线上的每一个点给它打标记。

    实际上这里可以使用二阶差分优化,也就是给差分标记再差分,那么又变成单点的修改了。

    影响范围越界

    上面的东西都是在影响范围不越界时考虑的,而越界时就会有些麻烦了。

    实际上,由于我们做的是二维前缀和,如果落在网格图右边或下面显然不可能产生任何影响。

    否则,对于第一区域的标记,可以直接移到((1,1)),而第二、第三区域的标记分别可以移到对应列的第一行和对应行的第一列。

    那么只要给第一行或第一列打上二阶差分标记,做完一遍前缀和之后再归到总差分标记中就好了。

    代码:(O(HW+n+q))

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define SZ 10000000
    #define int long long
    #define max(x,y) ((x)>(y)?(x):(y))
    #define min(x,y) ((x)<(y)?(x):(y))
    using namespace std;
    int w,h,n,A[SZ+5],B[SZ+5];
    struct Array {int a[SZ+5];I int* operator [] (CI x) {return a+x*(h+2);}}S,P,Q;//用指针开数组
    class FastIO
    {
    	private:
    		#define FS 100000
    		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
    		#define pc(c) (C==E&&(clear(),0),*C++=c)
    		#define D isdigit(c=tc())
    		int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
    	public:
    		I FastIO() {A=B=FI,C=FO,E=FO+FS;}
    		Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
    		Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
    		Tp I void writeln(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);pc('
    ');}
    		I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
    }F;
    #define DS(x1,y1,x2,y2,v) (S[x1][y1]+=v,S[x1][y2+1]-=v,S[x2+1][y1]-=v,S[x2+1][y2+1]+=v)//二维差分
    I void TagS(CI x,CI y,CI a,CI b)//给影响范围打上一个相同的标记
    {
    	DS(max(x-a/b,1),max(y-a/b,1),min(x+a/b,w),min(y+a/b,h),a%b-b);
    }
    #define DT(T,l,r,v) (T[l]+=v,T[(r)+1]-=v)//一维差分
    I void WorkP(CI x1,CI y1,CI x2,CI y2,CI b)
    {
    	if(x1>x2) return;if(x2<1&&y2<1) return (void)DT(A,1,1,(x2-x1+1)*b);//完全落在第一部分
    	if(x2<1) y1>=1?DT(B,y1,y2,b):(DT(B,1,y2,b),DT(B,1,1,(1-y1)*b));//落在第二部分,判断是否经过第一部分
    	if(y2<1) x1>=1?DT(A,x1,x2,b):(DT(A,1,x2,b),DT(A,1,1,(1-x1)*b));//落在第三部分,判断是否经过第一部分
    }
    I void WorkQ1(CI y1,CI y2,CI b) {y1>=y2&&y2<=h&&DT(B,y2,min(y1,h),-b);}//落在第二部分
    I void WorkQ2(CI x1,CI x2,CI b) {x1<=x2&&x1<=w&&DT(A,x1,min(x2,w),-b);}//落在第三部分
    I void TagPQ(CI x,CI y,CI a,CI b)//给对角线打标记
    {
    	WorkP(x-a/b,y-a/b,x-min(a/b,min(x-1,y-1))-1,y-min(a/b,min(x-1,y-1))-1,b),//主对角线超出范围的左上部分
    	WorkQ1(y+1+a/b,y+1+min(a/b,min(x-1,h-y-1))+1,b),//副对角线超出范围的右上部分
    	WorkQ2(x+1+min(a/b,min(w-x-1,y-1))+1,x+1+a/b,b),//副对角线超出范围的左下部分
    	P[x-min(a/b,min(x-1,y-1))][y-min(a/b,min(x-1,y-1))]+=b,//主对角线二阶差分
    	P[x+1+min(a/b,min(w-x-1,h-y))+1][y+1+min(a/b,min(w-x,h-y-1))+1]-=b,
    	Q[x-min(a/b,min(x-1,h-y-1))][y+1+min(a/b,min(x-1,h-y-1))]-=b,//副对角线二阶差分
    	Q[x+1+min(a/b,min(w-x-1,y-1))+1][y-min(a/b,min(w-x-1,y-1))-1]+=b;
    }
    signed main()
    {
    	RI i,j,x,y,a,b;for(F.read(w,h,n),i=1;i<=n;++i)
    		F.read(x,y,a,b),TagS(x,y,a,b),TagPQ(x,y,a,b);
    	#define REP for(i=1;i<=w;++i) for(j=1;j<=h;++j)//为使代码简洁,#define循环
    	REP P[i][j]+=P[i-1][j-1],Q[i][j]+=Q[i-1][j+1];REP P[i][j]+=Q[i][j];//一遍前缀和求出差分标记并归到P
    	for(i=1;i<=w;++i) P[i][1]+=(A[i]+=A[i-1]);//第一列的差分标记
    	for(i=1;i<=h;++i) P[1][i]+=(B[i]+=B[i-1]);//第一行的差分标记
    	REP P[i][j]+=P[i-1][j]+P[i][j-1]-P[i-1][j-1];//差分标记做前缀和求出实际值
    	REP S[i][j]+=S[i-1][j]+S[i][j-1]-S[i-1][j-1];//原本打上的相同标记做前缀和求出实际值
    	REP S[i][j]+=S[i-1][j]+S[i][j-1]-S[i-1][j-1]+P[i][j];//二维前缀和方便询问
    	RI Qt,t;F.read(Qt);W(Qt--) F.read(x,y,a,b),
    		(t=S[a][b]-S[x-1][b]-S[a][y-1]+S[x-1][y-1])<0&&(t+=1ull<<63),//一个小技巧
    		F.writeln((int)(1.0L*t/((a-x+1)*(b-y+1))+0.5));//计算答案并输出
    	return F.clear(),0;
    }
    
  • 相关阅读:
    获取地址栏的key-value形式的值(包括重复的key值)形成对象--重复的变成数组形式
    vue---定时循环setInterval
    动画效果----webkit-animation-----动画背景变化transition:background-color 1s linear;
    background-clip--背景是否填充
    box-sizing盒子的大小,修改文字种类而保持字体大小不变font-size-adjust,----块级和内联display---盒子阴影box-shadow---对盒子中容纳不下的内容的显示overflow
    地图api学习第一天
    文字添加阴影
    css循环样式: nth-child(n)
    es6语法
    将中国标准时间--转换为yyyy-MM-dd 时分秒
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu4800.html
Copyright © 2011-2022 走看看