[SDOI2009]虔诚的墓主人

题目链接:https://www.luogu.org/problemnew/show/P2154

昨天在宿舍想了一个晚上终于弄明白这题是怎么一回事了;

我们来看样例:

  • 001000
  • 001000
  • 001000
  • 110110
  • 110101
  • 001000
  • 001000

一个很显然的暴力做法:二维前缀和+组合数,期望得分:30,实际得分:32;

但是二维前缀和显然无法得到全部的分数;

一般来说,一维前缀和单点修改时间爆炸的情况,我们考虑线段树;

那么二维前缀和时空都爆炸,我们肯定不能搞个什么二维线段树出来;

想办法消掉一维?排序二分?

扫描线!

没错就是扫描线思想,当整个图数据太大而只有一部分数据对我们有用时,常用扫描线;

我们对常青树以x为第一关键字,y为第二关键字从小到大排序;

然后从左往右扫(个人习惯);

一般来说,线段树与扫描线方向垂直,我们先确定线段树方向是垂直于x轴的;

然后我们考虑线段树维护的东西;

从样例中观察,这个“恰好”二字并不能表示为一个bool型的量作“是1非0”处理,好像是组合数!(考场上纠结了很久甚至想要打死出题人,“恰好”难道不是多了也不算吗qwq)

那么对于一个有贡献的墓地,其贡献等于四方向常青树数目对k的组合数乘积;

那么线段树维护的信息应该与组合数有关;

既然线段树随扫描线从左向右平移,那么维护的信息应该在水平方向;

于是我们就得到了楼下的式子。于是我们就得到了这个问题精妙的解决方案。

于是你动手写线段树。于是你愉快地TLE了

线段树常数很大怎么办?

对于这类问题我们考虑两个方案:
1. 对于单标记的永久化
2. 更换常数较小的数据结构

两者都能在一定程度上解决线段树自身常数过大的问题;

既然树状数组的题解已经有了,那我就来一发zkw线段树!

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

const int MAXN=(1<<17)-1;

int n,m,W,k,ans;
int C[MAXN][13];
int tree[MAXN<<1],cnto[MAXN],cntd[MAXN],cntl[MAXN],cntr[MAXN];
struct pnt{
    int x,y;
    int rex,rey;
}p[MAXN];

bool cmp1(pnt a,pnt b){
    if(a.y==b.y) return a.x<b.x;
    return a.y<b.y;
}bool cmp2(pnt a,pnt b){
    if(a.x==b.x) return a.y<b.y;
    return a.x<b.x;
}

void init(){
    scanf("%d%d%d",&n,&m,&W);
    for(int i=1;i<=W;++i){
        scanf("%d%d",&p[i].x,&p[i].y);
    }scanf("%d",&k);
    for(int i=0;i<=W;++i){
        C[i][0]=1;
        for(int j=1;j<=min(i,k);++j){
            C[i][j]=C[i-1][j]+C[i-1][j-1];
        }
    }sort(p+1,p+W+1,cmp1);
    p[1].rey=1;
    for(int i=2;i<=W;++i){
        if(p[i].y==p[i-1].y) p[i].rey=p[i-1].rey;
        else p[i].rey=p[i-1].rey+1;
    }sort(p+1,p+W+1,cmp2);
    p[1].rex=cntr[p[1].rey]=cnto[p[1].rex]=1;
    for(int i=2;i<=W;++i){
        if(p[i].x==p[i-1].x) p[i].rex=p[i-1].rex;
        else p[i].rex=p[i-1].rex+1;
        ++cntr[p[i].rey];++cnto[p[i].rex];
    }
}

void solve(){
    for(int i=1;i<=W;++i){
        ++cntl[p[i].rey];--cntr[p[i].rey];
        ++cntd[p[i].rex];--cnto[p[i].rex];
        tree[MAXN+p[i].rey]=C[cntl[p[i].rey]][k]*C[cntr[p[i].rey]][k];
        for(int j=MAXN+p[i].rey>>1;j;j>>=1) tree[j]=tree[j<<1]+tree[j<<1|1];
        if(p[i].rex==p[i-1].rex&&p[i].rey-p[i-1].rey>1){
            if(cnto[p[i].rex]+1>=k&&cntd[p[i].rex]-1>=k){
                int l=MAXN+p[i-1].rey,r=MAXN+p[i].rey;
                int sum=0;
                while(l^r^1){
                    if(~l&1) sum+=tree[l^1];
                    if(r&1) sum+=tree[r^1];
                    l>>=1;r>>=1;
                }ans+=sum*C[cnto[p[i].rex]+1][k]*C[cntd[p[i].rex]-1][k];
            }
        }
    }
}

void write(){
    printf("%d\n",ans>=0?ans:ans+2147483648ll);
}

int main(){
    freopen("religious.in","r",stdin);
    freopen("religious.out","w",stdout);
    init();
    solve();
    write();
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_39766648/article/details/79725874