atcoder一些题目复盘以及思路

atcoder abc384 F double sum 2

题目大意:对于任意一个数,考虑它的二进制表示,从最低位开始删除连续的零,直到首次遇见1时停止也就说,对于函数f凡是mod2^x具有相同结果的变量其函数值一定相同,于是考虑从2^x的倍数下手,并且x\epsilon[0, log_21e7 + 1] 25左右。设函数g(x)表示a[i]中所有可以整除2^x的数,容易得出,g(x + 1)一定是g(x)的一个子集,因为能整除2^{x + 1}的一定能整除2^x反之则不一定,所以g(x)-g(x + 1)就表示所有能整除2^x的数,并且不大于2^{x + 1}的数的集合,(对于x从0枚举到25一定可以讨论完a中的所有数)那么\frac{g(x) - g(x + 1)}{2^x}就表示去掉这些数后面连续的0。现在回到这个题目,根据题意改变g(x)函数的定义,让他表示A_i + A_j的和那么现在要做的就是如何处理这个和。对于每个2^x,遍历数组a,引入数组cnt维护可以与当前a_i匹配的数的个数,val维护可以与当前a_i匹配的数的和,则容易得出\sum^{n}_{i = 1}cnt[y] * a[i] + val[y]其中y表示先前维护与a[i]在mod2^x意义下的补数,并且该补数的值为(2^x - a[i] mod2^x)mod2^x(该式子括号内容易理解,写出来同余式,移项即可,括号外又mod则是为了处理括号内为2^x的情况),最后将值赋给g(x)即可。g数组处理过后,执行\frac{g(x) - g(x + 1)}{2^x}即可

代码:
 

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

typedef long long ll;
const ll N = 5e7 + 10;

ll cnt[N], val[N];

int main() {
    int n;
    cin >> n;
    vector<ll> a(n + 1);
    for(int i = 1; i <= n; i ++ ) cin >> a[i];
    vector<int> g(27);
    for(int bit = 0; bit <= 25; bit ++ ) {
        for(int i = 0; i <= 50000000; i ++ ) val[i] = cnt[i] = 0;
        ll res = 0;
        ll k = 1ll << bit;
        for(int i = 1; i <= n; i ++ ) {
            cnt[a[i] % k] ++;
            val[a[i] % k] += a[i];
            res += cnt[(k - a[i] % k) % k] * a[i] + val[(k - a[i] % k) % k];
        }
        g[bit] = res;
    }

    ll ans = 0;
    for(int i = 0; i <= 25; i ++ ) ans += (g[i] - g[i + 1]) / (1ll << i);
    cout << ans;
    return 0;
}

atcoder abc386 D Diagonal Separation

大致题意:我们目标的矩阵样式是矩阵的左上角是黑色,右下角是白色,给你m块固定颜色的网格,问是否可以将矩阵变成目标样式,类似于这样

m的值是在可控的范围内的,考虑从m下手,现在思考当黑色色块和白色色块在满足什么条件时不满足题意。先将所有的黑色色块放入网格中,再加入白色色块判断是否合法,很显然,在插入白色色块的过程中,如果存在黑色色块比该白色色块更低,并且横轴坐标大于等于该白色色块的坐标,那么就不满足题意。现在考虑如何实现这件事情,其实问题的本质就是找到该白色色块下方所有黑色色块横坐标的最大值,再将这个最大值和白色色块横坐标判断。线段树即可。

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

const int N = 2e5 + 10;

struct node {
    int l, r, val;
}tr[N * 4];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int n, m;
    cin >> n >> m;
    vector<pair<int, int>> op;
    vector<pair<int, int>> op1;
    
    vector<int> alls;
    for(int i = 1; i <= m; i ++ ) {
        int x, y;
        char co;
        cin >> x >> y >> co;
        if(co == 'B') op1.push_back({x, y});
        else op.push_back({x, y});
    }
    
    for(auto t: op1) alls.push_back(t.first);
    alls.push_back(-1);
    sort(alls.begin(), alls.end());
    alls.erase(unique(alls.begin(), alls.end()), alls.end());
    
    auto find = [&](int x) {
        int l = 0, r = alls.size() - 1;
        while(l < r) {
            int mid = l + r >> 1;
            if(alls[mid] >= x) r = mid;
            else l = mid + 1;
        }  
        return l;
    };
    
    
    /
    function<void(int, int, int)> build = [&](int u, int l, int r) {
        tr[u].l = l, tr[u].r = r;
        if(l == r) return;
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
    };
    
    build(1, 1, m + 1);
    
    auto pushup = [&](int u) {
        tr[u].val = max(tr[u << 1].val, tr[u << 1 | 1].val);
    };
    
    function<void(int, int, int)> insert = [&](int u, int id, int x) {
        if(tr[u].l == id && tr[u].r == id) tr[u].val = max(tr[u].val, x);
        else {
            int mid = tr[u].l + tr[u].r >> 1;
            if(mid >= id) insert(u << 1, id, x);
            else insert(u << 1 | 1, id, x);
            pushup(u);
        }
    };
    
    function<int(int, int, int)> query = [&](int u, int l, int r) {
        if(tr[u].l >= l && tr[u].r <= r) return tr[u].val;
        int maxv = 0;
        int mid = tr[u].l + tr[u].r >> 1;
        if(mid >= l) maxv = query(u << 1, l, r);
        if(mid < r) maxv = max(maxv, query(u << 1 | 1, l, r));
        return maxv;
    };
    /
    
    
    if(op1.empty() || op.empty()) {
        cout << "Yes";
        return 0;
    }
    
    for(auto t: op1) {
        int x = t.first, y = t.second;
        insert(1, find(x), y);
    }
    
    bool ok = true;
    for(auto t: op) {
        int x = t.first, y = t.second;
        int pos = find(x);
        if(alls[pos] >= x) {
            if(y <= query(1, pos, m + 1)) ok = false;
        }
    }
    
    if(ok) cout << "Yes";
    else cout << "No";
    return 0;
}

最初一直wa一个点,debug后发现当时insert函数中

if(tr[u].l == id && tr[u].r == id) tr[u].val = x;

这样就可能导致当前更新的值将之前的值覆盖,解决方法也很简单,取max即可

并且由于坐标的值域很大,离散化即可。

赛后发现每次查找的区间一定是原串的一个后缀,这样的话其实没有必要用线段树进行维护,将所有的色块按照纵坐标排序后,同时维护最靠右的黑色色块的坐标,最靠左的白色色块坐标即可

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

int main() {
    int n, m;
    cin >> n >> m;
    struct node {
        int x, y, color;
        bool operator < (const node &W) const {
            if(x == W.x) return color < W.color;
            return x < W.x;
        }
    };

    vector<node> alls(m + 1);
    for(int i = 1; i <= m; i ++ ) {
        int x, y;
        char c;
        cin >> x >> y >> c;
        alls[i] = {x, y, c == 'B'};
    }

    sort(alls.begin() + 1, alls.end());
    bool ok = true;
    int maxb = -1, minw = n + 1;
    for(int i = 1; i <= m; i ++ ) {
        int x = alls[i].x, y = alls[i].y, type = alls[i].color;
        if(type) {
            maxb = max(y, maxb);
            if(maxb >= minw) ok = false;
        } else {
            minw = min(minw, y);
            maxb = -1;
        }
    }

    if(ok) cout << "Yes";
    else cout << "No";
    return 0;
}

这里在扫到白色色块时将黑色色块最靠右的点初始化是因为,位于白色色块之上的点实际上不会对当前白色色块产生影响(因为已经判断过了,重复判断反而会导致答案错误),并且在黑白色块高度相同时要先更新白色色块,因为代码中进行判断是否合法的是黑色色块。

E Maximize XOR

对于一个组合数来说1e6是一个很小的数,到这里就可以直接爆搜了

剪枝策略:后缀XOR优化

代码:
 

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

typedef long long ll;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    ll n, k;
    cin >> n >> k;
    vector<ll> a(n + 1);
    vector<ll> suf(n + 2);
    for(int i = 1; i <= n; i ++ ) cin >> a[i];
    for(int i = n; i >= 1; i -- ) suf[i] = suf[i + 1] ^ a[i];
    ll ans = 0;
    function<void(int, ll, int)> dfs = [&](int u, ll now, int start) {
        if(u == k) {
            ans = max(ans, now);
            return;
        }    
        
        if(n - start + 1 + u == k) {
            ans = max(ans, now ^ suf[start]);
            return;
        }
        if(n - start + 1 + u < k) return;
        for(int i = start; i <= n; i ++ ) dfs(u + 1, now ^ a[i], i + 1);
    };
    
    dfs(0, 0, 1);
    cout << ans;
    return 0;
}

F operate K

题目大意:同最短编辑距离,只不过这里有次数限制,字符串的长度也很大

先简单回忆一下最短编辑距离dp(i, j)表示使得串s[1, i]与串t[1, j]相同的最短编辑距离。

状态转移方程如下:

if(a[i] == b[j]) dp[i][j] = dp[i - 1][j - 1];
            else {
                dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);
                dp[i][j] = min(dp[i][j], dp[i - 1][j - 1] + 1);
            }

第一个if条件表示接下来要匹配的字符相同,因此不需要操作。else中第一个转移表示接下来要匹配的串不同,要进行替换操作,再下面的转移其实是两种情况的合并:串s与串t的前j - 1个相同,需要删除,串s的前i - 1与串t相同,需要插入

初始化:
 

for(int i = 1; i <= n; i ++ ) dp[i][0] = i;
for(int j = 1; j <= m; j ++ ) dp[0][j] = j;

现在回到该题,该题目的串的长度很大,因此要在dp数组上做出一些变动。注意到操作距离k很小,换句话来说,若现在匹配到串s的第i位,那么对于串t的j位置,满足j < i - k, j > i + k的位置都是不需要讨论的,因此可以将dp数组的第二维表示成偏移量,由于这个偏移量可能是负数,再加上一个偏移量(可以取k的最大值20)将其置为正数即可

代码:

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

const int N = 5e5 + 10;
const int INF = 0x3f3f3f3f;
const int M = 22;

int dp[N][M * 2];//二维表示偏移量

int main() {
    int k;
    string s, t;
    cin >> k >> s >> t;
    int n = s.length();
    int m = t.length();
    s = ' ' + s;
    t = ' ' + t;

    if(abs(n - m) > k) {
        cout << "No";
        return 0;
    }
    
    memset(dp, 0x3f, sizeof dp);
    for(int i = M - k; i <= M + k; i ++ ) {
        int j = i - M;
        if(j < 0 || j > m) continue;
        dp[0][i] = j;
    }

    for(int i = 0; i <= min(k, n); i ++ ) {
        dp[i][M - i] = i;
    }

    for(int i = 1; i <= n; i ++ ) {
        for(int j = M - k; j <= M + k; j ++ ) {
            int now = i + j - M;
            if(now < 0 || now > m) continue;
            if(dp[i - 1][j] != INF && s[i] == t[now]) dp[i][j] = min(dp[i][j], dp[i - 1][j]);
            else if(s[i] != t[now]) {
                if(dp[i - 1][j] != INF) dp[i][j] = min(dp[i][j], dp[i - 1][j] + 1);//replace
                if(/*now + 1 <= m && */dp[i - 1][j + 1] != INF) dp[i][j] = min(dp[i][j], dp[i - 1][j + 1] + 1);
                if(now - 1 >= 0 && dp[i][j - 1] != INF) dp[i][j] = min(dp[i][j], dp[i][j - 1] + 1);
            }
        }
    }

    bool ok = false;
    for(int i = M - k; i <= M + k; i ++ ) {
        int now = n + i - M;
        if(now == m) {
            if(dp[n][i] <= k) ok = true;
        }
    }
    
    if(ok) cout << "Yes";
    else cout << "No";
    /*for(int i = 0; i <= n; i ++ ) {
        for(int j = M - k; j <= M + k; j ++ ) {
            int now = i + j - M;
            if(now < 0 || now > m) continue;
            cout << i << " \t" << j << " \t" << now << " \t" << dp[i][j] << "\n";
        }
        cout << "\n";
    }*/
    return 0;
}

代码这里加上限制条件会wa一个点,这里暂时没找到原因

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值