题目
题解
我在考场上写的是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;
}
总结
坚决不能把大量时间花在调试上;
这种固定一维来操作另一维的思想;线段树可以不断更新不必使用二维线段树以减少空间
以上