JEOI-R1:棋

「JEOI-R1棋

题目前言

巨大诈骗题

题面

题目描述

现在有一个 \(n\times m\) 的棋盘,从上到下依次是 \(1\sim n\) 行,从左到右依次是 \(1\sim m\) 列,一个位于第 \(x\) 行第 \(y\) 列的位置被标记为 \((x,y)\)。共有 \(c\) 个棋子,不重叠地摆放在棋盘的某些位置上。一个位于 \((x,y)\) 的棋子可以走向 \((x-1,y-1),(x-1,y+1),(x+1,y-1),(x+1,y+1)\)(如果这些位置存在且其上没有棋子)。

现有若干询问,每次询问给定 \(x_1,y_1,x_2,y_2,p\),然后给定 \(p\) 个位置,表示一个子矩阵的左上角位置为 \((x_1,y_1)\),右下角位置为 \((x_2,y_2)\),问是否可以移动棋子(无次数限制)使得矩阵内有且仅有给定的 \(p\) 个位置上有棋子。询问之间相互独立。

为了减少程序时间复杂度的常数影响,建议使用更快的读入方式。

输入格式

第一行三个正整数 \(n,m,c\),表示棋盘的列数、行数和已有的棋子数。

接下来 \(c\) 行,一行两个整数 \(a_i,b_i\),表示有一个棋子位于 \(a_i\)\(b_i\) 列处。

接下来一行一个正整数 \(q\)

接下来 \(q\) 组询问。每一组询问第一行是五个正整数 \(x_1,y_1,x_2,y_2,p\),接下来 \(p\) 行每行两个正整数 \(c_i,d_i\),表示只希望矩阵内有棋子的位置。

输出格式

对于每个询问,如果可以移动棋子(无次数限制)使得矩阵内有且仅有给定的位置上有棋子,输出 YES,否则输出 NO

样例 #1

样例输入 #1

3 3 5
1 2
1 3
2 1
3 2
3 3
3
1 2 2 3 0
1 2 3 3 4
1 2
1 3
2 3
3 3
1 1 2 3 2
1 3
2 2

样例输出 #1

NO
YES
NO

@ 提示

【样例解释 #1】

解释以 0 代表空位,1 代表放置了棋子的位置。

初始状态:

011
100
011

对于第一个询问,可以证明 \((1,2)\) 处的棋子无法移出 \((1,2)\)\((2,3)\) 的矩阵。

对于第二个询问,考虑把 \((3,2)\) 处的棋子移到 \((2,3)\),得:

011
101
001

满足询问要求。移动方式不唯一。

对于第三个询问,可以证明 \((2,1)\) 处的棋子无法移出 \((1,1)\)\((2,3)\) 的矩阵。

【数据范围】

对于 \(25\%\) 的数据,\(n,m,q\le 10\)\(c\le 20\)

对于另外 \(25\%\) 的数据,保证 \(a_i+b_i\equiv 0 \pmod 2\)\(c_i+d_i\equiv 0 \pmod 2\)

对于另外 \(25\%\) 的数据,保证 \(n\cdot m-c\le(x_2-x_1+1)\cdot (y_2-y_1+1)-p\)

对于 \(100\%\) 的数据,\(2\le n,m\le 10^5\)\(1\le c,q\le 10^5\)\(c\le n\cdot m\)\(1\le a_i\le n\)\(1\le b_i\le m\)\(\sum p\le 2\times 10^5\)。对于每个询问,\(1\le p\le (x_2-x_1+1)\cdot (y_2-y_1+1)\)\(x_1\le c_i\le x_2\)\(y_1\le d_i\le y_2\)

【提示与说明】

提供一种较快的读入一个 int 类型非负整数的方式。调用下文中的 read(),其作用是返回输入中的一个非负整数,同时读取其后的一个字符。

int read() {
  int x(0);
  char c(getchar());
  while (c < '0' || c > '9') c = getchar();
  while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
  return x;
}

题解

巨大诈骗题

首先看着移动方式:

棋子可以走向 \((x-1,y-1),(x-1,y+1),(x+1,y-1),(x+1,y+1)\)

不难发现,更改后的\(x',y'\)满足:\(x'+y'\equiv x+y (\bmod 2)\),这启发我们分奇偶性讨论这个问题
以下称一类格为横纵坐标之和为奇的格子,零类格为横纵坐标之和为偶的格子

引理 :
一个棋子可以到达棋盘上与其同类的格子

由移动前后奇偶性不变可得必要性,现在来证明充分性。

本题的棋子移动数量不限,则可以以一种延迟撤回的思想,即由这个棋子往目标方向跳,若有撞上的则将撞上的移开,若撞上的那个棋子无路可走,则又移动撞上的那个棋子的周围棋子,如此类似递归步骤,可知这个棋子无论如何可以被移开。故充分性得证。

那么本题做法就比较明显了:先预处理出原矩阵\((1,1,n,m)\)中零一类点个数分别记为\(Q_0,Q_1\),将零一类棋子数量分别记为\(S_0,S_1\)。对于每一次输入的待求矩阵,统计出它总共的零一类点的个数为\(T_0,T_1\),并求出需要移动棋子到达的位置(对应\(c_i,d_i\))中零一类位置有\(P_0,P_1\)个,则这个问题有解的充要条件为:

\[\left\{ \begin{aligned} P_0&\le S_0 \\ P_1&\le S_1 \\ \end{aligned} \right. \]

\[\left\{ \begin{aligned} S_0-P_0\le Q_0-T_0\\ S_1-P_1\le Q_1-T_1\\ \end{aligned} \right. \]

然后我们讨论对于这些变量的求法。首先若\(nm\)为偶数,那么\(Q_0,Q_1\)就都等于\(\frac{nm}{2}\),否则\(Q_0=\left\lceil\frac{nm}{2}\right\rceil,Q_1=nm-Q_0\)(第一个出现的是\((1,1)\),属于零类点。)

而对于\(T_0,T_1\)的计算方式类似于\(Q_0,Q_1\),记\(l=x_2-x_1+1,r=y_2-y_1+1\),若\(lr\equiv 0 (\bmod 2)\)则平分,否则先将\(T_0,T_1\)全部赋值为\(\left\lfloor\frac{lr}{2}\right\rfloor\),然后根据\((x_1,y_1)\)所属哪一类点,则将所对应的\(T\)加上\(1\)。至于\(S_0,S_1,P_0,P_1\)就没得说了,非常简单。

十年OI一场空,不开longlong见祖宗

#include<iostream>
#include<cstdio>
#include<algorithm>
#define int long long
using namespace std;
int n,m,t1,t0,p,q0,q1,s1,s0,a,b,c,d;
int read() {
  int x(0);
  char c(getchar());
  while (c < '0' || c > '9') c = getchar();
  while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
  return x;
}

signed main(){
	n=read(),m=read(),c=read();
	q1=n*m/2,q0=n*m-q1;
	for(int i=1;i<=c;i++){
		int x=read(),y=read();
		if((x+y)&1)s1++;
		else s0++; 
	}
	int t=read();
	while(t--){
		int x1=read(),y1=read(),x2=read(),y2=read();
		int l=x2-x1+1,r=y2-y1+1,p0=0,p1=0;
		if((l*r)&1){
			if((x1+y1)&1){
				t0=l*r/2;
				t1=l*r-t0;
			}
			else {
				t1=l*r/2;
				t0=l*r-t1;
			}
		}
		else {
			t1=l*r/2;
			t0=l*r/2;
		}
		int q=read();
		for(int i=1;i<=q;i++){
			int x=read(),y=read();
			if((x+y)&1)p1++;
			else p0++;
		} 
		if(p0<=s0&&p1<=s1&&(s0+t0)<=(q0+p0)&&(s1+t1)<=(q1+p1))puts("YES");
		else puts("NO");
	}
}
posted @ 2022-12-03 15:11  spdarkle  阅读(39)  评论(0编辑  收藏  举报