[SDOI2009]虔诚的墓主人

这个题是今天上午模拟赛做的,考场上代码最后时间紧写得巨丑,所以改完以后还是巨丑

80分做法

这是考场上写的,然而数学实在太渣,不知道在模数下不能做除法,组合数部分写ci了

组合数预处理

首先都知道组合数是必须要处理的,然而这个题直接处理阶乘做组合数公式是不行的,因为模数下不能做除法。然后既然都要处理出来,又不需要O(1),可以用杨辉三角,如果普通的杨辉三角,可以做到3000*3000(其实组合数的处理决定了大部分分数)

做法

首先可以看出肯定要离散化坐标,然后也需要用扫描线,然后对于每一个横坐标,枚举在这一横坐标上的从下向上第K个点和从上向下第K个点中间所有的墓地,算出它们的权值,然后相加,这个做法亲测80

92分

这12分是给组合数处理的,看题会发现K惊奇地小,而且所有组合数是从x个里面找K个,所以组合数只需要枚举1到W*1到K,这样可以处理所有情况下的组合数了

数据结构

前面暴力的做法已经说完了,如果是赛场上剩下的8分其实可以选择性放弃了,然而在刷题的时候还是尽可能优化。可以发现在同一横坐标两个点之间,上下的组合数乘积是一定的,然后我们只要能快速求出两个节点之间,所有满足条件墓地左右组合数乘积的和就可以了,这个可以用一个数据结构维护(我写的线段树大概最慢0.9s,luogu可能是为了必须用树状数组时间开到0.8),然后中间还可以有一些优化,例如如果一个纵坐标上所有点加起来都不到2*K,这个纵坐标上所有的点就不用加入数据结构(小优化线段树还是过不了最后一个点)

(后期补一下,本来以为过不去是数据结构的问题,结果建哥告诉我直接用按坐标排下序就可以了,没必要用vector,我想不至于啊不就是把每个数扔到vector里面又取出来一遍吗。然而改了一下快到飞起,vector常数杀手)

#include
#include
#include
#include
#include
#define LL long long
#define MAXN (100010)
#define MOD (2147483648)
#define ls ((rt<<1)+1)
#define rs ((rt<<1)+2)
#define mid ((l+r)>>1)
#define fr(i,s,t) for (i=s;i<=t;i++)
using namespace std;
vector  vec[MAXN];
int W,N,M,K,L[MAXN],H[MAXN],temp[MAXN];
LL Xx[MAXN],Yy[MAXN],Temp[MAXN],C[MAXN+20][13],ans;
bool f[MAXN];
struct Point{
    int x,y;
}P[MAXN];
struct Node{
	int l,r;
	LL val;
}T[MAXN*10];
LL Read(){
	LL x=0; char ch=0;
	while (ch<'0'||ch>'9') ch=getchar();
	while (ch>='0'&&ch<='9'){
		x=(x<<3)+(x<<1)+ch-'0';
		ch=getchar();
	}
	return x;
}
void InIt(){
    LL N1,M1;
    cin>>N1>>M1>>W;
    N1++; M1++;
    int i,j;
    for (i=1;i<=W;i++)
        Xx[i]=Read(),Yy[i]=Read(),Xx[i]++,Yy[i]++;
    fr(i,1,W) Temp[i]=Xx[i];
    sort(Temp+1,Temp+W+1);
    N=unique(Temp+1,Temp+W+1)-Temp-1;
    fr(i,1,W) P[i].x=lower_bound(Temp+1,Temp+N+1,Xx[i])-Temp;
    
    
    fr(i,1,W) Temp[i]=Yy[i];
    sort(Temp+1,Temp+W+1);
    M=unique(Temp+1,Temp+W+1)-Temp-1;
    fr(i,1,W) P[i].y=lower_bound(Temp+1,Temp+M+1,Yy[i])-Temp;
    
    scanf("%d",&K);
    
    for (i=1;i<=W;i++) {
        L[P[i].x]++; H[P[i].y]++;
        vec[P[i].x].push_back(P[i].y);
    }
    C[0][0]=1;
    for (i=1;i<=W;i++){
        C[i][0]=1;
        for (j=1;j<=min(i,K);j++)
            C[i][j]=C[i-1][j-1]+C[i-1][j];
        C[i][j]%=MOD;
    }
}
void build(int rt,int l,int r){
	if (l==r){
		T[rt].r=H[l]; 
		return;
	}
	build(ls,l,mid); build(rs,mid+1,r);
}
void update(int rt){
	T[rt].val=T[ls].val+T[rs].val;
}
void Add(int rt,int l,int r,int pos){
	if (l==r){
		T[rt].l++; T[rt].r--;
		if (T[rt].l>=K&&T[rt].r>=K) {
			T[rt].val=C[T[rt].l][K]*C[T[rt].r][K];
			if (T[rt].val>=MOD) T[rt].val%=MOD;
		}
		else T[rt].val=0;
		return;
	}
	if (pos<=mid) Add(ls,l,mid,pos);
	else Add(rs,mid+1,r,pos);
	update(rt);
}
int Query(int rt,int l,int r,int l1,int r1){
	if (l>=l1&&r<=r1) return T[rt].val;
	
	LL res=0;
	if (l1<=mid) res+=Query(ls,l,mid,l1,r1);
	if (r1>mid) res+=Query(rs,mid+1,r,l1,r1);
	return res;
}
int main(){
//	freopen("religious.in","r",stdin);
//	freopen("religious.out","w",stdout);
    InIt();
    int i,k,j;
    build(0,1,M);
    fr(i,1,N){
        k=0; 
        memset(temp,0,sizeof(temp));
        for (j=0;jr) {
			 	x++;//开始忘了 
				continue;
			}
            LL num=Query(0,1,M,l,r);
           	ans+=num*C[K+x][K]*C[L[i]-K-x][K];
            if (ans>=MOD) ans%=MOD;
            x++;
        }
        fr(j,1,k) 
    	  if (H[temp[j]]>=2*K)//贪心优化一下,如果该行一定不可以就不用加了 
			Add(0,1,M,temp[j]);
    }
    cout<


你可能感兴趣的:(线段树)