2021牛客多校8

D-OR(思维,按位算贡献)

题意:

给定两个序列 b [ 2 … n ] , c [ 2 … n ] b[2\dots n],c[2\dots n] b[2n],c[2n],它们分别表示: b i = a i − 1 ∣ a i ,   c i = a i − 1 + a i b_i = a_{i-1}|a_i, \ c_i=a_{i-1}+a_i bi=ai1ai, ci=ai1+ai

现在要求存在多少种 a [ ] a[] a[] 序列满足 b [ ] , c [ ] b[],c[] b[],c[] 的限制。

题解:

x & y = ( x + y ) − x ∣ y x \& y = (x + y) - x | y x&y=(x+y)xy

已知: a i − 1 & a i a_{i-1} \& a_i ai1&ai a i − 1 ∣ a i a_{i-1}|a_i ai1ai,已知一个数就能退出所有数,那么枚举 a 1 a_1 a1 的每一位,分别判断该位有多少种数 ( c n t ) (cnt) (cnt)可以取,即 0 , 1 0,1 01 。然后计算该位的贡献 a n s = a n s × c n t ans = ans \times cnt ans=ans×cnt

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
const int N = 2e5 + 10;
const double pi = acos(-1);
const double eps = 1e-9;

int b[N], c[N];

bool check(int now, int OR, int AND) {
    bool f = true;
    if (now) {
        if ((!AND && !OR) || (!OR && AND)) f = false;
    }
    else {
        if ((AND && OR) || (!OR && AND)) f = false;
    }
    return f;
}

int main () {
    int n;
    scanf("%d", &n);
    for (int i = 2; i <= n; i++)
        scanf("%d", &b[i]);
    for (int i = 2; i <= n; i++)
        scanf("%d", &c[i]);
    for (int i = 2; i <= n; i++)
        c[i] = c[i] - b[i];

    ll ans = 1;
    for (int i = 0; i <= 30; i++) {
        int cnt = 0;
        // 第i位 选 1
        int now = 1;
        bool ok = true;
        for (int j = 2; j <= n; j++) {
            int AND = (c[j] >> i) & 1;
            int OR = (b[j] >> i) & 1;
            if (!check(now, OR, AND)) {
                ok = false;
                break;
            }
            if (now) now = AND;
            else now = OR;
        }
        if (ok) cnt++;

        // 选0
        now = 0;
        ok = true;
        for (int j = 2; j <= n; j++) {
            int AND = (c[j] >> i) & 1;
            int OR = (b[j] >> i) & 1;
            if (!check(now, OR, AND)) {
                ok = false;
                break;
            }
            if (now) now = AND;
            else now = OR;
        }
        if (ok) cnt++;
//        cout << cnt << "\n";
        ans *= cnt;
    }
    printf("%lld\n", ans);
    return 0;
}

F-Robots(离线分治)

题目:

有一个 01 01 01 二维平面, 0 0 0 表示可以走, 1 1 1 表示不能走。

存在 3 3 3 种机器人,一种是只能往下走,一种是只能往右种,还有一种是可以往右也能往下。

现在给你 q q q 个机器人,分别给定机器人的种类、起点、终点,判断每一个机器人是否可以走到终点

题解:

看了看题解,离线分治,讲得非常简单(指写的内容),完全不会实现 …。然后开始瞪标程,等待顿悟。

正解:

对列进行分治,每次将区域分为两块 [ l , m i d ] , [ m i d , r ] [l,mid],[mid,r] [l,mid],[mid,r]

首先,对于分治区间内的所有节点都要有一个 长度为 n n n (分治列的情况下)的状态。

对于两个区间的 m i d mid mid 边界,要单独处理,对于每个点,如果为 1 1 1,说明不可经过,该点的状态置为全 0 0 0 ,若该点为 1 1 1 ,将该点的这个位置的状态置为 1 1 1,然后再 o r or or 上它的前一个状态(左半边区域是倒着扫上来,右半边区域是顺着扫下去)。

对于左半部分区间, b i t s e t < N > l m [ N ] [ N ] bitset<N>lm[N][N] bitset<N>lm[N][N] l m [ i ] [ j ] lm[i][j] lm[i][j] 表示 点 ( i , j ) (i,j) (i,j) 可以走到 m i d mid mid 线上的哪些为 0 0 0 的点,

如果当前点 ( i , j ) (i,j) (i,j) 1 1 1,即不可通过,那么该点的状态为全 0 0 0

如果当前点 ( i , j ) (i,j) (i,j) 0 0 0,即可以通过,那么可以得到点 ( i , j ) (i,j) (i,j) 的状态为 l m [ i + 1 ] [ j ]   ∣   l m [ i ] [ j + 1 ] lm[i+1][j] \ | \ lm[i][j + 1] lm[i+1][j]  lm[i][j+1]

对于右半部分区间, b i t s e t < N > m r [ N ] [ N ] bitset<N>mr[N][N] bitset<N>mr[N][N] m r [ i ] [ j ] mr[i][j] mr[i][j] 表示 点 ( i , j ) (i,j) (i,j) 可以由 m i d mid mid 线上的哪些为 0 0 0 的点转移到。

如果当前点 ( i , j ) (i,j) (i,j) 1 1 1,即不可通过,那么该点的状态为全 0 0 0

如果当前点 ( i , j ) (i,j) (i,j) 0 0 0,即可以通过,那么可以得到点 ( i , j ) (i,j) (i,j) 的状态为 m r [ i − 1 ] [ j ]   ∣   m r [ i ] [ j − 1 ] mr[i-1][j] \ | \ mr[i][j - 1] mr[i1][j]  mr[i][j1]

最后,对于起点在左半边区域,终点在右半边区域的机器人,可以得到答案。

然后对于起点和终点均在一个区域的机器人,可以将这些机器人分为两类,往左右两个区间递归下去。

代码:

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

const int N = 5e5 + 10;

struct node{
    pair<int, int> s, t;
    int id;
    node() {}
    node(pair<int, int> _s, pair<int, int> _t, int _id) {
        s = _s; t = _t; id = _id;
    }
};

int n, m;
char str[N];
int g[505][505], up[505][505], le[505][505];
int ans[N];
bitset<505> lm[505][505], mr[505][505]; // lm[i][j] 表示点(i,j)可以到达mid线上的哪些点
                                        // mr[i][j] 表示点(i,j)可以由mid线上哪些点移动过来

void solve(int l, int r, vector<node> v) {
    if (l > r) return;
    int mid = (l + r) >> 1;

//    // 处理中间一段,每个位置是否可以从左半边移动到右半边
    for (int i = n; i >= 1; i--) { // n - 1 倒着上来,与移动方向有关
        lm[i][mid].reset();
        if (g[i][mid] == 0) {
            lm[i][mid].set(i);
            lm[i][mid] |= lm[i + 1][mid];
        }
    }
    for (int i = 1; i <= n; i++) { // 1 - n 原因同上
        mr[i][mid].reset();
        if (g[i][mid] == 0) {
            mr[i][mid].set(i);
            mr[i][mid] |= mr[i - 1][mid];
        }
    }


    // 处理 l-mid
    for (int j = mid - 1; j >= l; j--) {  // 左半块区域,每个点能到达的位置由 下方、右方 转移过来
        for (int i = n; i >= 1; i--) {
            lm[i][j].reset();
            if (g[i][j] == 0) {
                lm[i][j] |= lm[i][j + 1];
                lm[i][j] |= lm[i + 1][j];
            }
        }
    }

    // 处理 mid-r
    for (int j = mid + 1; j <= r; j++) {  // 右半块区域,每个点的状态由 上方、左方 转移过来
        for (int i = 1; i <= n; i++) {
            mr[i][j].reset();
            if (g[i][j] == 0) {
                mr[i][j] |= mr[i][j - 1];
                mr[i][j] |= mr[i - 1][j];
            }
        }
    }
    vector<node> vl, vr;
    for (node now : v) {
        pair<int, int> s = now.s;
        pair<int, int> t = now.t;
        if (s.second > mid) vr.push_back(now);
        else if (t.second < mid) vl.push_back(now);
        else {  // 起点在左半区域,终点在右半区域的点,可以算出答案
            if ((lm[s.first][s.second] & mr[t.first][t.second]).any()) {
                ans[now.id] = 1;
            }
        }
    }
    solve(l, mid - 1, vl);
    solve(mid + 1, r, vr);
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        scanf("%s", str + 1);
        for (int j = 1; j <= m; j++) {
            g[i][j] = str[j] - '0';
            up[i][j] = up[i - 1][j] + g[i][j];
            le[i][j] = le[i][j - 1] + g[i][j];
        }
    }
    int k;
    scanf("%d", &k);
    vector<node> qry;
    for (int i = 1; i <= k; i++) {
        int ty, x1, y1, x2, y2;
        scanf("%d%d%d%d%d", &ty, &x1, &y1, &x2, &y2);
        if (x1 > x2 || y1 > y2) continue;
        if (ty == 1) {      // 向下走
            if (y1 == y2 && up[x2][y2] - up[x1 - 1][y1] == 0) ans[i] = 1;
        }
        else if (ty == 2) { // 向右走
            if (x1 == x2 && le[x2][y2] - le[x1][y1 - 1] == 0) ans[i] = 1;
        }
        else {
            qry.push_back(node({x1, y1}, {x2, y2}, i));
        }
    }
    solve(1, m, qry);
    for (int i = 1; i <= k; i++) {
        if (ans[i]) printf("yes\n");
        else printf("no\n");
    }
    return 0;
}

/*
4 4
0000
0000
0001
0010
4
1 1 1 1 4
2 2 1 2 4
3 1 1 3 3
3 3 3 4 4
*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值