初见安~这里是传送门:洛谷P2154 [SDOI2009]虔诚的墓主人
题解
题意很简单:在n*m的网格上,求所有未标记点的权值和。一个未标记点的权值为:设其上下左右有个标记点,则其权值为:
。
首先数据范围很够,K很小,我们就预处理了。
暴力做法枚举每个格点的话,我们可以预处理出每行每列标记点的前缀和然后得到每个点的权值。因为每个未标记点要上下左右都有标记点才有用,所以空行和空列是没用的,可以放心离散化,这样的话复杂度就是
,还是不行。
我们考虑把这个二维问题的变成
试试,就可以想到扫描线了。把所有关键点按x坐标为第一关键字,y坐标为第二关键字从小到大排序,从左往右依次加入每个点,每加入一个点就可以发现:这个点和下一个点的空隙中间的行的
是相同的,因为竖列上的点没有增减。也就是说我们可以把这些横行的
求和后直接乘。也就是说我们可以维护竖列的情况,每加入一个点就是单点修改,求和就是区间查询,那不就是树状数组可以干的事儿嘛!【线段树也可,毕竟扫描线大多用线段树】
每次加入点后累加一次和,做一次修改,复杂度,完美。
好了上代码——
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<map>
#define maxn 100005
using namespace std;
typedef long long ll;
const ll mod = 2147483648;
int read() {
int x = 0, f = 1, ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
return x * f;
}
int n, m, w, k;
struct node {int x, y;} a[maxn];
bool cmp(node a, node b) {return a.x == b.x? a.y < b.y : a.x < b.x;}
vector<int> tmp; map<int, int> mp;
int cntc[maxn], cntl[maxn], L[maxn];
ll C[maxn][20], t[maxn], num[maxn];
void change(int x, ll y) {for(; x <= 100000; x += (x & -x)) t[x] += y;}//树状数组基础操作
ll ask(int x) {ll res = 0; for(; x; x -= (x & -x)) res += t[x]; return res;}
signed main() {
n = read(), m = read(); w = read();
for(int i = 1; i <= w; i++) a[i].x = read(), a[i].y = read();
k = read(); C[0][0] = 1;
for(int i = 1; i <= w; i++) {
C[i][0] = 1;
for(int j = 1; j <= min(i, k); j++) C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]);
}
for(int i = 1; i <= w; i++) tmp.push_back(a[i].x);//很丑的横行纵列离散化。
sort(tmp.begin(), tmp.end()); register int size = 0;
for(int i = 0; i < tmp.size(); i++) if(!mp[tmp[i]]) mp[tmp[i]] = ++size;
for(int i = 1; i <= w; i++) a[i].x = mp[a[i].x], cntc[a[i].x]++;
tmp.clear(); mp.clear();
for(int i = 1; i <= w; i++) tmp.push_back(a[i].y);
sort(tmp.begin(), tmp.end()); size = 0;
for(int i = 0; i < tmp.size(); i++) if(!mp[tmp[i]]) mp[tmp[i]] = ++size;
for(int i = 1; i <= w; i++) a[i].y = mp[a[i].y], cntl[a[i].y]++;
sort(a + 1, a + 1 + w, cmp);
register int tot = 0, now; a[0].x = -1;
ll ans = 0;
for(int i = 1; i <= w; i++) {
if(a[i].x != a[i - 1].x) tot = 0; tot++;//tot记录这一列有多少个点了
ll sum = 0, x = a[i].x, y = a[i].y; L[y]++;//L数组表示第y行有多少个点了
if(L[y] >= k && cntl[y] - L[y] >= k) sum = 1ll * C[L[y]][k] * C[cntl[y] - L[y]][k] % mod;//满足左右大于等于K
change(y, sum - num[y]), num[y] = sum;//其实就是修改成sum的值
if(i == w || x != a[i + 1].x || a[i + 1].y - y <= 1 || tot < k || cntc[x] - tot < k)
continue;//要计算跟下一个点之间的空行。
ans = (ans + 1ll * C[tot][k] * C[cntc[x] - tot][k] % mod * (ask(a[i + 1].y - 1) - ask(a[i].y)) % mod) % mod;
}
printf("%lld\n", ans);
return 0;
}
迎评:)
——End——