【容斥】2017 ACM Arabella Collegiate Programming Contest

比赛连接

G. Snake Rana

Old Macdonald wants to build a new hen house for his hens. He buys a new rectangular area of size N by M. The night before he builds the hen house, snake Rana devises an evil plan to plant bombs in K distinct cells in the area to kill the hens and eat them for dinner later.
The morning of, Old Macdonald notices that each of the K cells, where snake Rana planted a bomb, have a marking on them. That won’t stop him though, all he must do is build the hen house in an area with no bombs.
Assume that rows are numbered from top to bottom, and columns are numbered from left to right. Old Macdonald now wants to know the number of ways he can choose sub-rectangles of top left coordinates (x1, y1) and bottom right coordinates (x2, y2) (x1 ≤ x2) (y1 ≤ y2) such that there are no bombs in the sub rectangle.

Description

一个 n * m 的矩阵包含 k 个炸弹,统计不包含炸弹的子矩阵个数。

Solution

计算子矩阵数目:
对于 n * m的矩阵,一共有 n * (n + 1) / 2 * m * (m + 1) / 2 个,也就是等差数列
∑ i = 1 n i ∗ ∑ j = 1 m j \sum_{i=1}^{n}i \ast \sum_{j=1}^{m}j i=1nij=1mj
计算在 n * m 的矩阵中,包含任意一个子矩阵的所有矩阵数目为 up * left * (n + 1 - right) * (m + 1 - down) ,其中 up、down、left、right 代表当前子矩阵的上下左右边界位置。

容斥原理:
要计算几个集合并集的大小,首先将所有单个集合的大小计算出来,然后减去所有两个集合相交的部分,再加回所有三个集合相交的部分,再减去所有四个集合相交的部分,依此类推,一直计算到所有集合相交的部分。

可以简化理解为 奇加偶减奇减偶加(具体情况具体分析)

对于本题来说,首先计算出整个矩阵的所有子矩阵数目(n、m的前缀和乘积),然后分别减去包含一个炸弹、两个炸弹……的矩阵,利用二进制枚举炸弹状态(实在是妙)和容斥原理(本题是奇减偶加)避免重复相减的情况。

Code

#pragma GCC diagnostic error "-std=c++11"
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define lowbit(x) x&(-x)
#define PII pair<int, int>;
#define mst(a, b) memset(a, b, sizeof(a))
#define FIO ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define rush() int T; scanf("%d", &T); while(T--)
using namespace std;
typedef long long LL;
//#define int LL
const int INF = 0x3f3f3f3f;
const double eps = 1e-9;
const int Mod = 1e9 + 7;
const int MaxN = 1e5 + 5;

struct node {
	 int x, y;
}boom[25];

int n, m, k;
LL ans;

void solve() {
	 for(int i = 1; i < (1 << k); i++) { // 利用二进制枚举炸弹的状态
	 	 int u = INF, d = 0, l = INF, r = 0;
	 	 int cnt = 0;
	 	  for(int j = 0; j < k; j++) { // 判断当前状态i有哪些炸弹(第j为1说明当前状态下有第j个炸弹
		  	   if(i & (1<<j)) { // 判断第j位是否为1,为1说明有炸弹
		  	   	   cnt++;
			   	    u = min(u, boom[j].y);
			   	    d = max(d, boom[j].y);
			   	    l = min(l, boom[j].x);
			   	    r = max(r, boom[j].x);
			   }
		  }
		  if(cnt) {
			  LL res = 1LL * u * l * (n+1-r) * (m+1-d);
			  if(cnt & 1) ans -= res; // 容斥原理
			  else ans += res;
		  }
	 }
}

int32_t main()
{
	int t; scanf("%d", &t);
	while(t--) {
		scanf("%d %d %d", &n, &m, &k);
		for(int i = 0; i < k; i++) scanf("%d %d", &boom[i].x, &boom[i].y);
		ans = 1LL * n * (1LL + n) / 2LL * m * (1LL + m) / 2LL;
		solve();
		printf("%lld\n", ans);
	}
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值