P2154 [SDOI2009]虔诚的墓主人

题意: 给出n,m ( n,m<=1e9 ), 表示一个矩阵 (0,0)到(n,m)有 w 棵(w<=1e5)常青树,求这个矩阵的虔诚度,虔诚度的定义如下: 对于矩阵上任意一点,正上下左右方向有恰好 k 棵(k<=10)常青树的方案数。 例如 某一点四个方向依次有 2,2,2,3此时  k = 2, 则虔诚度为 3。  保证 w 棵常青树位置各不相同,并给出w个坐标每个代表一颗常青树。答案对2,147,483,648 取模。

 解: 先离散化坐标, 先按 y ,x 为一二键值从小到大排序, 给 y 离散化,并记录每 列有多少常青树, 再按照x,y为一二键值排序,给 x离散化,记录每行有多少常青树。显然,每个空地方案数 = C(up,k)*C(down,k)*C(left,k)*C(right,k)%MOD;

接下来,对每一行而言,相邻两常青树之间的空地 左右常青树的个数是相同的,这些空地上下常青树的个数不同,用树状数组记录每列的贡献,详见代码。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll MOD=2147483648;
const int MAXN=100005;
ll num[MAXN];
ll num2[MAXN];
ll n,m,w,k;
struct N{
	ll x,y;
}point[MAXN];
bool cmp1(N p1,N p2){
	if(p1.x!=p2.x)return p1.x<p2.x;
	return p1.y<p2.y;
}         
bool cmp2(N p1,N p2){
	if(p1.y!=p2.y)return p1.y<p2.y;
	return p1.x<p2.x;
}    
ll h[MAXN],l[MAXN]; 
ll r[MAXN];
ll b[MAXN],tmp[MAXN];
void add(int x,ll v){
	while(x<MAXN){
		b[x]+=v;
		x+=(x&(-x));
	}
}
ll sum(int x){
	ll res = 0;
	while(x){
		res+=b[x];
		x-=(x&(-x));
	}
	return res;
}     
ll C[MAXN][20];
int main()
{
	ll cnt = 0;
	scanf("%lld %lld",&n,&m);
	scanf("%lld",&w);
	for(int i=1;i<=w;i++)scanf("%lld %lld",&point[i].x,&point[i].y);
	scanf("%lld",&k);
	sort(point+1,point+1+w,cmp2);
	
	for (int i = 0; i <= w; i++) C[i][0] = 1;
    for (ll i = 1; i <= w; i++) for (ll j = 1; j <= min(i, k); j++)
        C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
        
	for(int i=1;i<=w;i++){
		if(i==1 || point[i].y!=point[i-1].y)cnt++;
		tmp[i] = cnt;
	}
	for(int i=1;i<=w;i++){
		point[i].y = tmp[i];
		num2[tmp[i]]++;//y 列 常青树的个数 
	}
	sort(point+1,point+1+w,cmp1);
	cnt = 0;
	for(int i=1;i<=w;i++){
		if(i==1 || point[i].x!=point[i-1].x)cnt++;
		tmp[i] = cnt; 
	}
	for(int i=1;i<=w;i++){
		point[i].x = tmp[i];
		num[tmp[i]]++;//x 行 常青树 的个数
	}
	ll ans = 0,col;//col 列向上 的个数 
	cnt = 0;
	for(int i=1;i<=w;i++){
		if(i==1 || point[i].x!=point[i-1].x)cnt = 0;
		col = point[i].y;
		ll v = ++h[col];
		v = v>=k && num2[col] - v >=k ? C[v][k]*C[num2[col] - v][k]%MOD : 0; cnt++;
		add(col,v-r[col]); r[col] = v;
		if(i==w || cnt <k || num[point[i].x] - cnt <k || point[i].x!=point[i+1].x || point[i+1].y - point[i].y <=1)
			continue;
		ans+= ( C[cnt][k]*C[num[ point[i].x ] - cnt][k]%MOD )*(sum(point[i+1].y - 1) - sum(point[i].y) )%MOD;
	}
	printf("%lld\n",(ans+MOD)%MOD);
	return 0; 
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wym_king

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值