第十四届蓝桥杯大赛软件赛省赛C/C++大学A组

第十四届蓝桥杯大赛软件赛省赛C/C++大学A组

AB 为填空思维题
C 数论基础题
D 暴力或者区间DP
E 启发式合并模板题
F DFS + 剪枝
G kruskal重构树 + LCA
H 拆位算贡献
I DFS + 剪枝
J 不会

A: 幸运数

题面
本题总分:5 分

【问题描述】
小蓝认为如果一个数含有偶数个数位,并且前面一半的数位之和等于后面一半的数位之和,则这个数是他的幸运数字。例如 2314 是一个幸运数字,因为它有 4 个数位,并且 2 + 3 = 1 + 4 。现在请你帮他计算从 1 至 100000000 之间共有多少个不同的幸运数字。

【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

思路

法一
暴力测试每一个数字是否符合要求,本机能跑出正确结果 4430091

法二
注意到数字的最大范围是 1e8,说明符合条件的数字位数最多是 8 位,那么一半就是 4 位

定义数组 \(cnt[i][j]\) 表示位数为 \(i\) 且和为 \(j\) 时数字的个数,对 1 ~ 9999 之间的数字进行统计

枚举前后部分数字的位数 \(i \in [1, 4]\),注意前面的部分不能有前导 0;再枚举前半部分数位的和 \(j \in [1, 9 \times i]\);再枚举后面部分的位数 \(k \in [1, i]\),位数不够时默认用 0 补充即可。那么符合条件的数字个数即为 $\displaystyle \sum cnt[i][j] \times cnt[k][j] $

代码

  • 暴力做法
void solve(){
	int n = 5, a[10] = {};
	a[0] = 1;
	for(int i = 1; i < n; ++ i){
		a[i] = a[i - 1] * 10;
	}
	const int N = 1e8;
	int ans = 0;
	for(int i = 1; i <= N; ++ i){
		string ss = to_string(i);
		int len = ss.size();
		if(len % 2) continue;
		int x = i / a[len / 2], y = i % a[len / 2];
		int sumx = 0, sumy = 0;
		while(x){
			sumx += x % 10;
			x /= 10;
		}
		while(y){
			sumy += y % 10;
			y /= 10;
		}
		if(sumx == sumy){
			++ ans;
		}
	}
	cout << ans << '\n';
	return ;
}
  • 优化做法
void solve(){
	vector cnt(5, vector<int>(50, 0));
	for(int i = 1; i < 1e4; ++ i){
		int len = 0, sum = 0;
		for(int j = i; j; j /= 10){
			++ len;
			sum += j % 10;
		}
		++ cnt[len][sum];
	}
	int ans = 0;
	for(int i = 1; i <= 4; ++ i){
		for(int j = 1; j <= i * 9; ++ j){
			for(int k = 1; k <= i; ++ k){
				ans += cnt[i][j] * cnt[k][j];
			}
		}
	}
	cout << ans << '\n';
	return ;
}

B: 有奖问答

题面
本题总分:5 分

【问题描述】
小蓝正在参与一个现场问答的节目。活动中一共有 30 道题目,每题只有答对和答错两种情况,每答对一题得 10 分,答错一题分数归零。
小蓝可以在任意时刻结束答题并获得目前分数对应的奖项,之后不能再答任何题目。最高奖项需要 100 分,所以到达 100 分时小蓝会直接停止答题。
已知小蓝最终实际获得了 70 分对应的奖项,请问小蓝所有可能的答题情况有多少种?

【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

思路
直接暴搜,同时根据最终 70 分的限制进行剪枝,剩下的题不够理论 70 分就直接结束递归

注意题目的限制条件,一定要看清楚!!!

  • 可以在任意时刻结束答题并获得对应的分数,这意味着只要出现 70 分就是一个可以的答题方式
  • 达到 100 分时直接停止答题

代码

void solve(){
	int n = 30, ans = 0;
	auto dfs = [&](auto self, int i, int c) -> void {
		if(c == 70) ++ ans;
		if(c == 100) return ; 
		if(i > n){
			return ;
		}
		int yu = n - i;
		if(c + 10 * yu * 10 >= 70)
			self(self, i + 1, c + 10);
		if(yu >= 7)
			self(self, i + 1, 0);
	};
	dfs(dfs, 0, 0);
	cout << ans << '\n';
	return ;
}

C: 平方差

题面
时间限制: 1.0s 内存限制: 256.0MB 本题总分:10 分

【问题描述】
给定 \(L, R\),问 \(L ≤ x ≤ R\) 中有多少个数 \(x\) 满足存在整数 \(y,z\) 使得 \(x = y^2 − z^2\)

【输入格式】
输入一行包含两个整数 \(L, R\),用一个空格分隔。

【输出格式】
输出一行包含一个整数满足题目给定条件的 \(x\) 的数量。

【样例输入】

1 5

【样例输出】

4

【样例说明】
\(1 = 1^2 − 0^2\)
\(3 = 2^2 − 1^2\)
\(4 = 2^2 − 0^2\)
\(5 = 3^2 − 2^2\)

【评测用例规模与约定】
对于 40% 的评测用例,$L R ≤ 5000 $;
对于所有评测用例,\(1 ≤ L ≤ R ≤ 10^9\)

思路
对于式子 $ x = y^2 - z^2 = (y + z) \times (y - z) $

  • 如果说 \(x\) 为奇数,那么可以发现,只要令 \(y = z + 1\) 即可找到一组可行解
  • 如果说 \(x\) 为偶数
    • \(y = z + 2\),此时 \(z = 4(z + 1)\),说明 4 的倍数一定可以被表示
    • 注意到此时得满足式子$ x = (y + z) \times (y - z) $ 右侧包含因子 2。当 \(y - z\) 是偶数时,\(y + z\) 也必然是偶数,此时 \(x\) 也就是 4 的倍数;若 \(y + z\) 为偶数,此时 \(y - z\) 也必然是偶数,此时 \(x\) 依然是 4 的倍数。说明偶数中,只有 4 的倍数可以被表示。

综上,任意区间 \([l, r]\) 中可以被表示的数只有奇数和 4 的倍数

阿巴阿巴,蓝桥官方提交最后一个点需要long long 才能过,啊哈哈哈

代码

long long getsum(long long x){
	return x / 4 + (x + 1) / 2;
}

void solve(){
	long long l, r, ans = 0;
	cin >> l >> r;
	ans = getsum(r) - getsum(l - 1);
	cout << ans << '\n';
	return ;
}

D: 更小的数

题面
时间限制: 1.0s 内存限制: 256.0MB 本题总分:10 分

【问题描述】
image

小蓝有一个长度均为 n 且仅由数字字符 0 ∼ 9 组成的字符串,下标从 0 到n − 1,你可以将其视作是一个具有 n 位的十进制数字 num,小蓝可以从 num 中选出一段连续的子串并将子串进行反转,最多反转一次。小蓝想要将选出的子串进行反转后再放入原位置处得到的新的数字 \(num_{new}\) 满足条件 \(num_{new}\) < num,请你帮他计算下一共有多少种不同的子串选择方案,只要两个子串在 num 中的位置不完全相同我们就视作是不同的方案。
注意,我们允许前导零的存在,即数字的最高位可以是 0 ,这是合法的。

【输入格式】
输入一行包含一个长度为 n 的字符串表示 num(仅包含数字字符 0 ∼ 9),从左至右下标依次为 0 ∼ n − 1。

【输出格式】
输出一行包含一个整数表示答案。

【样例输入】

210102

【样例输出】

8

【样例说明】
\(\quad\)一共有 8 种不同的方案:
$\quad\quad$1)所选择的子串下标为 0 ∼ 1 ,反转后的 \(num_{new}\) = 120102 < 210102 ;
$\quad\quad$2)所选择的子串下标为 0 ∼ 2 ,反转后的 \(num_{new}\) = 012102 < 210102 ;
$\quad\quad$3)所选择的子串下标为 0 ∼ 3 ,反转后的 \(num_{new}\) = 101202 < 210102 ;
$\quad\quad$4)所选择的子串下标为 0 ∼ 4 ,反转后的 \(num_{new}\) = 010122 < 210102 ;
$\quad\quad$5)所选择的子串下标为 0 ∼ 5 ,反转后的 \(num_{new}\) = 201012 < 210102 ;
$\quad\quad$6)所选择的子串下标为 1 ∼ 2 ,反转后的 \(num_{new}\) = 201102 < 210102 ;
$\quad\quad$7)所选择的子串下标为 1 ∼ 4 ,反转后的 \(num_{new}\) = 201012 < 210102 ;
$\quad\quad$8)所选择的子串下标为 3 ∼ 4 ,反转后的 \(num_{new}\) = 210012 < 210102 ;

【评测用例规模与约定】
对于 20% 的评测用例,1 ≤ n ≤ 100 ;
对于 40% 的评测用例,1 ≤ n ≤ 1000 ;
对于所有评测用例,1 ≤ n ≤ 5000 。

思路
注意到数据范围,\(O(n ^ 2)\) 显然可过,直接暴力判断每一个子串是否逆序字典序小于正序字典序即可

也可以利用区间 DP 解决这个问题,详见附上的代码

代码

  • 暴力
void solve(){
	string ss;
	cin >> ss;
	int ans = 0;
	for(int i = 0; i < ss.size(); ++ i){
		for(int j = i + 1; j < ss.size(); ++ j){
			int l = i, r = j;
			bool f = false;
			while(l < r){
				if(ss[r] < ss[l]){
					f = true;
					break;
				}else if(ss[r] > ss[l])
					break;
				++ l; -- r;
			}
			if(f){
				++ ans;
			}
		}
	}
	cout << ans << '\n';
	return ;
}
  • 再摘记一个区间 DP 思路
void solve(){
	string ss;
	cin >> ss;
	int ans = 0, n = ss.size();
	vector dp(n, vector<int>(n, 0));
	for(int len = 2; len <= n; ++ len){
		for(int i = 0; i + len - 1 < n; ++ i){
			int j = i + len - 1;
			if(ss[i] > ss[j]) dp[i][j] = 1;// 为 1 表示该区间可以翻转
			else if(ss[i] == ss[j]) dp[i][j] = dp[i + 1][j - 1];
			ans += dp[i][j];
		}
	}
	cout << ans << '\n';
	return ;
}

E: 颜色平衡树

题面
时间限制: 1.0s 内存限制: 256.0MB 本题总分:15 分

【问题描述】
给定一棵树,结点由 \(1\)\(n\) 编号,其中结点 \(1\) 是树根。树的每个点有一个颜色 \(C_i\)
如果一棵树中存在的每种颜色的结点个数都相同,则我们称它是一棵颜色平衡树。
求出这棵树中有多少个子树是颜色平衡树。

【输入格式】
输入的第一行包含一个整数 \(n\) ,表示树的结点数。
接下来 \(n\) 行,每行包含两个整数 \(C_i, F_i\),用一个空格分隔,表示第 \(i\) 个结点的颜色和父亲结点编号。
特别地,输入数据保证 \(F_1\)\(0\) ,也即 \(1\) 号点没有父亲结点。保证输入数据是一棵树。

【输出格式】
输出一行包含一个整数表示答案。

【样例输入】

6
2 0
2 1
1 2
3 3
3 4
1 4

【样例输出】

4

【样例说明】
编号为 1, 3, 5, 6 的 4 个结点对应的子树为颜色平衡树。

【评测用例规模与约定】
对于 30% 的评测用例,\(n ≤ 200,C_i ≤ 200\)
对于 60% 的评测用例,\(n ≤ 5000,C_i ≤ 5000\)
对于所有评测用例,\(1 ≤ n ≤ 200000,1 ≤ C_i ≤ 200000,0 ≤ F_i < i\)

思路
启发式合并模板题
不会的话可以搜搜网上资料学一学

代码

const int N = 2e5 + 5;
int n, c[N];
vector<int> e[N];

int son[N], sz[N], id[N], rid[N];
int ans = 0, idx = 0;
map<int, int> cnt;
void dfs1(int u, int f){
	sz[u] = 1;
	id[u] = ++ idx;
	rid[idx] = u;
	for(auto v : e[u]){
		if(v == f) continue;
		dfs1(v, u);
		sz[u] += sz[v];
		if(sz[son[u]] < sz[v])
			son[u] = v;
	}
	return ;
}

void dfs2(int u, int f, bool keep){
	for(auto v : e[u]){
		if(v == son[u] || v == f) continue;
		dfs2(v, u, false);
	}
	if(son[u]) dfs2(son[u], u, true);
	for(auto v : e[u]){
		if(v == son[u] || v == f) continue;
		for(int l = id[v], r = id[v] + sz[v]; l < r; ++ l){
			++ cnt[c[rid[l]]];
		}
	}
	++ cnt[c[u]];
	int s = 0;
	for(auto [u, v] : cnt){
		if(s == 0) s = v;
		else if(s != v){
			s = -1;
			break;
		}
	}
	if(s != -1) ++ ans;
	if(!keep) cnt.clear();
	return ;
}

void solve(){
	cin >> n;
	for(int i = 1; i <= n; ++ i){
		int col, f;
		cin >> col >> f;
		c[i] = col;
		e[i].push_back(f);
		e[f].push_back(i);
	}
	dfs1(1, 0);
	dfs2(1, 0, false);
	cout << ans << '\n';
	return ;
}

F: 买瓜

题面
时间限制: 1.0s 内存限制: 256.0MB 本题总分:15 分

【问题描述】
小蓝正在一个瓜摊上买瓜。瓜摊上共有 \(n\) 个瓜,每个瓜的重量为 \(A_i\)
小蓝刀功了得,他可以把任何瓜劈成完全等重的两份,不过每个瓜只能劈一刀。
小蓝希望买到的瓜的重量的和恰好为 \(m\)
请问小蓝至少要劈多少个瓜才能买到重量恰好为 \(m\) 的瓜。如果无论怎样小
蓝都无法得到总重恰好为 \(m\) 的瓜,请输出 −1 。

【输入格式】
输入的第一行包含两个整数 \(n, m\),用一个空格分隔,分别表示瓜的个数和小蓝想买到的瓜的总重量。
第二行包含 \(n\) 个整数 \(A_i\),相邻整数之间使用一个空格分隔,分别表示每个瓜的重量。

【输出格式】
输出一行包含一个整数表示答案。

【样例输入】

3 10
1 3 13

【样例输出】

2

【评测用例规模与约定】
对于 20% 的评测用例,\(\sum n ≤ 10\)
对于 60% 的评测用例,\(\sum n ≤ 20\)
对于所有评测用例,\(1 ≤ n ≤ 30,1 ≤ A_i ≤ 10^9 ,1 ≤ m ≤ 10^9\)

思路
DFS + 剪枝
将所有数字都扩大 2 倍以避免小数的出现
将瓜按照重量从大到小排序,再统计一个后缀和,用于搜索时剪枝
DFS 时有三种选择状态:不选当前瓜、完全选择当前瓜,选择当前瓜的一半
剪枝部分详见代码

代码

void solve(){
	ll n, m;
	cin >> n >> m;
	m <<= 1;
	vector<ll> a(n);
	for(int i = 0; i < n; ++ i){
		cin >> a[i];
		a[i] <<= 1;// 2 倍为了避免小数的出现
	}
	sort(a.begin(), a.end(), greater<int>());
	vector<ll> sum(n + 1);
	for(int i = n - 1; i >= 0; -- i){// 从大到小排序后计算后缀
		sum[i] += sum[i + 1] + a[i];
	}
	int ans = 50;
	auto dfs = [&](auto self, ll s, int i, int cnt) -> void {
		if(cnt > ans) return ;// 当前需要次数已经超过目前最优解
		if(s == m) ans = cnt;
		if(i == n || s > m || s + sum[i] < m) return ;
		self(self, s, i + 1, cnt);// 当前瓜不选
		self(self, s + a[i], i + 1, cnt);// 选择当前瓜
		self(self, s + a[i] / 2, i + 1, cnt + 1);// 选择当前瓜的一半
	};
	dfs(dfs, 0, 0, 0);
	if(ans == 50) ans = -1;
	cout << ans << '\n';
	return ;
}

G: 网络稳定性

题面
时间限制: 1.5s 内存限制: 256.0MB 本题总分:20 分

【问题描述】
有一个局域网,由 n 个设备和 m 条物理连接组成,第 i 条连接的稳定性为 \(w_i\)
对于从设备 A 到设备 B 的一条经过了若干个物理连接的路径,我们记这条路径的稳定性为其经过所有连接中稳定性最低的那个。
我们记设备 A 到设备 B 之间通信的稳定性为 A 至 B 的所有可行路径的稳定性中最高的那一条。
给定局域网中的设备的物理连接情况,求出若干组设备 \(x_i\)\(y_i\) 之间的通信稳定性。如果两台设备之间不存在任何路径,请输出 −1 。

【输入格式】
输入的第一行包含三个整数 n, m, q ,分别表示设备数、物理连接数和询问数。
接下来 m 行,每行包含三个整数 \(u_i, v_i, w_i\),分别表示 \(u_i\)\(v_i\) 之间有一条稳定性为 \(w_i\) 的物理连接。
接下来 q 行,每行包含两个整数 \(x_i, y_i\) ,表示查询 \(x_i\)\(y_i\) 之间的通信稳定性。

【输出格式】
输出 q 行,每行包含一个整数依次表示每个询问的答案。

【样例输入】

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

【样例输出】

-1
3
5

【评测用例规模与约定】
对于 30% 的评测用例,n, q ≤ 500,m ≤ 1000 ;
对于 60% 的评测用例,n, q ≤ 5000,m ≤ 10000 ;
对于所有评测用例,\(2 ≤ n, q ≤ 10^5,1 ≤ m ≤ 3 × 10^5,1 ≤ u_i, v_i, x_i, y_i ≤ n,1 ≤ w_i ≤ 10^6,u_i \ne v_i,x_i \ne y_i\)

思路
注意到对于一个局域网连通块中的任意两个设备,我们都需要找到其路径途中最小值最大的任意一条路

考虑利用 kruskal 构造一个最大生成树,那么其所选择出来的路径就是我们想要的路径

那么我们会得到一个森林,对于每一棵树,利用树上倍增的思想维护路径上的边权最小值,查询时找 lca 的同时维护最小值即可

难度不大,但是涉及的知识点较多

代码

const int N = 1e5 + 5, M = 3e5 + 5, K = 17, inf = 0x3f3f3f3f;
int n, m, q;
vector<pair<int, int>> e[N];

bool vis[N];
int fa[N][K], c[N][K], dep[N];

struct edge{
	int u, v, w;
}p[M];

struct DSU{
	int num;
	vector<int> fa, sz;
	DSU(int x) : fa(x + 1), sz(x + 1, 1), num(x) {
		for(int i = 0; i <= x; ++ i)
			fa[i] = i;
	}
	int findfa(int x){
		while(x != fa[x]) x = fa[x] = fa[fa[x]];
		return x;
	}
	int size(int x) { return sz[findfa(x)]; }
	bool same(int x, int y) { return findfa(x) == findfa(y); }
	bool merge(int x, int y){
		x = findfa(x); y = findfa(y);
		if(x == y) return false;
		if(sz[x] < sz[y]) swap(x, y);
		sz[x] += sz[y];
		fa[y] = x;
		return true;
	}
};

void kruskal(DSU& dsu){// 跑最大生成树
	sort(p, p + m, [&](edge& x, edge& y){
		return x.w > y.w;
	});
	for(int i = 0; i < m; ++ i){
		int u = p[i].u, v = p[i].v, w = p[i].w;
		if(!dsu.same(u, v)){
			dsu.merge(u, v);
			e[u].push_back({v, w});
			e[v].push_back({u, w});
		}
	}
	return ;
}

void dfs(int u, int f){
	vis[u] = true;
	dep[u] = dep[f] + 1;
	fa[u][0] = f;
	for(int i = 1; i < K; ++ i){
		fa[u][i] = fa[fa[u][i - 1]][i - 1];
		c[u][i] = min(c[u][i - 1], c[fa[u][i - 1]][i - 1]);
	}
	for(auto [v, w] : e[u]){
		if(v == f) continue;
		c[v][0] = w;
		dfs(v, u);
	}
	return ;
}

int lca(int u, int v){
	int ans = inf;
	if(dep[u] < dep[v]) swap(u, v);
	for(int i = K - 1; i >= 0; -- i){
		if(dep[fa[u][i]] >= dep[v]){
			ans = min(ans, c[u][i]);
			u = fa[u][i];
		}
		if(u == v) return ans;
	}
	for(int i = K - 1; i >= 0; -- i){
		if(fa[u][i] != fa[v][i]){
			ans = min(ans, min(c[u][i], c[v][i]));
			u = fa[u][i];
			v = fa[v][i];
		}
	}
	ans = min(ans, min(c[u][0], c[v][0]));
	return ans;
}

void solve(){
	cin >> n >> m >> q;
	for(int i = 0; i < m; ++ i){
		cin >> p[i].u >> p[i].v >> p[i].w;
	}
	DSU dsu(n);
	kruskal(dsu);
	for(int i = 1; i <= n; ++ i){
		if(!vis[i]) dfs(i, 0);
	}
	for(int i = 0; i < q; ++ i){
		int x, y, ans;
		cin >> x >> y;
		if(!dsu.same(x, y))
			ans = -1;
		else ans = lca(x, y);
		cout << ans << '\n';
	}
	return ;
}

H: 异或和之和

题面
时间限制: 1.0s 内存限制: 256.0MB 本题总分:20 分

【问题描述】
给定一个数组 \(A_i\),分别求其每个子段的异或和,并求出它们的和。或者说,对于每组满足 1 ≤ L ≤ R ≤ n 的 L, R ,求出数组中第 L 至第 R 个元素的异或和。然后输出每组 L, R 得到的结果加起来的值。

【输入格式】
输入的第一行包含一个整数 n 。
第二行包含 n 个整数 \(A_i\) ,相邻整数之间使用一个空格分隔。

【输出格式】
输出一行包含一个整数表示答案。

【样例输入】

5
1 2 3 4 5

【样例输出】

39

【评测用例规模与约定】
对于 30% 的评测用例,n ≤ 300 ;
对于 60% 的评测用例,n ≤ 5000 ;
对于所有评测用例,\(1 ≤ n ≤ 10^5,0 ≤ A_i ≤ 2^{20}\)

思路

先求得异或前缀和 $ b_i = a_1 \oplus a_2 \oplus \cdots \oplus a_i $

易知题目应求式子:$ \displaystyle \sum_{i = 1}^{n} \sum_{j = i}^{n} b_j \oplus b_{i - 1} $

拆位考虑这个问题,对于某一个特定的位,仅当当前位上 $ a_j $ 与 $ a_{i - 1} $ 不相同时才对答案产生贡献。

所以我们可以枚举每一位,统计前缀异或和这一位上 0 和 1 的个数,那么这一位对答案产生的贡献就是 $ cnt_0 \times cnt_1 \times 2 ^ i $,所有位的和即为最终的答案

代码

void solve(){
	const int N = 20;
	int n;
	cin >> n;
	vector<ll> a(n + 1);
	for(int i = 1; i <= n; ++ i){
		cin >> a[i];
		a[i] ^= a[i - 1];
	}
	ll ans = 0;
	vector cnt(N + 1, vector<ll>(2));
	for(int k = 0; k <= N; ++ k){
		for(int i = 0; i <= n; ++ i){// 注意从 0 开始
			++ cnt[k][a[i] >> k & 1];
		}
		ans += cnt[k][0] * cnt[k][1] * (1ll << k);
	}
	cout << ans << '\n';
	return ;
}

I: 像素放置

题面
时间限制: 1.0s 内存限制: 256.0MB 本题总分:25 分

【问题描述】
小蓝最近迷上了一款名为《像素放置》的游戏,游戏在一个 n × m 的网格棋盘上进行,棋盘含有 n 行,每行包含 m 个方格。玩家的任务就是需要对这n × m 个方格进行像素填充,填充颜色只有黑色或白色两种。有些方格中会出现一个整数数字 x(0 ≤ x ≤ 9),这表示当前方格加上周围八个方向上相邻的方格(分别是上方、下方、左方、右方、左上方、右上方、左下方、右下方)共九个方格内有且仅有 x 个方格需要用黑色填充。
玩家需要在满足所有数字约束下对网格进行像素填充,请你帮助小蓝来完成。题目保证所有数据都有解并且解是唯一的。

【输入格式】
输入的第一行包含两个整数 n, m,用一个空格分隔,表示棋盘大小。
接下来 n 行,每行包含 m 个字符,表示棋盘布局。字符可能是数字 0 ∼ 9,这表示网格上的数字;字符还有可能是下划线(ASCII 码为 95 ),表示一个不带有数字的普通网格。

【输出格式】
输出 n 行,每行包含 m 个字符,表示答案。如果网格填充白色则用字符 0 表示,如果网格填充黑色则用字符 1 表示。

【样例输入】

6 8
_1__5_1_
1_4__42_
3__6__5_
___56___
_688___4
_____6__

【样例输出】

00011000
00111100
01000010
11111111
01011110
01111110

【样例说明】

image

上图左是样例数据对应的棋盘布局,上图右是此局游戏的解。例如第 3 行第 1 列处的方格中有一个数字 3 ,它周围有且仅有 3 个格子被黑色填充,分别是第 3 行第 2 列、第 4 行第 1 列和第 4 行第 2 列的方格。

【评测用例规模与约定】
对于 50% 的评测用例,1 ≤ n, m ≤ 5 ;
对于所有评测用例,1 ≤ n, m ≤ 10 。

思路
DFS + 剪枝

代码

void solve(){
	int n, m;
	cin >> n >> m;
	vector<string> ss(n + 2);
	vector<vector<int>> f(n + 3, vector<int>(m + 3));
	for(int i = 1; i <= n; ++ i){
		cin >> ss[i];
		ss[i] = ' ' + ss[i];
	}
	auto check = [&](int x, int y) -> bool {
		if(ss[x][y] == '_') return true;
		int cnt = 0;
		for(int i = -1; i <= 1; ++ i){
			for(int j = -1; j <= 1; ++ j){
				cnt += f[x + i][y + j];
			}
		}
		if(cnt == ss[x][y] - '0') return true;
		return false;
	};
	auto dfs = [&](auto self, int x, int y) -> void {
		if(x == n + 1){// 全放好了,需要检查是否符合最后一行
			for(int i = 1; i <= m; ++ i)
				if(!check(n, i)) return ;
			// 找到一个答案,输出即可
			for(int i = 1; i <= n; ++ i){
				for(int j = 1; j <= m; ++ j)
					cout << (char)(f[i][j] + '0');
				cout << '\n';
			}
			return ;
		}
		if(y == m){// 到了最后一列,需要换行
			f[x][y] = 0;
			if(x == 1 || (y == 1 && check(x - 1, y)) || (check(x - 1, y - 1) && check(x - 1, y))){
				self(self, x + 1, 1);// 换行
			}
			f[x][y] = 1;
			if(x == 1 || (y == 1 && check(x - 1, y)) || (check(x - 1, y - 1) && check(x - 1, y))){
				self(self, x + 1, 1);// 换行
			}
		}else{// 遍历下一列
			f[x][y] = 0;
			if(x == 1 || y == 1 || check(x - 1, y - 1)){
				self(self, x, y + 1);// 换行
			}
			f[x][y] = 1;
			if(x == 1 || y == 1 || check(x - 1, y - 1)){
				self(self, x, y + 1);// 换行
			}
		}
		return ;
	};
	dfs(dfs, 1, 1);
	return ;
}

J: 翻转硬币

题面
时间限制: 3.0s 内存限制: 256.0MB 本题总分:25 分

【问题描述】
给定 n 个按顺序摆好的硬币,一开始只有第 1 个硬币朝下,其他硬币均朝上。你每次操作可以选择任何一个整数 i 并将所有满足 j mod i = 0 的位置 j 的硬币翻转。
求最少需要多少次操作可以让所有硬币都朝上。

【输入格式】
输入一行包含一个整数 n 。

【输出格式】
输出一行包含一个整数表示最少需要的操作次数。

【样例输入 1】

7

【样例输出 1】

6

【样例输入 2】

1131796

【样例输出 2】

688042

【评测用例规模与约定】
对于 30% 的评测用例,\(n ≤ 5 × 10^6\)
对于 70% 的评测用例,\(n ≤ 10^9\)
对于所有评测用例,\(1 ≤ n ≤ 10^{18}\)

思路
难题,日后再说
https://blog.csdn.net/qq_40485202/article/details/137413077?spm=1001.2014.3001.5502

代码
xxx

posted on 2024-04-01 17:06  Qiansui  阅读(255)  评论(0编辑  收藏  举报