《XOR》与《再次跳跃吧,俊潇!》题解
《XOR》与《再次跳跃吧,俊潇!》题解
出题人:软件231***
XOR
题目
给你一个包含 nnn 个正整数的数组 aaa,你最多可以执行一次操作:选择一个下标 i(1≤i≤n)i \ (1 \leq i \leq n)i (1≤i≤n) 和一个正整数 x(x≤ai)x \ (x \leq a_i)x (x≤ai),将 aia_iai 修改为 ai−xa_i - xai−x。目标是使得最终数组的异或和为 000。
请输出你选择的下标 iii 和 xxx。如果无法实现该目标,或者初始数组的异或和已经为 000,则输出 −1-1−1。
题解
- 如果初始数组的异或和已经为 000,答案是 −1-1−1。
- 否则的话,对于一个数组 a1,a2,a3,...,ana_1,a_2,a_3,...,a_na1,a2,a3,...,an。设其异或和为 sss,选取某个下标 iii ,将 aia_iai 修改为 ai⊕sa_i \oplus sai⊕s ,这样数组的异或和为 s⊕s=0s \oplus s = 0s⊕s=0 。这样做的前提是 ai⊕s≤aia_i \oplus s \leq a_iai⊕s≤ai ,我们尝试证明一下是否总有一个下标 idxidxidx 满足aidx⊕s≤aidxa_{idx} \oplus s \leq a_{idx}aidx⊕s≤aidx ,如果能够证明的话就意味着问题一定有解。
证明如下:
- 设 sss 的最高位为 highestBithighestBithighestBit ,数组中在 highestBithighestBithighestBit 位为 111 的元素数量为奇数。
- 设 valvalval 为 aidxa_{idx}aidx 去掉超过 highestBithighestBithighestBit 位后的数值。令 t=2highestBitt=2^{highestBit}t=2highestBit 。
- 有 s⊕val<ts \oplus val < ts⊕val<t , 则有 s⊕aidx<aidxs \oplus a_{idx} < a_{idx}s⊕aidx<aidx ,证毕。
- 我们证明了一定存在,只需要遍历去找就行了。
代码
#include <bits/stdc++.h>
using namespace std;int main()
{ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);int n;cin >> n;int XOR = 0;vector<int> a(n + 1);for (int i = 1; i <= n; i++){cin >> a[i];XOR ^= a[i];}if (XOR == 0){cout << -1 << '\n';return 0;}for (int i = 1; i <= n; i++){int newAi = a[i] ^ XOR;if (newAi <= a[i]){int x = a[i] - newAi;cout << i << ' ' << x << '\n';return 0;}}return 0;
}
再次跳跃吧,俊潇!
题目
我们有 n+1n+1n+1 个格子,编号 1,2,…,n+11, 2, \dots, n+11,2,…,n+1 。
起点是格子 111,终点是格子 n+1n+1n+1。
对于每个格子 iii (1≤i≤n)(1 \le i \le n)(1≤i≤n) ,有两个参数:
- a[i]a[i]a[i]:最大跳跃长度。从格子 iii 可以跳到区间 [i+1,i+a[i]][i+1, i+a[i]][i+1,i+a[i]] 中的任意格子。
- w[i]w[i]w[i]:跳跃消耗的生命值。从格子 iii 跳到下一个格子时,会扣除 w[i]w[i]w[i] 点生命值。
初始生命值为 HHH。
最多使用 kkk 次魔法。在每次跳跃时,可以选择是否使用魔法(最多在该次跳跃用 111 次魔法,且总魔法次数不超过 kkk ),使用魔法则本次跳跃不扣血。
目标:
求最小的魔法使用次数 mmm(0≤m≤k)(0 \le m \le k)(0≤m≤k) ,使得能到达终点且生命值始终满足 >0> 0>0 。
如果不可能用最多 kkk 次魔法完成,则输出 −1-1−1。
题解
为什么不能贪心?
- 因为跳跃可前进不同距离
- 使用魔法的位置会影响路径长度和后续血量
- 直接按 w[i] 排序或只选最大的不一定对
- 考虑动态规划,设 dp[i][j]:dp[i][j]:dp[i][j]: 跳到第 iii 个格子使用了 jjj 次魔法的最大剩余血量。
- ai≤10a_i \leq 10ai≤10 ,我们可以枚举 iii 前面的 101010 个格子判断可达进行转移。再枚举 kkk 是否使用魔法转移。
- 状态转移方程:对于所有可转移的前驱点 jjj 且遍历 kkk ,dp[i][k]=max(dp[j][k−1],dp[j][k]−w[j])dp[i][k]=\max(dp[j][k-1],dp[j][k]-w[j])dp[i][k]=max(dp[j][k−1],dp[j][k]−w[j]) 前者使用魔法,后者不使用魔法。
弱化版 U614251 跳跃吧,俊潇!
代码
#include <bits/stdc++.h>
using namespace std;using ll = long long;
int main()
{int n, K;ll H;cin >> n >> K >> H;vector<int> a(n + 1), w(n + 1);for (int i = 1; i <= n; i++)cin >> a[i];for (int i = 1; i <= n; i++)cin >> w[i];vector<vector<ll>> dp(n + 2, vector<ll>(K + 1, -1e12));dp[1][0] = H;for (int i = 2; i <= n + 1; i++){for (int j = max(1, i - 10); j < i; j++){if (j + a[j] >= i){dp[i][0] = max(dp[i][0], dp[j][0] - w[j]);for (int k = 1; k <= K; k++)dp[i][k] = max({dp[i][k], dp[j][k - 1], dp[j][k] - w[j]});}}}int ans = -1;for (int j = 0; j <= K; j++){if (dp[n + 1][j] > 0){ans = j;break;}}cout << ans << '\n';return 0;
}