题目链接: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;
}