CSP-J 2020题解

CSP-J 2020题解

日期:2020-12-13

前言:闲来无事,蒟蒻要来做做历届\(CSP-J \ / \ NOIP\)的题了,毕竟明年比赛时就初三了qwq。

A. 优秀的拆分

题目链接

核心算法:模拟

水题,直接上代码:

#include <bits/stdc++.h>
using namespace std;

int n, k = 1 << 30;

int main(){ 
	scanf("%d", &n);
	
	if (n & 1) printf("-1\n");
	else { 
		while (k) { 
			if (n & k) printf("%d ", k);
			k >>= 1;
		} 
	} 
	
	return 0;
} 

B. 直播获奖

题目链接

水题,因为选手成绩不超过\(600\),所以开个桶计数,每次扫一遍即可。

核心算法:计数排序

时间复杂度:\(O(wn)\),其中\(w\)是常数且等于\(600\)

\(Hint\):做题时注意数据范围

#include <bits/stdc++.h>
using namespace std;

const int M = 660;
int cnt[M];
int n, w, x;

int main(){ 
	scanf("%d%d", &n, &w);
	
	memset(cnt, 0, sizeof cnt);
	
	for (int i = 1; i <= n; ++i) { 
		scanf("%d", &x);
		++cnt[x];
		
		int p = i * w / 100;
		if (!p) p = 1;
		
		for (int j = 600; j >= 0; --j) 
			if (cnt[j]) { 
				p -= cnt[j];
				if (p <= 0) { 
					printf("%d ", j);
					break;
				} 
			} 
	} 
	
	return 0;
} 

C. 表达式

题目链接

1. 分析

这是最难的一道题目,首先可以考虑建一棵表达式树,样例\(2\)如下图所示:

我们尝试预处理每个\(x\)更改后最后结果的值,所以自顶向下搜索。由于\(\&\)\(|\)是短路运算符,所以:

  1. 当运算符为\(\&\)时,若一侧为\(0\),则另一侧无论怎么修改,也无法改变最后结果;
  2. 当运算符为\(|\)时,若一侧为\(1\),则另一侧无论怎么修改,也无法改变最后结果。

根据这个性质,在搜索时,若一侧出现上述情况,则将另一侧的所有\(x\)打上\(false\)标记,表示对结果无影响。

(红色表示有影响)

令未更改时最后结果为\(ans\)。若\(x_i\)\(false\)标记,结果就是\(ans\);否则结果为\(!ans\)

2. 小结

核心算法:树形结构短路运算符

时间复杂度:\(O(n+q)\)

\(Hint\):遇到表达式时,可以考虑建表达式树;注意分析短路运算符的性质。

#include <bits/stdc++.h>

const int L = 1.1e6;
const int N = 1.1e5;
bool a[N], flag[N];
char s[L];
int l, n, top = 1, num = 0, q;
int sta[N];
struct node{ 
	int val, opt, l, r;
	void init(int x, int z, int ll, int rr){ 
		val = x; opt = z;
		l = ll; r = rr; 
	} 
};
node tree[L];

void dfs(int p, bool mark){ 
	if (!tree[p].l && !tree[p].r) { 
		flag[tree[p].opt] = mark;
		return ;
	} 
	
	if (!mark) { 
		dfs(tree[p].l, mark);
		dfs(tree[p].r, mark);
		return ;
	} 
	
	dfs(tree[p].l, tree[tree[p].r].val ^ tree[p].opt);
	dfs(tree[p].r, tree[tree[p].l].val ^ tree[p].opt);
} 

int main(){ 
	gets(s + 1);
	l = strlen(s + 1);
	
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", a + i);
	
	for (int i = 1; i <= l; ++i) { 
		if (s[i] == 'x') { 
			int id = 0;
			for (int j = i + 1; j <= l; ++j) { 
				if (std::isdigit(s[j])) id = id * 10 + (s[j] - 48);
				else { i = j; break; } 
			} 
			sta[top++] = ++num;
			tree[num].init(a[id], id, 0, 0);
		} 
		if (s[i] == '&') { 
			int y = sta[--top], x = sta[--top];
			sta[top++] = ++num;
			tree[num].init(tree[x].val & tree[y].val, 0, x, y);
		} 
		if (s[i] == '|') { 
			int y = sta[--top], x = sta[--top];
			sta[top++] = ++num;
			tree[num].init(tree[x].val | tree[y].val, 1, x, y);
		} 
		if (s[i] == '!') tree[sta[top - 1]].val ^= 1;
	} 
	
	bool ans = tree[sta[1]].val;
	dfs(sta[1], true);
	
	scanf("%d", &q);
	for (int x, i = 1; i <= q; ++i) { 
		scanf("%d", &x);
		printf("%d\n", ans ^ flag[x]);
	} 
	
	return 0;
} 

D. 方格取数

题目链接

手推样例后不难发现,不同路线的某一部分可能重叠,由此可以想到动态规划

1. 分析1

初始地,我们定义最简单的转移方程。令\(f(i, j)\)表示从\((1,1)\)走到\((i,j)\)位置,取到数的和的最大值。

我们按列转移,则有转移方程:

\[f(i,j)= \begin{cases} \max_{k=1}^n(f(k, j-1)+ \begin{cases} \sum_{t=i}^k a[i][j] & i \leq k \\ \sum_{t=k}^i a[i][j] & i > k \end{cases} ) & j > 1 \\ \sum_{k=1}^ia[i][j] & j=1 \end{cases} \]

\(\sum\)可用前缀和优化,令\(S[k]\)表示\(\sum_{t=1}^k a[j][t]\),则原方程可优化为:

\[f(i,j)= \begin{cases} \max_{k=1}^n(f(k, j-1)+ \begin{cases} S[k] - S[i-1] & i \leq k \\ S[i]-S[k-1] & i > k \end{cases} ) & j > 1 \\ S[i] & j=1 \end{cases} \]

至此,我们得到了一个\(O(n^2m)\)\(70pts\)算法。

2. 分析2

画图可知,一个点只能由它的上和下转移得来(左包含在内);否则就是非法的。

于是,我们定义方程\(f(i,j,0/1)\)表示从\((1,1)\)走到\((i,j)\)位置,由上/下转移过来,取到数的和的最大值。

同样按列转移,则有方程:

\[f(i,j,0)=\max(f[i-1][j][0],\max(f[i][j-1][0],f[i][j-1][1])) \\ f(i,j,1)=\max(f[i-1][j][1],\max(f[i][j-1][0],f[i][j-1][1])) \\ \]

注意:两个方程循环顺序不同,还需理清边界条件。

时间复杂度:\(O(nm)\)。可得满分。

3. 小结

核心算法:动态规划

\(Hint\):先推出最简单的方程,再画图/分析,考虑优化

#include <bits/stdc++.h>
using std::max;

const int N = 1100;
int n, m, map[N][N];
long long f[N][N][2];

int main(){ 
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; ++i) 
		for (int j = 1; j <= m; ++j) scanf("%d", &map[i][j]);
	
	for (int i = 1; i <= m; ++i) f[0][i][0] = f[n + 1][i][1] = -1e10;
	for (int i = 1; i <= n; ++i) f[i][0][0] = f[i][0][1] = -1e10;
	f[0][1][0] = 0;
	
	for (int j = 1; j <= m; ++j) { 
		for (int i = 1; i <= n; ++i) f[i][j][0] = max(f[i - 1][j][0], max(f[i][j - 1][0], f[i][j - 1][1])) + map[i][j];
		for (int i = n; i >= 1; --i) f[i][j][1] = max(f[i + 1][j][1], max(f[i][j - 1][0], f[i][j - 1][1])) + map[i][j];
	} 
	
	printf("%lld\n", f[n][m][0]);
	
	return 0;
} 
posted @ 2020-12-13 16:04  _lhy  阅读(217)  评论(0编辑  收藏  举报