信息学奥赛一本通【21CSPS提高组】和【21NOIP提高组】题解

目录

2078:【21CSPS提高组】廊桥分配(airport)

【题目描述】

【输入】

【输出】

【输入样例】

【输出样例】

【提示】

【数据范围】

2079:【21CSPS提高组】括号序列(bracket)

【题目描述】

【输入】

【输出】

【输入样例】

【输出样例】

【提示】

【数据范围】

2080:【21CSPS提高组】回文(palin)

【题目描述】

【输入】

【输出】

【输入样例】

【输出样例】

【提示】

2081:【21CSPS提高组】交通规划(traffic)

【题目描述】

【输入】

【输出】

【输入样例】

【输出样例】

【提示】

2082:【21NOIP提高组】报数

【题目描述】

【输入】

【输出】

【输入样例】

【输出样例】

【提示】

2083:【21NOIP提高组】数列

【题目描述】

【输入】

【输出】

【输入样例】

【输出样例】

【提示】

2084:【21NOIP提高组】方差

【题目描述】

【输入】

【输出】

【输入样例】

【输出样例】

【提示】

2085:【21NOIP提高组】棋局

【题目描述】

【输入】

【输出】

【输入样例】

【输出样例】

【提示】


2078:【21CSPS提高组】廊桥分配(airport)


时间限制: 1000 ms         内存限制: 524288 KB
提交数: 310     通过数: 95

【题目描述】

当一架飞机抵达机场时,可以停靠在航站楼旁的廊桥,也可以停靠在位于机场边缘的远机位。乘客一般更期待停靠在廊桥,因为这样省去了坐摆渡车前往航站楼的周折。

然而,因为廊桥的数量有限,所以这样的愿望不总是能实现。

机场分为国内区和国际区,国内航班飞机只能停靠在国内区,国际航班飞机只能停靠在国际区。一部分廊桥属于国内区,其余的廊桥属于国际区。

L 市新建了一座机场,一共有 n 个廊桥。该机场决定,廊桥的使用遵循“先到先得”

的原则,即每架飞机抵达后,如果相应的区(国内/国际)还有空闲的廊桥,就停靠在廊桥,否则停靠在远机位(假设远机位的数量充足)。该机场只有一条跑道,因此不存在两架飞机同时抵达的情况。

现给定未来一段时间飞机的抵达、离开时刻,请你负责将 n 个廊桥分配给国内区和国际区,使停靠廊桥的飞机数量最多。

【输入】

输入的第一行包含 3 个正整数 n, m1, m2 分别表示廊桥的个数、国内航班飞机的数量、国际航班飞机的数量。

接下来 m1 行是国内航班的信息,第 i 行包含 2 个正整数 a1,i, b1,i,分别表示一架国内航班飞机的抵达、离开时刻。

接下来 m2 行是国际航班的信息,第 i 行包含 2 个正整数 a2,i, b2,i,分别表示一架国际航班飞机的抵达、离开时刻。

每行的多个整数由空格分隔。

【输出】

输出一个正整数,表示能够停靠廊桥的飞机数量的最大值。

【输入样例】

3 5 4
1 5
3 8
6 10
9 14
13 18
2 11
4 15
7 17
12 16

【输出样例】

7

【提示】

【样例 1 解释】

在图中,我们用抵达、离开时刻的数对来代表一架飞机,如 (1, 5) 表示时刻 1 抵达、时刻 5 离开的飞机;用  表示该飞机停靠在廊桥,用 × 表示该飞机停靠在远机位。

我们以表格中阴影部分的计算方式为例,说明该表的含义。在这一部分中,国际区有 2 个廊桥,4 架国际航班飞机依如下次序抵达:

1. 首先 (2, 11) 在时刻 2 抵达,停靠在廊桥

2. 然后 (4, 15) 在时刻 4 抵达,停靠在另一个廊桥

3. 接着 (7, 17) 在时刻 7 抵达,这时前 2 架飞机都还没离开、都还占用着廊桥,而国际区只有 2 个廊桥,所以只能停靠远机位

4. 最后 (12, 16) 在时刻 12 抵达,这时 (2,11) 这架飞机已经离开,所以有 1 个空闲的廊桥,该飞机可以停廊桥

根据表格中的计算结果,当国内区分配 2 个廊桥、国际区分配 1 个廊桥时,停靠廊桥的飞机数量最多,一共 7 架。

【样例 2 输入】

2 4 6
20 30
40 50
21 22
41 42
1 19
2 18
3 4
5 6
7 8
9 10

【样例 2 输出】

4

【样例 2 解释】

当国内区分配 2 个廊桥、国际区分配 0 个廊桥时,停靠廊桥的飞机数量最多,一共4 架,即所有的国内航班飞机都能停靠在廊桥。

需要注意的是,本题中廊桥的使用遵循“先到先得”的原则,如果国际区只有 1 个廊桥,那么将被飞机 (1, 19) 占用,而不会被 (3, 4)、(5, 6)、(7, 8)、(9, 10) 这4 架飞机先后使用。

【样例 3】

见选手目录下的 airport/airport3.in 与 airport/airport3.ans。

【数据范围】

对于 20% 的数据,1≤n≤100, 1≤m1+m2≤100。

对于 40% 的数据,1≤n≤5000, 1≤m1+m2≤5000。

对于 100% 的数据,1≤n≤100000, 1≤m1+m2≤100000。

所有 a1,i, b1,i, a2,i, b2,i 为数值不超过 108 的互不相同的正整数。

保证 ∀i∈[1,n], a1,i<b1,i,a2,i<b2,i。

本题目评测默认开启-O2

题解:

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
template<class T>inline void read(T &s){
    int f=1; s=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-') f*=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
    s*=f;
}
#define MAXN (int)(1e5+10)
#define debug cout<<"I f*** **F\n";
int n,m1,m2,in[MAXN],out[MAXN];
struct node{
	int beg,end;
	inline void Read(){read(beg),read(end);}
}a[MAXN],b[MAXN]; 
struct Node{
	int ID,end; 
	bool operator<(const Node&x)const{
		return x.end<end;
	}
};
priority_queue<Node> Q;
priority_queue<int,vector<int>,greater<int> >id;
void init(){
	while(!id.empty()) id.pop();
	while(!Q.empty()) Q.pop();
	for(int i=1;i<=n;i++) id.push(i);
}
void solve1(){
	for(int i=1;i<=m1;i++){
		while(Q.size()&&Q.top().end<a[i].beg)
			id.push(Q.top().ID),Q.pop();
		if(id.size())
			Q.push((Node){id.top(),a[i].end}),
			in[id.top()]++,id.pop();
	}
}
void solve2(){
	for(int i=1;i<=m2;i++){
		while(Q.size()&&Q.top().end<b[i].beg)
			id.push(Q.top().ID),Q.pop();
		if(id.size())
			Q.push((Node){id.top(),b[i].end}),
			out[id.top()]++,id.pop();
	}
}
bool cmp(const node&x,const node&y){return x.beg<y.beg;}
signed main(){
	read(n),read(m1),read(m2);
	for(int i=1;i<=m1;i++) a[i].Read();
	for(int i=1;i<=m2;i++) b[i].Read();
	sort(a+1,a+m1+1,cmp);sort(b+1,b+m2+1,cmp);
	int ans=-114514;
	init(),solve1(); init(),solve2();
	for(int i=1;i<=n;i++) 
		in[i]+=in[i-1],out[i]+=out[i-1];
	for(int i=0;i<=n;i++)
		ans=max(ans,in[i]+out[n-i]); 
	printf("%d",ans);
	return 0;
}

2079:【21CSPS提高组】括号序列(bracket)


时间限制: 1000 ms         内存限制: 524288 KB
提交数: 154     通过数: 54

【题目描述】

小 w 在赛场上遇到了这样一个题:一个长度为 n 且符合规范的括号序列,其有些位置已经确定了,有些位置尚未确定,求这样的括号序列一共有多少个。

身经百战的小 w 当然一眼就秒了这题,不仅如此,他还觉得一场正式比赛出这么简单的模板题也太小儿科了,于是他把这题进行了加强之后顺手扔给了小 c。

具体而言,小 w 定义“超级括号序列”是由字符 ( 、)、* 组成的字符串,并且对于某个给定的常数 k ,给出了“符合规范的超级括号序列”的定义如下:

1、() 、(S) 均是符合规范的超级括号序列,其中 S 表示任意一个仅由不超过 k 个字符 * 组成的非空字符串(以下两条规则中的 S 均为此含义);

2、如果字符串 A 和 B 均为符合规范的超级括号序列,那么字符串 AB 、ASB 均为符合规范的超级括号序列,其中 AB 表示把字符串 A 和字符串 B 拼接在一起形成的字符串;

3、如果字符串 A 为符合规范的超级括号序列,那么字符串 (A) 、(SA) 、(AS) 均为符合规范的超级括号序列。

4、所有符合规范的超级括号序列均可通过上述 3 条规则得到。

例如,若 k=3 ,则字符串 ((**()*(*))*)(***) 是符合规范的超级括号序列,但字符串 *() 、(*()*) 、((**))*) 、(****(*)) 均不是。特别地,空字符串也不被视为符合规范的超级括号序列。

现在给出一个长度为 n 的超级括号序列,其中有一些位置的字符已经确定,另外一些位置的字符尚未确定(用 ? 表示)。小 w 希望能计算出:有多少种将所有尚未确定的字符一一确定的方法,使得得到的字符串是一个符合规范的超级括号序列?

可怜的小 c 并不会做这道题,于是只好请求你来帮忙。

【输入】

第 1 行,2 个正整数 n, k 。

第 2 行,一个长度为 n 且仅由( 、) 、*、? 构成的字符串 S 。

【输出】

输出一个非负整数表示答案对 109+7 取模的结果。

【输入样例】

7 3
(*??*??

【输出样例】

5

【提示】

【样例 1 解释】

如下几种方案是符合规范的:

 (**)*()
 (**(*))
 (*(**))
 (*)**()
 (*)(**)

【样例 2 输入】

10 2
???(*??(?)

【样例 2 输出】

19

【样例 3】

见选手目录下的 bracket/bracket3.in 与 bracket/bracket3.ans。

【样例 4】

见选手目录下的 bracket/bracket4.in 与 bracket/bracket4.ans。

【数据范围】

测试点编号n ≤特殊性质
1 ∼ 315
4 ∼ 840
9 ∼ 13100
14 ∼ 15500S 串中仅含有字符?
16 ∼ 20

对于 100% 的数据,1≤k≤n≤500 。

本题目评测默认开启-O2

题解:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define MOD (int)(1e9 + 7)
#define in read()

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' or c > '9') c = getchar();
	while('0' <= c and c <= '9'){
		x = x * 10 + c - '0'; c = getchar();
	}
	return x;
}

int n = 0; int k = 0;
int f[510][510][6] = { 0 };
char s[510];

bool canMatch(int a, int b){
	return (s[a] == '(' or s[a] == '?') and (s[b] == ')' or s[b] == '?');
}

void dp(){
	for(int i = 1; i <= n; i++) f[i][i-1][0] = 1;
	for(int len = 1; len <= n; len++){
	    for(int l = 1; l <= n - len + 1; l++){
	        int r = l + len - 1;
	        if(len <= k) f[l][r][0] = f[l][r-1][0] and (s[r] == '*' or s[r] == '?');
	        if(len >= 2){
               if(canMatch(l, r)) f[l][r][1] = (f[l+1][r-1][0] + f[l+1][r-1][2] + f[l+1][r-1][3] + f[l+1][r-1][4]) % MOD;
	            for(int i = l; i <= r - 1; i++){
	                f[l][r][2] = (f[l][r][2] + f[l][i][3] * f[i+1][r][0]) % MOD;
	                f[l][r][3] = (f[l][r][3] + (f[l][i][2] + f[l][i][3]) * f[i+1][r][1]) % MOD;
	                f[l][r][4] = (f[l][r][4] + (f[l][i][4] + f[l][i][5]) * f[i+1][r][1]) % MOD;
	                f[l][r][5] = (f[l][r][5] + f[l][i][4] * f[i+1][r][0]) % MOD;
	            }
	        }
	        f[l][r][5] = (f[l][r][5] + f[l][r][0]) % MOD;
	        f[l][r][3] = (f[l][r][3] + f[l][r][1]) % MOD;
	    }
	}
}

signed main(){
    n = in; k = in;
    scanf("%s", s + 1);
    dp();
    printf("%lld\n", f[1][n][3]);
}

2080:【21CSPS提高组】回文(palin)


时间限制: 1000 ms         内存限制: 524288 KB
提交数: 327     通过数: 94

【题目描述】

给定正整数 n 和整数序列 a1, a2, , a2n,在这 2n 个数中,1, 2,... , n 分别各出现恰好 2 次。现在进行 2n 次操作,目标是创建一个长度同样为 2n 的序列 b1, b2, , b2n,初始时 b 为空序列,每次可以进行以下两种操作之一:

1、将序列 a 的开头元素加到 b 的末尾,并从 a 中移除

2、将序列 a 的末尾元素加到 b 的末尾,并从 a 中移除

我们的目的是让 b 成为一个回文数列,即令其满足对所有 1≤i≤n,有 bi = b2n+1−i。

请你判断该目的是否能达成,如果可以,请输出字典序最小的操作方案,具体在【输出格式】中说明。

【输入】

每个测试点包含多组测试数据。

输入的第一行包含一个整数 T,表示测试数据的组数。

每组测试数据的第一行包含一个正整数 n,第二行包含 2n 个用空格隔开的整数a1, a2, . . . , a2n。

【输出】

对每个测试数据输出一行答案。

如果无法生成出回文数列,输出一行 −1,否则输出一行一个长度为 2n 的、由字符L 或 R 构成的字符串(不含空格),其中 L 表示移除开头元素的操作 1,R 表示操作 2。

你需要输出所有方案对应的字符串中字典序最小的一个。

字典序的比较规则如下:长度均为 2n 的字符串 s1..2n 比 t1..2n 字典序小,当且仅当存在下标 1≤k≤2n 使得 ∀1≤i<k 有 si=ti 且 sk<tk。

【输入样例】

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

【输出样例】

LRRLLRRRRL
‐1

【提示】

【样例 1 解释】

在第一组数据中,生成的的 b 数列是 4 5 3 1 2 2 1 3 5 4,可以看出这是一个回文数列。

另一种可能的操作方案是 LRRLLRRRRR,但比答案方案的字典序要大。

【样例 2】

见选手目录下的 palin/palin2.in 与 palin/palin2.ans。

【数据范围】

令∑n 表示所有 T 组测试数据中 n 的和。

对所有测试点保证 1≤T≤100, 1≤n,∑n≤5×105。

测试点编号Tn∑n特殊性质
1 ∼ 7≤ 10≤ 10≤ 50
8 ∼ 10≤ 100≤ 20≤ 1000
11 ∼ 12≤ 100
13 ∼ 15≤ 1000≤ 25000
16 ∼ 17= 1≤ 5 × 105≤ 5 × 105
18 ∼ 20≤ 100
21 ∼ 25

特殊性质:如果我们每次删除 a 中两个相邻且相等的数,存在一种方式将序列删空(例如 a=[1,2,2,1])。

本题目评测默认开启-O2

题解:

#include <cstdio>
#define maxn 1000005
int T, n, flag;
int a[maxn], ans[maxn];
int pos[2][maxn];

void print() {
	int l = 1, r = n << 1;
	for( int i = 1;i <= ( n << 1 );i ++ )
		if( ans[i] == l ) printf( "L" ), l ++;
		else printf( "R" ), r --;
	printf( "\n" );
}

void solve( int l, int r, int L, int R ) {
	for( int i = 2;i <= n;i ++ ) {
		if( l < L ) {
			if( pos[1][a[l]] == L - 1 ) { ans[i] = l ++, ans[(n << 1 | 1) - i] = -- L; continue; }
			if( pos[1][a[l]] == R + 1 ) { ans[i] = l ++, ans[(n << 1 | 1) - i] = ++ R; continue; }
		}
		if( r > R ) {
			if( pos[0][a[r]] == L - 1 ) { ans[i] = r --, ans[(n << 1 | 1) - i] = -- L; continue; }
			if( pos[0][a[r]] == R + 1 ) { ans[i] = r --, ans[(n << 1 | 1) - i] = ++ R; continue; }
		}
		flag = 0;
		return;
	}
}

int main() {
	scanf( "%d", &T );
	while( T -- ) {
		scanf( "%d", &n );
		for( int i = 1;i <= n;i ++ ) pos[0][i] = pos[1][i] = 0;
		for( int i = 1;i <= ( n << 1 );i ++ ) {
			scanf( "%d", &a[i] );
			if( ! pos[0][a[i]] ) pos[0][a[i]] = i;
			else pos[1][a[i]] = i;
		}
		flag = 1;
		ans[1] = 1, ans[n << 1] = pos[1][a[1]];
		solve( 2, n << 1, pos[1][a[1]], pos[1][a[1]] );
		if( flag ) { print(); continue; }
		flag = 1;
		ans[1] = n << 1, ans[n << 1] = pos[0][a[n << 1]];
		solve( 1, (n << 1) - 1, pos[0][a[n << 1]], pos[0][a[n << 1]] );
		if( flag ) { print(); continue; }
		printf( "-1\n" );
	}
	return 0;
}

2081:【21CSPS提高组】交通规划(traffic)


时间限制: 3000 ms         内存限制: 524288 KB
提交数: 151     通过数: 33

【题目描述】

给定一个平面上 n 条水平直线和 m 条垂直直线,它们相交形成 n 行 m 列的网格,从上到下第 r 条水平直线和从左到右第 c 条垂直直线之间的交点称为格点 (r, c)。网格中任意两个水平或垂直相邻的格点之间的线段称为一条边,每条边有一个非负整数边权。

进行 T 次询问,每次询问形式如下:

给出 k(T 次询问的 k 可能不同)个附加点,每个附加点位于一条从网格边缘向外出发的射线上。所有从网格边缘向外出发的射线按左上-右上-右下-左下-左上的顺序依次编号为 1 到 2n+2m,如下图:

对于每次询问,不同附加点所在的射线互不相同。每个附加点和最近的格点之间的线段也称为一条边,也有非负整数边权(注意,在角上的格点有可能和两个附加点同时相连)。

给定每个附加点的颜色(黑色或者白色),请你将网格内每个格点的颜色染成黑白二者之一,并使得所有两端颜色不同的边的边权和最小。请输出这个最小的边权和。

【输入】

第一行 3 个正整数 n, m, T 分别表示水平、垂直直线的数量,以及询问次数。

接下来 n−1 行,每行 m 个非负整数。其中第 i 行的第 j 个非负整数 x1i,j 表示(i, j) 和 (i+1, j) 间的边权。

接下来 n 行,每行 m−1 个非负整数。其中第 i 行的第 j 个非负整数 x2i,j 表示(i, j) 和 (i, j+1) 间的边权。

接下来依次输入 T 组询问。第 i 组询问开头为一行一个正整数 ki 表示这次询问附加点的总数。接下来 ki 行每行三个非负整数。其中第 j 行依次为 x3i,j , pi,j , ti,j 表示第 i个附加点和相邻格点之间的边权、所在的射线编号以及附加点颜色(0 为白色,1 为黑色)。保证同一组询问内 pi,j 互不相同。

每行的多个整数由空格分隔。

【输出】

输出 T 行,第 i 行输出一个非负整数,表示第 i 次询问染色之后两端颜色不同的边权和的最小值。

【输入样例】

2 3 1
9 4 7
3 8
10 5
2
19 3 1
17 9 0

【输出样例】

12

【提示】

【样例 1 解释】

最优方案:(1,3),(1,2),(2,3) 为黑色;(1,1),(2,1),(2,2) 为白色。

【样例 2】

见选手目录下的 traffic/traffic2.in 与 traffic/traffic2.ans。

【样例 3】

见选手目录下的 traffic/traffic3.in 与 traffic/traffic3.ans。

【样例 4】

见选手目录下的 traffic/traffic4.in 与 traffic/traffic4.ans。

【样例 5】

见选手目录下的 traffic/traffic5.in 与 traffic/traffic5.ans。

【数据范围】

测试点编号n, m ≤ki ≤
1,2550
3,4,5182
6,7,850
9,101022
11,1250
13,14,15,165002
17,18,19,2050

对于所有数据,2≤n,m≤500, 1≤T≤50, 1≤ki≤min{2(n+m),50}, 1≤∑Ti=1ki≤50, 0≤x≤106

, 1≤p≤2(n+m), t∈{0,1}。

保证对于每个 i∈[1,T],pi,j 互不相同。

本题目评测默认开启-O2

题解:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
struct Extra
{
	int w,p,c;
	bool operator < (const Extra &b) const
	{
		return p<b.p;
	}
}e[60];
priority_queue<pair<long long,int>> q;
int n,m,T,k,np,tot,no,h[510][510],s[510][510],head[300010],nxt[1200010],to[1200010],val[1200010],p[510][510];
long long ans,d[300010],dis[110][110],f[110][110];
inline int read(){
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')
            f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
void add(int x,int y,int z)
{
	nxt[++tot]=head[x],head[x]=tot,to[tot]=y,val[tot]=z,nxt[++tot]=head[y],head[y]=tot,to[tot]=x,val[tot]=z;
}
int main()
{
	n=read(),m=read(),T=read();
	for(int i=0;i<=n;i++) for(int j=0;j<=m;j++){
		if(i&&j&&i<n) s[i][j]=read();
		p[i][j]=++np;
	}
	for(int i=1;i<=n;i++) for(int j=1;j<m;j++) h[i][j]=read();
	while(T--){
		tot=no=0,np=(n+1)*(m+1),ans=0x3f3f3f3f3f3f3f3f,k=read();
		for(int i=1,w,p,c;i<=k;i++){
			e[i].w=w=read(),e[i].p=p=read(),e[i].c=c=read();
			if(p<=m) s[0][p]=w;
			else if(p<=n+m) h[p-m][m]=w;
			else if(p<=m*2+n) s[n][m*2+n-p+1]=w;
			else h[m*2+n*2-p+1][0]=w;
		}
		for(int i=0;i<=n;i++) for(int j=0;j<=m;j++){
			if(j>=1) add(p[i][j-1],p[i][j],s[i][j]);
			if(i>=1) add(p[i-1][j],p[i][j],h[i][j]);
		}
		sort(e+1,e+k+1),e[k+1]=e[1];
		for(int i=1;i<=k;i++) if(e[i].c!=e[i+1].c){
			no++;
			for(int j=e[i].p;j<e[i+1].p+(i==k?2*n+2*m:0);j++){
				int J=j%(2*n+2*m);
				if(J<=m) add(p[0][J],np+no,0);
				else if(J<=n+m) add(p[J-m][m],np+no,0);
				else if(J<=m*2+n) add(p[n][m*2+n-J],np+no,0);
				else add(p[m*2+n*2-J][0],np+no,0);
			}
		}
		for(int i=1;i<no;i++){
			for(int j=1;j<=np+no;j++) d[j]=ans;
			d[np+i]=0,q.push(make_pair(-d[np+i],np+i));
			while(!q.empty()){
				int x=q.top().second,temp=q.top().first;q.pop();
				if(temp!=-d[x]) continue;
				for(int j=head[x];j;j=nxt[j]) if(d[to[j]]>d[x]+val[j]) d[to[j]]=d[x]+val[j],q.push(make_pair(-d[to[j]],to[j]));
			}
			for(int j=i+1;j<=no;j++) dis[i][j]=dis[i][j+no]=dis[j][i+no]=dis[i+no][j+no]=d[np+j];
		}
		for(int i=1;i<=2*no;i++) f[i][i]=ans,f[i][i-1]=0;
		for(int i=2;i<=no;i++) for(int j=1;j+i-1<=2*no;j++){
			int l=j,r=j+i-1;
			f[l][r]=f[l+1][r-1]+dis[l][r];
			for(int p=l+1;p<r;p++) f[l][r]=min(f[l][r],f[l][p]+f[p+1][r]);
			if(i==no) ans=min(ans,f[l][r]);
		}
		for(int i=1,p,c;i<=k;i++){
			p=e[i].p,c=e[i].c;
			if(p<=m) s[0][p]=0;
			else if(p<=n+m) h[p-m][m]=0;
			else if(p<=m*2+n) s[n][m*2+n-p+1]=0;
			else h[m*2+n*2-p+1][0]=0;
		}
		for(int i=1;i<=np+no;i++) head[i]=0;
		printf("%lld\n",ans==0x3f3f3f3f3f3f3f3f?0:ans);
	}
	return 0;
}

2082:【21NOIP提高组】报数


时间限制: 1000 ms         内存限制: 524288 KB
提交数: 366     通过数: 107

【题目描述】

报数游戏是一个广为流传的休闲小游戏。参加游戏的每个人要按一定顺序轮流报数,但如果下一个报的数是 7 的倍数,或十进制表示中含有数字 7 ,就必须跳过这个数,否则就输掉了游戏。

在一个风和日丽的下午,刚刚结束 SP C20nn 比赛的小 r 和小 z 闲得无聊玩起了这个报数游戏。但在只有两个人玩的情况下计算起来还是比较容易的,因此他们玩了很久也没分出胜负。此时小 z 灵光一闪,决定把这个游戏加强:任何一个十进制中含有数字7 的数,它的所有倍数都不能报出来!

形式化地,设 p(x) 表示 x 的十进制表示中是否含有数字 7 ,若含有则 p(x) = 1 ,否则 p(x) = 0 。则一个正整数 x 不能被报出,当且仅当存在正整数 y 和 z ,使得 x = yz且 p(y) = 1 。

例如,如果小 r 报出了 6 ,由于 7 不能报,所以小 z 下一个需要报 8 ;如果小 r 报出了 33 ,则由于 34 = 17 × 2 ,35 = 7 × 5 都不能报,小 z 下一个需要报出 36 ;如果小 r 报出了 69 ,由于 70 ∼ 79 的数都含有 7 ,小 z 下一个需要报出 80 才行。

现在小 r 的上一个数报出了 x ,小 z 想快速算出他下一个数要报多少,不过他很快就发现这个游戏可比原版的游戏难算多了,于是他需要你的帮助。当然,如果小 r 报出的 x 本身是不能报出的,你也要快速反应过来小 r 输了才行。

由于小 r 和小 z 玩了很长时间游戏,你也需要回答小 z 的很多个问题。

【输入】

第 1 行,一个正整数 T 表示小 z 询问的数量。

接下来 T 行,每行 1 个正整数 x ,表示这一次小 r 报出的数。

【输出】

共 T 行,每行一个整数,如果小 r 这一次报出的数是不能报出的,输出 −1 ,否则输出小 z 下一次报出的数是多少。

【输入样例】

4
6
33
69
300

【输出样例】

8
36
80
‐1

【提示】

【样例 1 解释】

这一组样例的前 3 次询问在题目描述中已有解释。

对于第 4 次询问,由于 300 = 75 × 4,而 75 中含有 7 ,所以小 r 直接输掉了游戏。

【样例 2 输入】

5
90
99
106
114
169

【样例 2 输出】

92
100
109
‐1
180

【数据范围】

对于 10% 的数据,T ≤ 10, x ≤ 100 。

对于 30% 的数据,T ≤ 100, x ≤ 1000 。

对于 50% 的数据,T ≤ 1000, x ≤ 10000 。

对于 70% 的数据,T ≤ 10000, x ≤ 2 × 105 。

对于 100% 的数据,T ≤ 2 × 105, x ≤ 107 。

题解:

#include<iostream>
#include<cmath>
using namespace std;
 
const int MXX=1e7+11;
int table[MXX];
bool Sv(int k){
	while(k){
		if(k%10==7)return true;
		k/=10;
	}
	return false;
}
void Init(){
	for(int i=1;i<=MXX;i++){
		if(!table[i]&&Sv(i)){
			for(int j=1;i*j<=MXX;j++){
				table[i*j]=true;
			}
		}
	}
	int last=1;
	for(int i=2;i<=MXX;i++){
		if(!table[i]){
			table[last]=i;last=i;
		}else{
			table[i]=i;
		}
	}
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	int t,x;
	Init();
	cin>>t;
	while(t--){
		cin>>x;
		if(table[x]==x){
			cout<<"-1\n";
		}else{
			cout<<table[x]<<"\n";
		}
	}
	return 0;
}

2083:【21NOIP提高组】数列


时间限制: 1000 ms         内存限制: 524288 KB
提交数: 100     通过数: 43

【题目描述】

给定整数 n,m, k,和一个长度为m+1 的正整数数组 v0, v1, · · · ,vm。

对于一个长度为 n,下标从 1 开始且每个元素均不超过 m 的非负整数序列 {ai},我们定义它的权值为 va1 × va2 × · · · × van。

当这样的序列 {ai} 满足整数 S = 2a1 + 2a2 + · · · + 2an 的二进制表示中 1 的个数不超过 k 时,我们认为 {ai} 是一个合法序列。

计算所有合法序列 {ai} 的权值和对 998244353 取模的结果。

【输入】

输入的一行是三个整数 n, m, k。

第二行 m+1 个整数,分别是 v0, v1, · · · , vm。

【输出】

仅一行一个整数,表示所有合法序列的权值和对 998244353 取模的结果。

【输入样例】

5 1 1
2 1

【输出样例】

40

【提示】

【样例解释】

由于 k=1,而且由 n≤S≤n×2m 知道 5≤S≤10,合法的 S 只有一种可能:

S=8,这要求 a 中必须有 2 个 0 和 3 个 1,于是有 (52)=10 种可能的序列,每种序列的贡献都是 v20v31=4,权值和为 10×4=40。

对所有测试点保证 1≤k≤n≤30, 0≤m≤100, 1≤vi<998244353。

测试点nkm
1 ∼ 4= 8≤ n= 9
5 ∼ 7= 30= 7
8 ∼ 10= 12
11 ∼ 13= 1= 100
14 ∼ 15= 5≤ n= 50
16= 15= 100
17 ∼ 18= 30= 30
19 ∼ 20

= 100

题解:

#include <stdio.h>
#include <string.h>
#define NN 103
#define NN2 32
#define MM 998244353
#define I64 long long
 
I64 f[2][NN2][NN2][NN2];
I64 (*f1)[NN2][NN2], (*f2)[NN2][NN2];
I64 pow_v[NN][NN2];
 
void cal_c_powv(I64 c[][NN2], int v[], int m, int n)
{
	int i, j;
	c[0][0] = 1;
	for (i=1; i<NN2; i++) {
		c[i][0] = 1;
		for (j=1; j<i; j++) c[i][j] = (c[i-1][j]+c[i-1][j-1])%MM;
		c[i][i] = 1;
	}
	
	for (i=0; i<=m; i++) {
		pow_v[i][0]=1;
		for (j=1; j<=n; j++) pow_v[i][j] = pow_v[i][j-1]*v[i]%MM;
	}
}
 
int num_one(int s)
{
	int cnt=0;
	for (; s; s/=2) cnt += s%2;
	return cnt;
}
 
int main()
{
	int N, M, K, v[NN], n, k, i, j, s;
	I64 c[NN2][NN2];
	scanf("%d%d%d", &N, &M, &K);
	for (i=0; i<=M; i++) scanf("%d", v+i);
	
	cal_c_powv(c, v, M, N);
	
	f1 = f[0];
	f2 = f[1];
	memset(f, 0, sizeof(f));
	for (n = 0; n <= N; n++)
		for (s = 0; s <= N-n; s++)
			for (k = num_one(s+n); k<=K; k++)
				f2[n][k][s] = pow_v[M][n];
 
	for (i=M-1; i>=0; i--)
	{
		I64 (*tmp)[NN2][NN2] = f1;
		f1 = f2; f2 = tmp;
		memset(f2, 0, sizeof(I64)*NN2*NN2*NN2);
		f2[0][0][0] = 1;
		for (n=0; n<=N; n++) for (k=1; k<=K; k++) for (s=0; s<=(N-n)/2; s++)
		{
			for (j=0; j<=n; j++) {
				f2[n][k][s] += pow_v[i][j]*f1[n-j][k-(s+j)%2][(s+j)/2]%MM*c[n][j]%MM;
			}
			f2[n][k][s] %= MM;
		}
	}
	printf("%lld\n", f2[N][K][0]);
	return 0;
}

2084:【21NOIP提高组】方差


时间限制: 1000 ms         内存限制: 524288 KB
提交数: 134     通过数: 53

【题目描述】

给定长度为 n 的非严格递增正整数数列 1≤a1≤a2≤⋅⋅⋅≤an。每次可以进行的操作是:任意选择一个正整数 1<i<n,将 ai 变为 ai−1+ai+1−ai。求在若干次操作之后,该数列的方差最小值是多少。请输出最小值乘以 n2 的结果。

其中方差的定义为:数列中每个数与平均值的差的平方的平均值。更形式化地说,方差的定义为 D=1n∑ni−1(ai−¯¯¯a)2,其中 ¯¯¯a=1n∑ni−1ai。

【输入】

输入的第一行包含一个正整数 n,保证 n≤104。

输入的第二行有 n 个正整数,其中第 i 个数字表示 ai 的值。数据保证 1≤a−1≤a2≤⋅⋅⋅≤an。

【输出】

输出仅一行,包含一个非负整数,表示你所求的方差的最小值的 n2 倍。

【输入样例】

4
1 2 4 6

【输出样例】

52

【提示】

【样例 1 解释】

对于 (a1,a2,a3,a4) = (1,2,4,6),第一次操作得到的数列有 (1,3,4,6) ,第二次操作得到的新的数列有 (1,3,5,6)。之后无法得到新的数列。

对于 (a1,a2,a3,a4) = (1,2,4,6),平均值为 134 ,方差为 14((1−134)2+(2−134)2+(4−134)2+(6−134)2)=5916。

对于 (a1,a2,a3,a4) = (1,3,4,6),平均值为 72,方差为 14((1−72)2+(3−72)2+(4−72)2+(6−72)2)=134。

对于 (a1,a2,a3,a4) = (1,3,5,6),平均值为 154 ,方差为 14((1−154)2+(3−154)2+(5−154)2+(6−154)2)=5916。

【数据范围】

测试点编号n ≤ai ≤
1 ∼ 3410
4 ∼ 51040
6 ∼ 81520
9 ∼ 1220300
13 ∼ 155070
16 ∼ 1810040
19 ∼ 22400600
23 ∼ 251000050

对于所有的数据,保证 n≤10000,ai≤600。

题解:

#include <bits/stdc++.h>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
	int a = 0, c = getchar(), f = 1;
	for(; !isdigit(c); c=getchar())
		if(c == '-') f = -f;
	for(; isdigit(c); c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
void writeint(unsigned x){
	if(x > 9) writeint(x/10);
	putchar(char((x%10)^48));
}
inline void getMin(llong &x,const llong &y){
	if(y < x) x = y;
}

const int MAXN = 500005;
const long long INFTY = LONG_LONG_MAX>>1;
long long dp[2][MAXN];
int a[MAXN];

int main(){
	int n = readint(), s = readint();
	for(int i=1,nxt; i!=n; ++i){
		nxt = readint();
		a[i] = nxt-s, s = nxt;
	}
	sort(a+1,a+n,less<int>()); s = 0;
	fill(dp[0]+1,dp[0]+(MAXN<<1),INFTY);
	for(int i=1,fr=0,p=0; i!=n; ++i,fr^=1){
		s += a[i]; // current endpoint
		const int mov = i*a[i]; // append in front
		fill(dp[i&1],dp[i&1]+p+mov+1,INFTY);
		llong gap = llong(n)*s*s;
		rep(j,0,p) getMin(dp[i&1][j+s],dp[fr][j]+gap);
		gap = llong(i)*n*a[i]*a[i];
		const int step = n*(a[i]<<1); // for every j
		for(int j=0; j<=p; ++j,gap+=step)
			getMin(dp[i&1][j+mov],dp[fr][j]+gap);
		p += mov; // new range of sum
	}
	llong ans = INFTY;
	const int id = (n&1)^1; // last shot
	rep(i,0,s*(n-1))
		getMin(ans,dp[id][i]-llong(i)*i);
	printf("%lld\n",ans);
	return 0;
}

2085:【21NOIP提高组】棋局


时间限制: 4000 ms         内存限制: 1048576 KB
提交数: 570     通过数: 102

【题目描述】

在输了一晚上的麻将之后,小 z 和小 c 卸掉了手机上的所有牌类游戏。不过这怎么可能阻挡得了他们上课颓废的决心呢?现在他们的目光盯在了棋类游戏上,但他们两个除了天天下飞行器以外,几乎所有棋类游戏都只懂个大概规则。

“既然我们都会玩但只能玩一点点,不如我们自己搞个缝合怪出来吧!”

于是,在他们的精心脑洞之下,一个融合了围棋、象棋与军棋的奇妙游戏诞生了……

游戏在一张长 n 行宽 m 列的网格形棋盘上进行,棋子落在网格的交叉点上。我们不妨记左上角的交叉点的坐标为 (1, 1) ,右下角的交叉点坐标为 (n, m) 。

棋子分为黑白两色,对局双方各执一方棋子。

每个棋子除了颜色以外还有等级,不妨设 coli 为棋子 i 的颜色,lvi 为棋子 i 的等级。另外,棋盘上的网格线共有 4 种状态,对于第 i 条网格线,设其状态为 opti 。

轮到每方下棋时,他可以选择棋盘上的一个己方棋子沿网格线进行移动到另一个交叉点,称为走子。形式化定义走子的过程如下:选择一个坐标序列 (x0, y0),(x1, y1), ...,(xk, yk),其中 k 是任意选定的正整数,(x0, y0) 是棋子初始的位置,(xk, yk) 是棋子最终走到的位置,需要满足:

• 对于任意 i=0,1,...,k−1 ,坐标 (xi, yi) 和 (xi+1, yi+1) 之间必须有网格线直接相连,也就是说走子必须沿着网格线走 ;

• 对于任意 i ≠ j ,必须有 (xi, yi) ≠  (xj, yj ) ,也就是说走子过程中不能经过重复位置,特别地 (x0, y0)≠ (xk, yk) ,也就是说不能原地不动(或走回原地) 。

• 对于任意 i=1,...,k−1,坐标 (xi, yi) 上必须没有棋子,也就是说走子时不能越过已有的棋子 。

• 若 (xk, yk) 上没有棋子,称为普通走子,否则称为吃子。在吃子过程中,设正在走的棋子颜色为 col1 ,等级为 lv1 ,被吃的棋子颜色为 col2 ,等级为lv2 ,则必须满足 col1 ≠ col2, lv1 ≥ lv2 ,换句话说只能吃与自己颜色不同, 且等级不高于自己等级的 棋子 。

需要注意的是,由上述定义可以得出,不允许棋子在吃子后继续向前走。

网格线的状态含义如下所述:

• 如果 opti=0 ,代表此路不通,走子时不能经过这条网格线。

• 如果 opti=1 ,代表这条网格线是一条“普通道路”,每次走子时棋子最多只能经过 1 条普通道路。

• 如果 opti=2 ,代表这条网格线是一条“直行道路”,每次走子时棋子可以经过任意条直行道路,但只能一直沿横向或一直沿纵向走,不能转弯。如沿直行道路从(1,1) 经过 (1,2) 走到 (1,3) 是可以的,但是从 (1,1) 经过 (1,2) 走到 (2,2) 不行。

• 如果 opti=3 ,代表这条网格线是一条“互通道路”,每次走子时棋子可以经过任意条互通道路,且中途可任意转弯。

同时规定在一次走子过程中,棋子经过的网格线的状态必须全部相同,比如从 (1,1)经过直行道路走到 (1,2) 再经过互通道路走到 (1,3) 是不允许的。

至于如何判断胜负等其他细节,与本题无关,故略去。

小 z 和小 c 开发出这款棋类游戏后,为了提升水平,想了一个训练的策略:一开始棋盘是空的,然后小 c 会每次往棋盘的某个空交叉点上放一枚棋子,小 z 需要快速计算出:若选择这枚新放上的棋子进行一次走子,棋盘上一共有多少个位置是能被走到的?注意,因为这只是思维训练,他们并不会真的走这枚棋子。

可怜的小 z 发现他的计算力不足以算出这个问题,只好向你求助。

【输入】

每个测试点由多组数据组成。

第一行:一个正整数 T 表示数据组数。

对于每组数据:

第一行:3 个正整数 n, m, q ,分别表示棋盘的行数、列数和游戏的轮数。

接下来 n 行,每行为一个长 m−1 的字符串,每个字符为 0、1、2、3 中的一个,第 i 行第 j 个字符表示交叉点 (i,j) 连向交叉点 (i,j+1) 的网格线状态。

接下来 n−1 行,每行为一个长 m 的字符串,每个字符为 0、1、2、3 中的一个,第 i 行第 j 个字符表示交叉点 (i,j) 连向交叉点 (i+1,j) 的网格线状态。

接下来 q 行,每行 4 个非负整数 coli,lvi,xi,yi ,表示在第 i 轮有一枚颜色为 coli ,

等级为 lvi 的棋子放在了交叉点 (xi, yi) 上。其中 coli=0 表示黑子,coli=1 表示白子。

保证之前交叉点 (xi, yi) 上没有棋子。

【输出】

对于每组数据输出 q 行,每行一个非负整数,表示第 i 枚棋子放置后能走到的交叉点数量。

【输入样例】

1
3 3 5
13
22
23
010
233
0 1 2 3
1 2 2 1
1 3 1 2
0 2 3 2
1 3 2 2

【输出样例】

4
3
3
3
2

【提示】

【样例 1 解释】

放置棋子 1 后,它能走到的位置为 (2, 1),(2, 2),(3, 2),(3, 3) 。

放置棋子 2 后,它能走到的位置为 (2, 2),(2, 3),(3, 1) 

放置棋子 3 后,它能走到的位置为 (1, 1),(1, 3),(2, 2) 

放置棋子 4 后,它能走到的位置为 (2, 2),(3, 1),(3, 3) 

放置棋子 5 后,它能走到的位置为 (2, 3),(3, 2) 

【样例 2 输入】

2
2 3 4
22
33
123
0 2 1 2
0 1 2 1
1 2 1 3
0 3 2 2
3 2 3
3
1
3
32
32
0 2 1 2
1 2 3 2
0 1 2 2

【样例 2 输出】

3
4
4
2
5
5
1

【数据范围】

测试点编号n × m ≤q ≤特殊性质
1 ∼ 210050
3 ∼ 650002000
7 ∼ 82 × 105105不存在“直行道路”与“互通道路”
9 ∼ 11不存在“互通道路”
12 ∼ 14不存在“直行道路”
15 ∼ 16lvi = i
17 ∼ 18lvi = q − i + 1
19 ∼ 212000n, m ≤ 1000
22 ∼ 25105

对于 100% 的数据,1≤T≤5 ,2≤n,m≤105 ,4≤n×m≤2×105 ,1≤q≤min{105, n×m} ,1≤lvi≤q ,1≤xi≤n ,1≤yi≤m ,coli ∈ {0, 1} 。

注:由于本题输入输出规模较大,建议使用较为快速的输入输出方式。

题解:

#include <bits/stdc++.h>
using namespace std;
const int N = 5e5;
#define x first
#define y second
#define pii pair<int,int>
int n, m, q, idlim;

int idh(int x, int y) {
	return x * (m + 2) + y;
}

pii hdi(int id) {
	return pii(id / (m + 2), id % (m + 2));
}

int idv(int x, int y) {
	return y * (n + 2) + x;
}

pii vdi(int id) {
	return pii(id % (n + 2), id / (n + 2));
}
template<class T>

struct array2d {
	T a[N];
	int n, m;
	void init(int val = 0) {
		memset(a, val, sizeof(a));
	}
	void set_size(int n, int m, int val = 0) {
		this->n = n;
		this->m = m;
		init(val);
	}
	T *operator[](int i) {
		return a + i * (m + 2);
	}
	const T *operator[](int i)const {
		return a + i * (m + 2);
	}
	T &operator[](pii p) {
		return a[p.x * (m + 2) + p.y];
	}
	const T &operator[](pii p)const {
		return a[p.x * (m + 2) + p.y];
	}
};
array2d<int> v, h, col, lv, tim;
vector<pii> piece;

struct node {
	int ch[2], sz;
};
node t[N * 30];
int cnt;

void pushup(int x) {
	t[x].sz = t[t[x].ch[0]].sz + t[t[x].ch[1]].sz;
}

void insert(int p, int &x, int l, int r) {
	if (!x)
		x = ++cnt;
	if (l == r) {
		t[x].sz = 1;
		return;
	}
	int mid = (l + r) >> 1;
	if (p <= mid)
		insert(p, t[x].ch[0], l, mid);
	else
		insert(p, t[x].ch[1], mid + 1, r);
	pushup(x);
}

void erase(int p, int &x, int l, int r) {
	if (!x)
		return;
	if (l == r) {
		t[x].sz = 0;
		return;
	}
	int mid = (l + r) >> 1;
	if (p <= mid)
		erase(p, t[x].ch[0], l, mid);
	else
		erase(p, t[x].ch[1], mid + 1, r);
	pushup(x);
}

int merge(int x, int y) {
	if (!(x && y))
		return x + y;
	t[x].ch[0] = merge(t[x].ch[0], t[y].ch[0]);
	t[x].ch[1] = merge(t[x].ch[1], t[y].ch[1]);
	if (t[x].ch[0] || t[x].ch[1])
		pushup(x);
	else
		t[x].sz |= t[y].sz;
	return x;
}

int query_rk(int v, int x, int l, int r) {
	if (!t[x].sz)
		return 0;
	if (l == r)
		return t[x].sz;
	int mid = (l + r) >> 1;
	if (v <= mid)
		return query_rk(v, t[x].ch[0], l, mid);
	else
		return t[t[x].ch[0]].sz + query_rk(v, t[x].ch[1], mid + 1, r);
}

bool exist(int v, int x, int l, int r) {
	if (!t[x].sz)
		return 0;
	if (l == r)
		return 1;
	int mid = (l + r) >> 1;
	if (v <= mid)
		return exist(v, t[x].ch[0], l, mid);
	else
		return exist(v, t[x].ch[1], mid + 1, r);
}

struct DSU_with_lr {
	int l[N], r[N], f[N];
	int _(int x) {
		return f[x] == x ? x : f[x] = _(f[x]);
	}
	void merge(int x, int y) {
		x = _(x), y = _(y);
		if (x == y)
			return;
		f[x] = y;
		l[y] = min(l[y], l[x]);
		r[y] = max(r[y], r[x]);
	}
	int getl(int id) {
		return l[_(id)];
	}
	int getr(int id) {
		return r[_(id)];
	}
	bool check(int x, int y) {
		return _(x) == _(y);
	}
};
DSU_with_lr hseg, vseg;

struct block {
	int rt0, rt1, rth, rtv;
	void merge(block &y) {
		rt0 =::merge(rt0, y.rt0);
		rt1 =::merge(rt1, y.rt1);
		rth =::merge(rth, y.rth);
		rtv =::merge(rtv, y.rtv);
	}
	int get0(int v) {
		return query_rk(v, rt0, 1, q);
	}
	void ins0(int v) {
		insert(v, rt0, 1, q);
	}
	void era0(int v) {
		erase(v, rt0, 1, q);
	}
	int get1(int v) {
		return query_rk(v, rt1, 1, q);
	}
	void ins1(int v) {
		insert(v, rt1, 1, q);
	}
	void era1(int v) {
		erase(v, rt1, 1, q);
	}
	void ins01(int col, int v) {
		col ? ins1(v) : ins0(v);
	}
	void era01(int col, int v) {
		col ? era1(v) : era0(v);
	}
	void insp(int x, int y) {
		insert(idh(x, y), rth, 1, idlim);
		insert(idv(x, y), rtv, 1, idlim);
	}
	int getsz(int col, int lv) {
		return t[rth].sz + (1 - col ? get1(lv) : get0(lv));
	}
	int geth(int x, int y1, int y2, int colx, int lvx) {
		auto check = [&](int p, int q) {
			return col[p][q] == 1 - colx && lv[p][q] <= lvx && exist(lv[p][q], 1 - colx ? rt1 : rt0, 1, ::q);
		};
		return query_rk(idh(x, y2), rth, 1, idlim) - query_rk(idh(x, y1 - 1), rth, 1, idlim)
		       + (h[x][y1 - 1] == 2 && check(x, y1 - 1)) + (h[x][y2] == 2 && check(x, y2 + 1));
	}
	int getv(int x1, int x2, int y, int colx, int lvx) {
		auto check = [&](int p, int q) {
			return col[p][q] == 1 - colx && lv[p][q] <= lvx && exist(lv[p][q], 1 - colx ? rt1 : rt0, 1, ::q);
		};
		return query_rk(idv(x2, y), rtv, 1, idlim) - query_rk(idv(x1 - 1, y), rtv, 1, idlim)
		       + (v[x1 - 1][y] == 2 && check(x1 - 1, y)) + (v[x2][y] == 2 && check(x2 + 1, y));
	}
};
template<class T>

struct DSU_array2d {
	array2d<T>a;
	array2d<pii>f;
	void init(int val = 0) {
		a.init(val);
	}
	void set_size(int n, int m, int val = 0) {
		a.set_size(n, m, val);
		f.set_size(n, m, val);
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)
				f[i][j] = {i, j};
	}
	pii _(pii x) {
		return x == f[x] ? x : f[x] = _(f[x]);
	}
	T &operator[](pii p) {
		return a[_(p)];
	}
	void merge(pii x, pii y) {
		x = _(x), y = _(y);
		if (x == y)
			return;
		a[x].merge(a[y]);
		f[y] = x;
	}
};
DSU_array2d<block> b;

int qsum = 0;
int ans[N];
int lvcnt[N];
void mian() {
	cin >> n >> m >> q;
	v.set_size(n, m);
	h.set_size(n, m);
	col.set_size(n, m, -1);
	lv.set_size(n, m);
	tim.set_size(n, m);
	piece.clear();
	memset(t, 0, sizeof(t));
	memset(&hseg, 0, sizeof(hseg));
	memset(&vseg, 0, sizeof(vseg));
	memset(lvcnt, 0, sizeof(lvcnt));
	b.set_size(n, m);
	for (int i = 0; i < N; i++)
		hseg.l[i] = hseg.r[i] = hseg.f[i] = i;
	for (int i = 0; i < N; i++)
		vseg.l[i] = vseg.r[i] = vseg.f[i] = i;
	idlim = (n + 3) * (m + 3);

	cnt = 1;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j < m; j++) {
			char c;
			cin >> c;
			h[i][j] = c - '0';
		}
	for (int i = 1; i < n; i++)
		for (int j = 1; j <= m; j++) {
			char c;
			cin >> c;
			v[i][j] = c - '0';
		}
	for (int i = 1; i <= q; i++) {
		int colx, lvx, x, y;
		cin >> colx >> lvx >> x >> y;
		col[x][y] = colx;
		lv[x][y] = lvx;
		++lvcnt[lvx];
		tim[x][y] = i;
		piece.push_back(pii(x, y));
	}
	for (int i = 1; i <= q; i++)
		lvcnt[i] += lvcnt[i - 1];
	for (int i = q - 1; i >= 0; --i)
		lv[piece[i].x][piece[i].y] = lvcnt[lv[piece[i].x][piece[i].y]]--;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j < m; j++) {
			if (h[i][j] == 2 && !lv[i][j] && !lv[i][j + 1])
				hseg.merge(idh(i, j), idh(i, j + 1));
			if (h[i][j] == 3 && !lv[i][j] && !lv[i][j + 1])
				b.merge({i, j}, {i, j + 1});
			if (h[i][j] == 3 && lv[i][j] && !lv[i][j + 1]) {
				b[ {i, j + 1}].ins01(col[i][j], lv[i][j]);
			}
			if (h[i][j] == 3 && !lv[i][j] && lv[i][j + 1]) {
				b[ {i, j}].ins01(col[i][j + 1], lv[i][j + 1]);
			}
		}
	}
	for (int i = 1; i < n; i++) {
		for (int j = 1; j <= m; j++) {
			if (v[i][j] == 2 && !lv[i][j] && !lv[i + 1][j])
				vseg.merge(idv(i, j), idv(i + 1, j));
			if (v[i][j] == 3 && !lv[i][j] && !lv[i + 1][j])
				b.merge({i, j}, {i + 1, j});
			if (v[i][j] == 3 && lv[i][j] && !lv[i + 1][j]) {
				b[ {i + 1, j}].ins01(col[i][j], lv[i][j]);
			}
			if (v[i][j] == 3 && !lv[i][j] && lv[i + 1][j]) {
				b[ {i, j}].ins01(col[i + 1][j], lv[i + 1][j]);
			}
		}
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			if (!lv[i][j]) {
				b[ {i, j}].insp(i, j);
			}
		}
	}
	for (int i = q - 1; i >= 0; --i) {
		int ans = -1;
		int x = piece[i].x, y = piece[i].y;
		int lvx = lv[x][y], colx = col[x][y];
		lv[x][y] = 0;
		col[x][y] = -1;

		if (h[x][y] == 2 && !lv[x][y] && !lv[x][y + 1])
			hseg.merge(idh(x, y), idh(x, y + 1));
		if (h[x][y - 1] == 2 && !lv[x][y - 1] && !lv[x][y])
			hseg.merge(idh(x, y - 1), idh(x, y));
		pii l = hdi(hseg.getl(idh(x, y))), r = hdi(hseg.getr(idh(x, y)));
		ans += r.y - l.y + 1;

		if (v[x][y] == 2 && !lv[x][y] && !lv[x + 1][y])
			vseg.merge(idv(x, y), idv(x + 1, y));
		if (v[x - 1][y] == 2 && !lv[x - 1][y] && !lv[x][y])
			vseg.merge(idv(x - 1, y), idv(x, y));
		pii u = vdi(vseg.getl(idv(x, y))), d = vdi(vseg.getr(idv(x, y)));
		ans += d.x - u.x + 1;

		b[ {x, y}].insp(x, y);
		if (h[x][y] == 3) {
			if (!lv[x][y + 1])
				b.merge({x, y}, {x, y + 1});
			else
				b[ {x, y}].ins01(col[x][y + 1], lv[x][y + 1]);
		}
		if (h[x][y - 1] == 3) {
			if (!lv[x][y - 1])
				b.merge({x, y - 1}, {x, y});
			else
				b[ {x, y}].ins01(col[x][y - 1], lv[x][y - 1]);
		}
		if (v[x][y] == 3) {
			if (!lv[x + 1][y])
				b.merge({x, y}, {x + 1, y});
			else
				b[ {x, y}].ins01(col[x + 1][y], lv[x + 1][y]);
		}
		if (v[x - 1][y] == 3) {
			if (!lv[x - 1][y])
				b.merge({x - 1, y}, {x, y});
			else
				b[ {x, y}].ins01(col[x - 1][y], lv[x - 1][y]);
		}
		b[ {x, y}].era01(colx, lvx);
		ans += b[ {x, y}].getsz(colx, lvx);
		ans -= b[ {x, y}].geth(l.x, l.y, r.y, colx, lvx);
		ans -= b[ {x, y}].getv(u.x, d.x, u.y, colx, lvx);

		if (col[l.x][l.y - 1] == 1 - colx && h[l.x][l.y - 1] == 2 && lv[l.x][l.y - 1] <= lvx)
			++ans, --l.y;
		if (col[r.x][r.y + 1] == 1 - colx && h[r.x][r.y  ] == 2 && lv[r.x][r.y + 1] <= lvx)
			++ans, ++r.y;
		if (col[u.x - 1][u.y] == 1 - colx && v[u.x - 1][u.y] == 2 && lv[u.x - 1][u.y] <= lvx)
			++ans, --u.x;
		if (col[d.x + 1][d.y] == 1 - colx && v[d.x  ][d.y] == 2 && lv[d.x + 1][d.y] <= lvx)
			++ans, ++d.x;

		auto check = [&](int p, int q) {
			return !lv[p][q] || (col[p][q] == 1 - colx && lv[p][q] <= lvx);
		};
		auto vis = [&](int p, int q) {
			if (b._({p, q}) == b._({x, y}))
				return 1;
			if (lv[p][q] && exist(lv[p][q], col[p][q] ? b[ {x, y}].rt1: b[ {x, y}].rt0, 1, ::q))
				return 1;
			return 0;
		};
		if (h[x][y] == 1 && check(x, y + 1) && !vis(x, y + 1))
			++ans;
		if (v[x][y] == 1 && check(x + 1, y) && !vis(x + 1, y))
			++ans;
		if (h[x][y - 1] == 1 && check(x, y - 1) && !vis(x, y - 1))
			++ans;
		if (v[x - 1][y] == 1 && check(x - 1, y) && !vis(x - 1, y))
			++ans;
		::ans[i] = ans;
	}
	for (int i = 0; i < q; i++)
		cout << ans[i] << "\n";
	qsum += q;
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	int T;
	cin >> T;
	while (T--) {
		mian();
	}
}

提交,全部正确。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值