luogu2154 [SDOI2009] 虔诚的墓主人 离散化 树状数组 扫描线

题目大意

  公墓可以看成一块N×M的矩形,矩形的每个格点,要么种着一棵常青树,要么是一块还没有归属的墓地。一块墓地的虔诚度是指以这块墓地为中心的十字架的数目,一个十字架可以看成中间是墓地,墓地的正上、正下、正左、正右都有恰好k棵常青树。小W希望知道他所管理的这片公墓中所有墓地的虔诚度总和是多少。1 ≤ N, M ≤ 1,000,000,000,0 ≤ xi ≤ N,0 ≤ yi ≤ M,1 ≤ W ≤ 100,000,1 ≤ k ≤ 10。

题解

  首先,由N、M的巨大数量和相对地W的较小数量让我们想到离散化。一个点若其所在排和列都没有常青树,则这些点都是无效的。所以我们可以通过离散化将原矩形中的无效部分删去得到一个小矩形。

  然而离散化后我们不可以枚举坐标,这样时间复杂度至少是O(W^2)。所以我们就要以两个点之间的间隔上做文章。

  对于一个点$i$,定义它们上下左右方的常青树(包括它自己)个数分别为$u_i,b_i,l_i,r_i$,它对答案的贡献为$C_{u_i}^k C_{b_i}^k C_{l_i}^k C_{r_i}^k$。对于一排上相邻的两个点$a,b$,它们之间的间隔上每一个点的$C_{l_i}^k C_{r_i}^k$都相等,它们对答案的贡献是$C_{l_a}^k C_{r_a}^k \sum_{i在a,b之间}C_{u_i}^k C_{b_i}^k$。看见sigma了,是不是可以想到树状数组?当处理当前排时,树状数组的key值为列数(也就是架在这一行上),维护的是该行上每一个点的$C_{u_i}^k C_{b_i}^k$的前缀和。这样一行一行地按照列数从小到大(排一下序)扫描常青树,边查询边修改树状数组,这道题就完成了。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int MAX_NODE = 100010, MAX_CANDIDATE_VAL = MAX_NODE * 2, MAX_K = 15;
const unsigned int P = 2147483648u;
int TotRow, TotCol, TotNode, K;
int RowNodeCnt[MAX_CANDIDATE_VAL], ColNodeCnt[MAX_CANDIDATE_VAL];
unsigned int C[MAX_NODE][MAX_K];

struct Discretion
{
private:
	int OrgData[MAX_CANDIDATE_VAL], Rank[MAX_CANDIDATE_VAL];
	int N;

	int LowerBound(int l, int r, int k)
	{
		while (l < r)
		{
			int mid = (l + r) / 2;
			if (k <= OrgData[mid])
				r = mid;
			else
				l = mid + 1;
		}
		return l;
	}

public:
	Discretion(int n, int *orgData)
	{
		N = n;
		for (int i = 1; i <= N; i++)
			OrgData[i] = orgData[i];
		sort(OrgData + 1, OrgData + N + 1);
		OrgData[0] = -1;
		int curRank = 0;
		for (int i = 1; i <= N; i++)
			Rank[i] = OrgData[i] == OrgData[i - 1] ? curRank : ++curRank;
	}

	int GetRank(int val)
	{
		return Rank[LowerBound(1, N, val)];
	}

	int GetMaxRank()
	{
		return Rank[N];
	}
};

struct Node
{
	int Row, Col;

	bool operator < (const Node& a) const
	{
		return Row != a.Row ? Row < a.Row : Col < a.Col;
	}
}_nodes[MAX_NODE];

struct BIT
{
private:
	unsigned int C[MAX_CANDIDATE_VAL];
	int N;

	int Lowbit(int x)
	{
		return x & -x;
	}

public:
	BIT(int n):N(n){}

	void Update(int p, unsigned int delta, bool add)
	{
		while (p <= N)
		{
			add ? C[p] += delta : C[p] -= delta;
			p += Lowbit(p);
		}
	}

	unsigned int Query(int p)
	{
		unsigned int ans = 0;
		while (p >= 1)
		{
			ans += C[p];
			p -= Lowbit(p);
		}
		return ans;
	}
};

void Read()
{
	scanf("%d%d%d", &TotRow, &TotCol, &TotNode);
	for (int i = 1; i <= TotNode; i++)
		scanf("%d%d", &_nodes[i].Row, &_nodes[i].Col);
	scanf("%d", &K);
}

void GetC()
{
	for (int i = 0; i <= TotNode; i++)
	{
		C[i][0] = 1;
		if (i <= K)
			C[i][i] = 1;
		for (int j = 1; j <= min(i - 1, K); j++)
			C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
	}
}

void Discrete()
{
	static int a[MAX_CANDIDATE_VAL];
	for (int i = 1; i <= TotNode; i++)
	{
		a[i * 2 - 1] = _nodes[i].Row;
		a[i * 2] = _nodes[i].Col;
	}
	static Discretion g(TotNode * 2, a);
	TotRow = TotCol = g.GetMaxRank();
	for (int i = 1; i <= TotNode; i++)
	{
		_nodes[i].Row = g.GetRank(_nodes[i].Row);
		_nodes[i].Col = g.GetRank(_nodes[i].Col);
		RowNodeCnt[_nodes[i].Row]++;
		ColNodeCnt[_nodes[i].Col]++;
	}
}

int GetAns()
{
	static BIT g(TotCol);
	unsigned int ans = 0;
	sort(_nodes + 1, _nodes + TotNode + 1);
	int pass = 0;//passNodeCntInCurRow
	static int colPassCnt[MAX_CANDIDATE_VAL];
	for (int i = 1; i < TotNode; i++)
	{
		pass = _nodes[i - 1].Row == _nodes[i].Row ? pass + 1 : 1;
		if (_nodes[i + 1].Row == _nodes[i].Row)
			ans += C[pass][K] * C[RowNodeCnt[_nodes[i].Row] - pass][K] * (g.Query(_nodes[i + 1].Col - 1) - g.Query(_nodes[i].Col));
		g.Update(_nodes[i].Col, g.Query(_nodes[i].Col) - g.Query(_nodes[i].Col - 1), false);
		colPassCnt[_nodes[i].Col]++;
		g.Update(_nodes[i].Col, C[colPassCnt[_nodes[i].Col]][K] * C[ColNodeCnt[_nodes[i].Col] - colPassCnt[_nodes[i].Col]][K], true);
	}
	return (int)(ans % P);
}

int main()
{
	Read();
	GetC();
	Discrete();
	printf("%d\n", GetAns());
	return 0;
}

  

转载于:https://www.cnblogs.com/headboy2002/p/9512257.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值