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;
}
posted @ 2021-09-02 20:13  stler  阅读(57)  评论(0)    收藏  举报