BZOJ 4558|JLOI 2016|SHOI 2016|方|容斥原理

题面有毒。。poisonous poi!poi!poi!
不要忘了斜着的正方形也是要算的。
这种题考试时还是不要盼着能A了。。
这篇咋这么多阅读量。。

题目大意

统计棋盘内格点能围成的正方形的数目,其中有些格点不能作为正方形的顶点。

题解

如果没有删除点,总的方案数显然是

i=1min{n,m}i(ni+1)(mi+1)modp

i表示有i种斜着的正方形,(n-i+1)表示以(i,k)为左上角的正方形数目,(m-i+1)表示k的数目。

接下来考虑每个被删除的点,其对上半,左半,右半,下半部分的影响类似,重复计算的就是正着的正方形的个数,即长宽的较小值。
(l,r,h) 表示一个区域,删除的点在底边界上,左边有l个坐标,右边有r个坐标。
考虑(6+6)*6的区域。
倾斜0格的有6个,1格的有5个,2格的有4个,……,5格的有1个,6格的有6个,总的是27个。
如果是(6+6)*5的区域,那么就是5,4,3,2,1,5了。
如果是(2+2)*5的区域,那么就是2,2,2,2。
z=min{l+r,h}
我们先假设高度要不大于左右侧,那么此时的答案就是 z(z+3)2
如果大于了左右侧,那么考虑减去多计算的部分,如果左右侧补全到z,那么多出来的部分即 n=zlzr ,公式即为 n(n+1)2

然后任意枚举2个点,然后就可以统计被删除2个点的、3个、4个点的正方形的个数了,注意斜的和正的都要统计。
注意一下点的顺序问题,就没啥了。

#include 
#include 
#include 
using namespace std;
typedef long long ll;
const int N = 2005;
const ll mod = 1e8 + 7;
ll n, m, k;
struct Point {
    int x, y;
    Point(int a = 0, int b = 0) : x(a), y(b) { }
    bool operator< (const Point &b) const { return x < b.x || x == b.x && y < b.y; }
    bool operator== (const Point &b) const { return x == b.x && y == b.y; }
} pt[N];
ll nonRemoved() {
    int k = min(n, m), i; ll ans = 0;
    for (i = 1; i <= k; ++i) (ans += (n - i + 1) * (m - i + 1) % mod * i) %= mod;
    return ans;
}
ll onceRemoved0(int l, int r, int h) {
    int z = min(l + r, h);
    if (z == 0) return 0;
    ll ans = z * (z + 3) / 2;
    if (z > l) ans -= (z - l) * (z - l + 1) / 2;
    if (z > r) ans -= (z - r) * (z - r + 1) / 2;
    return ans;
}
ll onceRemoved(int x, int y) {
    int t = x, b = n - x, l = y, r = m - y;
    return (onceRemoved0(t, b, l) + onceRemoved0(t, b, r) + onceRemoved0(l, r, t) + onceRemoved0(l, r, b)
            - min(l, t) - min(t, r) - min(r, b) - min(b, l)) % mod;
}
bool inMap(int x, int y) {
    return 0 <= x && x <= n && 0 <= y && y <= m;
}
void count(Point a, Point b, int &cnt2, int &cnt3, int &cnt4) {
    if (inMap(a.x, a.y) && inMap(b.x, b.y)) {
        t = mp.count(a) + mp.count(b);
        ++cnt2;
        if (t > 0) ++cnt3;
        if (t > 1) ++cnt4, ++cnt3;
    }
}
int main() {
    set mp;
    int i, j, r, t, x, y, dx, dy;
    scanf("%lld%lld%lld", &n, &m, &k);
    ll ans = nonRemoved();
    for (i = 0; i < k; ++i) {
        scanf("%d%d", &pt[i].x, &pt[i].y); mp.insert(pt[i]);
        (ans -= onceRemoved(pt[i].x, pt[i].y)) %= mod;
    }
    ll cnt2 = 0, cnt3 = 0, cnt4 = 0;
    for (i = 0; i < k; ++i) {
        Point p = pt[i];
        for (j = i + 1; j < k; ++j) {
            Point q = pt[j];
            dx = p.x - q.x, dy = p.y - q.y;
            count(Point(p.x + dy, p.y - dx), Point(q.x + dy, q.y - dx), cnt2, cnt3, cnt4);
            count(Point(p.x - dy, p.y + dx), Point(q.x - dy, q.y + dx), cnt2, cnt3, cnt4);
            if ((abs(dx) + abs(dy)) & 1) continue;
            x = dx - dy >> 1, y = dx + dy >> 1;
            count(Point(p.x - x, p.y - y), Point(q.x + x, q.y + y), cnt2, cnt3, cnt4);
        }
    }
    ans = (ans + cnt2 - cnt3 / 3 + cnt4 / 6) % mod;
    if (ans < 0) ans += mod;
    printf("%lld\n", ans);
}

4558: [JLoi2016]方

Description

上帝说,不要圆,要方,于是便有了这道题。由于我们应该方,而且最好能够尽量方,所以上帝派我们来找正方形
上帝把我们派到了一个有N行M列的方格图上,图上一共有(N+1)×(M+1)个格点,我们需要做的就是找出这些格点形
成了多少个正方形(换句话说,正方形的四个顶点都是格点)。但是这个问题对于我们来说太难了,因为点数太多
了,所以上帝删掉了这(N+1)×(M+1)中的K个点。既然点变少了,问题也就变简单了,那么这个时候这些格点组成
了多少个正方形呢?

Input

第一行三个整数 N, M, K, 代表棋盘的行数、 列数和不能选取的顶点个数。 保证 N, M >= 1, K <=(N + 1) ×
(M + 1)。约定每行的格点从上到下依次用整数 0 到 N 编号,每列的格点依次用 0到 M 编号。接下来 K 行,每
行两个整数 x,y 代表第 x 行第 y 列的格点被删掉了。保证 0 <=x <=N<=10^6, 0 <=y<=M<=10^6,K<=2*1000且不
会出现重复的格点。

Output

仅一行一个正整数, 代表正方形个数对 100000007( 10^8 + 7) 取模之后的值

Sample Input

2 2 4
1 0
1 2
0 1
2 1

Sample Output

1

你可能感兴趣的:(BZOJ,省选,计数问题)