[bzoj1227][SDOI2009]虔诚的墓主人(线段树/树状数组)

题目

传送门

题解

我在考场上写的是O(nm),找到一个空地就分别取上下左右的树的数量分别为a,b,c,d,那么这块 墓地的值就是C[a][k]*C[b][k]*C[c][k]*C[d][k];
这里写图片描述
思路是没错的,我们考虑怎么使用数据结构优化时间和空间;
首先我们把每棵树按照y为第一关键字,x为第二关键字排序。然后统计每个行和列的树的数量;
从左往右做每一列,假设我们已经找到了在同一列的两棵不相邻的树P和Q,下面求PQ间的墓地的值;对于这些墓地正上方和正下方的组合数都是比较好求的,关键在于如何get到这些墓地左边和右边的值;这就是我们要维护的;
如下图红色区域
这里写图片描述
注意维护的值的意义:表示在选中当前列的状态下,该行的SUM值;所以我们每次做完一块(一部分)墓地都需要进行修改,假设我们已经做完的点的左边的树的数量为a,右边的树的数量为b,那么修改量就是 C [a+1][k] * C [b][k] - C [a][k] * C[b+1][k];
由上述分析可知,我们需要一种数据结构,资瓷单点修改和区间查询,线段树和树状数组均可;
另外,对于坐标的离散化,递推求组合数,这里不再赘述;

代码

#include<cstdio>
 #include<cstring>
 #include<iostream>
 #include<algorithm>
 #define FOR(a,b,c) for(int a=(b);a<=(c);a++)
 using namespace std;

 typedef long long LL;
 const int maxn = 400000+10;
 const LL MOD = 2147483648LL;
 struct Node{
     int x,y;
     bool operator<(const Node& rhs) const{
         return y<rhs.y || (y==rhs.y && x<rhs.x);
     }
 }nodes[maxn];

 int read() {
     char c=getchar();
     while(!isdigit(c)) c=getchar();
     int x=0;
     while(isdigit(c)) {
         x=x*10+c-'0';
         c=getchar();
     }
     return x;
 }
 //+单点修改+区间求和
 LL sumv[4*maxn];
 int v; LL d;
 void update(int u,int L,int R) {
     int lc=u*2,rc=lc+1;
     if(L==R) {
         sumv[u]=d;
     }
     else {
         int M=L+(R-L)/2;
         if(v<=M) update(lc,L,M);
         else update(rc,M+1,R);
         sumv[u]=sumv[lc]+sumv[rc];
     }
 }
 int y1,y2;
 LL query(int u,int L,int R) {
     int lc=u*2,rc=lc+1;
     if(y1<=L && R<=y2) {
         return sumv[u];
     }
     else {
         int M=L+(R-L)/2;
         LL res=0;
         if(y1<=M) res += query(lc,L,M);
         if(M<y2)  res += query(rc,M+1,R);
         res %= MOD;
         return res;
     }
 }
 int hash[5*maxn],x[maxn],y[maxn];
 int n,m,w,k; //组合函数
 LL C[maxn][20];
 void get_C(int n) {
     C[0][0]=1;
     for(int i=1;i<=n;i++) {
         C[i][0]=C[i][i]=1;
         for(int j=1;j<=10;j++)
             C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
     }
 }

 int cx[maxn],sumx[maxn],sumy[maxn];

 int main() 
 {
     n=read(),m=read(),w=read();
     get_C(w);
     int p=1;
     FOR(i,1,w) {
         x[i]=read(),y[i]=read();
         hash[p++]=x[i],hash[p++]=y[i];
     }
     p--;
     k=read();
     //离散化坐标 
     sort(hash+1,hash+p+1);
     p=unique(hash+1,hash+p+1)-hash; p--;
    n=0;
     FOR(i,1,w) 
     {
         x[i]=lower_bound(hash+1,hash+p+1,x[i])-hash;
         y[i]=lower_bound(hash+1,hash+p+1,y[i])-hash;
         n=max(n,x[i]);//n用于线段树表示最大x下标 
         sumx[x[i]]++,sumy[y[i]]++;
         nodes[i]=(Node){
   
   x[i],y[i]};
     }
     sort(nodes+1,nodes+w+1);
     //扫描每一棵树 
     LL ans=0,cy=0;
     FOR(i,1,w) 
     {
         int r=nodes[i].x,c=nodes[i].y;
         if(i>1 && c==nodes[i-1].y) {
             y1=nodes[i-1].x+1,y2=nodes[i].x-1;
             if(y1<=y2) 
             {
             ans = (ans+C[cy][k]*C[sumy[c]-cy][k]%MOD*query(1,1,n)%MOD)%MOD;
             }
             cy++;
         }
         else cy=1;

         cx[r]++;
//          printf("%lld %lld\n",C[cx[r]][k],C[sumx[r]-cx[r]][k]);
        v=r; d=C[cx[r]][k]*C[sumx[r]-cx[r]][k]%MOD;
//         printf("%lld\n",d);
         update(1,1,n);
     }
     printf("%lld\n",ans);
     return 0;
 }

总结

坚决不能把大量时间花在调试上;
这种固定一维来操作另一维的思想;线段树可以不断更新不必使用二维线段树以减少空间
以上

猜你喜欢

转载自blog.csdn.net/A_Comme_Amour/article/details/79703637