洛谷·[SDOI2009]虔诚的墓主人

初见安~这里是传送门:洛谷P2154 [SDOI2009]虔诚的墓主人

题解

题意很简单:在n*m的网格上,求所有未标记点的权值和。一个未标记点的权值为:设其上下左右有a_1,a_2,a_3,a_4个标记点,则其权值为:\prod_{i=1}^4C_{a_i}^k

首先数据范围很够,K很小,我们就预处理C[i][j]了。

暴力做法枚举每个格点的话,我们可以预处理出每行每列标记点的前缀和然后O(1)得到每个点的权值。因为每个未标记点要上下左右都有标记点才有用,所以空行和空列是没用的,可以放心离散化,这样的话复杂度就是O(10^{10}),还是不行。

我们考虑把这个二维问题的O(w^2)变成O(wlogw)试试,就可以想到扫描线了。把所有关键点按x坐标为第一关键字,y坐标为第二关键字从小到大排序,从左往右依次加入每个点,每加入一个点就可以发现:这个点和下一个点的空隙中间的行的a_1,a_2是相同的,因为竖列上的点没有增减。也就是说我们可以把这些横行的C_{a_3}^kC_{a_4}^k求和后直接乘。也就是说我们可以维护竖列的情况,每加入一个点就是单点修改,求和就是区间查询,那不就是树状数组可以干的事儿嘛!【线段树也可,毕竟扫描线大多用线段树】

每次加入点后累加一次和,做一次修改,复杂度O(wlogw),完美。

好了上代码——

#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——

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值