2021牛客暑期多校训练营8
2021牛客暑期多校训练营8_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ
D - OR
首先要知道一个结论,即\(a+b=a\&b+a|b\),那么\(c_i=a_{i-1}+a_i\)等价于\(a_{i-1}\&a_i+a_{i-1}|a_i\),而\(b_i=a_{i-1}|a_i\),我们可以构造新的数组\(d\),使得\(d_i=c_i-b_i=a_{i-1}\&a_i\)。
那么题目等价于求满足以下条件的\(a\)数组个数:
\(\bullet\) \(a_i|a_{i-1}=b_i\)
\(\bullet\) \(a_i\&a_{i-1}=c_i-b_i(我们令下面的c_i就表示c_i-b_i)\)
我们可以对每一位分开考虑贡献然后相乘。
假设当前枚举到了\(i\),那么会有四种情况\((以下情况都假设是某一位)\):
\(b_i = 1\&\&c_i=1(a_i|a_{i-1}=1\&\&a_i\&a_{i-1}=1)\):
\(\bullet\) 如果存在\(a_{i-1}=0\),那么不存在\(a_i\)。
\(\bullet\) 如果存在\(a_{i-1}=1\),那么\(a_i=1\)。
\(b_i = 1\&\&c_i=0(a_i|a_{i-1}=1\&\&a_i\&a_{i-1}=0)\):
\(\bullet\) 如果存在\(a_{i-1}=0\),那么\(a_i=1\)。
\(\bullet\) 如果存在\(a_{i-1}=1\),那么\(a_i=0\)。
\(b_i = 0\&\&c_i=1(a_i|a_{i-1}=0\&\&a_i\&a_{i-1}=1)\):
\(\bullet\) 如果存在\(a_{i-1}=0\),那么不存在\(a_i\)。
\(\bullet\) 如果存在\(a_{i-1}=1\),那么不存在\(a_i\)。
\(b_i = 0\&\&c_i=0(a_i|a_{i-1}=0\&\&a_i\&a_{i-1}=0)\):
\(\bullet\) 如果存在\(a_{i-1}=0\),那么\(a_i=0\)。
\(\bullet\) 如果存在\(a_{i-1}=1\),那么不存在\(a_i\)。
所以我们按照前一位和当前位分类讨论即可。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
int b[MAXN], c[MAXN];
int main(int argc, char *argv[]) {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int n;
cin >> n;
for (int i = 2; i <= n; ++i) {
cin >> b[i];
}
for (int i = 2; i <= n; ++i) {
cin >> c[i];
c[i] -= b[i];
}
long long res = 1;
for (int mask = 0; mask < 30; ++mask) {
// 前一位能否存在0或者1
bool pre0 = true, pre1 = true;
for (int i = 2; i <= n; ++i) {
// 当前位能否放0或者1
int now0 = false, now1 = false;
int x = b[i] >> mask & 1; // b[i]第mask位
int y = c[i] >> mask & 1; // c[i]第mask位
// a[i-1] & a[i] = 1
// a[i-1] | a[i] = 1
if (x && y) {
now0 = 0;
now1 = pre1;
}
// a[i-1] | a[i] = 1
// a[i-1] & a[i] = 0
if (x && !y) {
now0 = pre1;
now1 = pre0;
}
// a[i-1] | a[i] = 0
// a[i-1] & a[i] = 1
if (!x && y) {
now0 = 0;
now1 = 0;
}
// a[i-1] | a[i] = 0
// a[i-1] & a[i] = 0
if (!x && !y) {
now0 = pre0;
now1 = 0;
}
pre0 = now0;
pre1 = now1;
}
res *= pre0 + pre1;
}
cout << res << '\n';
system("pause");
return 0;
}
F - Robots
首先对于\(1\)和\(2\)类型的机器人,直接判断是否在相同行列即可,主要是对第\(3\)种机器人的处理。
因为询问很多,我们可以反向考虑,判断能否从终点到达起点,我们可以考虑离线处理这些询问,然后考虑动态规划的方法,设\(dp[i][j]\)表示能到达\((i,j)\)的坐标的集合。可以推出\(dp[i][j]=dp[i-1][j]~~or~~dp[i][j-1]~~or~~g[i][j]==0(or表示或运算)\)。因为转移方程包括了位运算,我们可以用\(bitset\)简化这个过程,我们将每个坐标抽象成一个点,例如\(5\cdot 3\)的网格的\((2,2)\)代表的点就是\((2-1)\cdot3+2=5\),那么它就是\(dp[i][j]\)的\(bitset\)的第\(5\)位。
这么做很容易发现一个问题:空间不足。
其实对于这样的二维动态规划,是可以采用滚动数组的方法的,如果写过P1002 NOIP2002 普及组 过河卒 - 洛谷 | 计算机科学教育新生态就知道\(dp\)数组的第一维是可以省去的,具体证明可以看洛谷的题解。
那么我们只需要把每个询问的下标,类型,起点坐标存在终点的位置即可,即设置一个\(vector<int>q[N][N]\),假如有询问\((t,x1,y1,x2,y2)\)我们就在\(q[x2][y2]\)存入这个询问的信息。
然后遍历到\((i,j)\)的时候顺便把终点为\((i,j)\)的询问处理掉即可,判断\((i,j)\)能否达到这个询问的起点。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 5e2 + 5;
struct Query {
int idx, type;
int x, y; // 起点坐标
};
int ans[MAXN * MAXN * 2];
vector<Query> q[MAXN][MAXN];
int g[MAXN][MAXN];
bitset<MAXN * MAXN> dp[MAXN];
int idx, ID[MAXN][MAXN];
void init(int n, int m) {
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
ID[i][j] = ++idx;
}
}
}
int main(int argc, char *argv[]) {
int n, m;
scanf("%d %d", &n, &m);
init(n, m); // 对每个坐标进行映射
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
scanf("%1d", &g[i][j]);
// 也可以用字符串存
// 直接存数字的话要用1d不然会读入整个字符串
}
}
int t;
scanf("%d", &t);
for (int i = 1; i <= t; ++i) {
int type, x1, y1, x2, y2;
scanf("%d %d %d %d %d", &type, &x1, &y1, &x2, &y2);
q[x2][y2].push_back({i, type, x1, y1}); // 存入询问
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
if (g[i][j] == 0) {
dp[j] |= dp[j - 1];
dp[j][ID[i][j]] = 1;
}
// 如果当前位置是障碍物则没有点能到直接清零
else {
dp[j].reset();
}
for (auto [idx, type, x, y]: q[i][j]) {
// 对于1和2的询问还需判断是否同行同列
// 只能向下
if (type == 1) {
ans[idx] = (y == j && dp[j][ID[x][y]]);
}
// 只能向右
else if (type == 2) {
ans[idx] = (x == i && dp[j][ID[x][y]]);
}
else {
ans[idx] = dp[j][ID[x][y]];
}
}
}
}
for (int i = 1; i <= t; ++i) {
cout << (ans[i] ? "yes" : "no") << '\n';
}
system("pause");
return 0;
}

浙公网安备 33010602011771号