2024 杭电多校 第四场

 

分组

给定 n 个正整数 a1,a2,…,an (1≤ai<2m) 以及 0 到 2m−1 的权重 w0,w1,…,w2m−1;你需要把这 n 个正整数分成四组 A,B,C,D,令 f(A),f(B),f(C),f(D) 分别表示每组中所有数字的异或和,你的分组方案需要最小化 wf(A),wf(B),wf(C),wf(D) 的极差,即:

max(wf(A),wf(B),wf(C),wf(D))−min(wf(A),wf(B),wf(C),wf(D))

每组都可以为空,此时 f(⋅)=0。

(4≤n≤18, 1≤m≤10)。

dp+暴搜。注意到n和m的数据范围比较小,去枚举每个数字选或者不选的复杂度O(2^n),枚举所有异或和的情况O(2^m),O(2^(n+m))加点优化,似乎正好能卡过。

我们先不去考虑异或值有权重的情况,只考虑异或值极差最小的情况(假如我们能解决异或值极差最小的情况,无非是将原来的异或值套一层映射看成新的异或值)

           

我们先分为两个区间,其中a1假定一定为左区间,O(2^(n-1))枚举他们的分组情况,同时能得到左右区间异或和xor1,xor2。

我们假定左区间分出wa,wb,满足wa>wb,右区间分出wc,wd,满足wc>wd。此时我们只要知道wa就能确定wb,知道wc就能确定wd。

此时区间的极差有两种情况:

1)wa>=wc,wa取得最大值,最小值在wb和wd中产生,需要去维护此时wd可能取得的最大值。

2)wc>=wa,wc取得最大值,最小值在wb和wd中产生,需要去维护此时wb可能取得的最大值。

如何去维护wd和wb呢?

以1)为例,wa>=wc>=wd,wd是在[0,wc]里最大的那个,我们在枚举wc时可以立马确定wd取值,于是我们从小到大枚举wc,就可以知道在当前wc范围内wd的最大取值。但是我们没有必要另开一个数组去存wd在何种范围下的最大值,直接左右区间同时从小到大去枚举wa和wc即可。

最后将异或映射到权值,注意从权值小的异或开始枚举。

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
int n, m;
int a[20];
struct node {
    ll w;
    int x;
} b[2020];

bool cmp(node aa, node bb) {
    return aa.w < bb.w;
}

ll ans;
int sum;
int fl[20][2020], fr[20][2020];
ll val[2020];


void dfs(int pos, int xor1, int xor2) {

    if (pos == n) {

        ll ml = -1, mr = -1;
        for (int i = 0; i < (1 << m); i++) {

            int x = b[i].x;
            if (fl[n][x] == 1 && val[x] >= val[xor1 ^ x]) {
                ml = max(ml, val[xor1 ^ x]);
                if (mr != -1) ans = min(ans, val[x] - min(val[xor1 ^ x], mr));
            }

            if (fr[n][x] == 1 && val[x] >= val[xor2 ^ x]) {
                mr = max(mr, val[xor2 ^ x]);
                if (ml != -1) ans = min(ans, val[x] - min(val[xor2 ^ x], ml));
            }
        }

        return;
    }

    int v = a[pos + 1];
    for (int i = 0; i < (1 << m); i++) {
        fl[pos + 1][i] = fl[pos][i] | fl[pos][i ^ v];
        fr[pos + 1][i] = fr[pos][i];
    }
    dfs(pos + 1, xor1 ^ a[pos + 1], xor2);

    for (int i = 0; i < (1 << m); i++) {
        fl[pos + 1][i] = fl[pos][i];

        fr[pos + 1][i] = fr[pos][i] | fr[pos][i ^ v];
    }

    dfs(pos + 1, xor1, xor2 ^ v);

}

void solve() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++)cin >> a[i];
    for (int i = 0, x; i < (1 << m); i++) {
        cin >> x;
        val[i] = x;
        b[i].w = x;
        b[i].x = i;
    }
    sort(b, b + (1 << m), cmp);

    ans = 1e14;
    for (int i = 0; i < (1 << m); i++)fl[1][i] = fr[1][i] = 0;
    fl[1][0] = fr[1][0] = 1;//
    fl[1][a[1]] = 1;
    dfs(1, a[1], 0);

    cout << ans << '\n';

}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int _ = 1;
    cin >> _;
    while (_--) solve();
    return 0;
}

寻找宝藏

你得到了一张藏宝图,这上面标记了埋藏在地底下的 n 个海盗藏宝箱,编号依次为 1 到 n,第 i 个宝箱的坐标是 (i,pi),打开它你将得到 vi 枚金币。

你现在位于 (0,0),每次你可以选择从 (x,y) 移动到 (x+1,y) 或者 (x,y+1),当你位于某个宝箱正上方时,你将可以挖出它并拿走里面的所有金币。

不幸的是,有一个危险的陷阱区域没有被标记出来!通过多方调研,你得知这是一个边平行坐标轴的矩形区域,它是 m 种可能的位置分布之一。请对于每种可能的情况分别计算按照最优路线你能拿走多少金币。

假设陷阱区域的位置分布是第 i 种可能,假设它是以 (x1,y1) 和 (x2,y2) 为对顶点的矩形,那么 (x,y) 是陷阱当且仅当 x1≤x≤x2 且 y1≤y≤y2。你的路线不能途径任何陷阱点。当然,你只需要考虑当前的第 i 个矩形,不需要考虑其它 m−1 个矩形。

扫描线+树状数组

f i 表示 (0 , 0) ( i, p i ) 的最大收益,则 f i = v i + max j<i,p j <p i { f j } ,可以通过扫描线 +
树状数组在 O ( n log n ) 时间内求出。同理可以求出 g i 表示 ( i, p i ) ( n + 1 , n + 1) 的最大收益,
再令 h i 表示经过 ( i, p i ) 的最大收益,有 h i = f i + g i v i
直接经过左下区域或者右上区域的,比较它们的hx即可。

不经过左下或者右上区域任何一个点的,去枚举他们经过L1区域的最后一个点和经过R1区域的第一个点的情况。(这里就画了不经过右上区域的情况)

如果对于每一个禁区都去扫这六个区域肯定要超时,考虑从区域角度去从小往大扫,将每一个询问的禁区边界作为上界,用树状数组即可得到在该边界内区域贡献的最大值。

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
#define int long long
const int N = 1e6 + 10;

int a[N], w[N];
int f[N], g[N], h[N];
int n, m;
int d1[N];
int ans[N];

int lowbit(int x) {

    return x & (-x);
}

void add(int x, int w) {

    while (x <= n) {
        d1[x] = max(d1[x], w);
        x += lowbit(x);
    }
}

int query(int x) {
    int ans = 0;
    while (x) {
        ans = max(d1[x], ans);
        x -= lowbit(x);
    }
    return ans;
}

vector<int> xu[N], xd[N];
int yu[N], yd[N];

int fa[N], fb[N], ga[N], gb[N];

void solve() {
    cin >> n >> m;
    for (int i = 1, x, v; i <= n; i++) {
        cin >> x >> v;
        a[i] = x, w[i] = v;
        xu[i].clear();
        xd[i].clear();
    }

    for (int i = 1; i <= m; i++) {

        int x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;

        xu[x1].push_back(i);
        xd[x2].push_back(i);

        yu[i] = y2;
        yd[i] = y1;
    }

    for (int i = 1; i <= n; i++) d1[i] = 0;
    for (int i = 1; i <= n; i++) {
        for (auto idx: xu[i]) {
            fa[idx] = query(yu[idx]);
        }
        f[i] = query(a[i]) + w[i];
        add(a[i], f[i]);
        for (auto idx: xd[i]) {
            fb[idx] = query(yd[idx] - 1);
        }
    }

    for (int i = 1; i <= n; i++) d1[i] = 0;
    for (int i = n; i >= 1; i--) {
        for (auto idx: xd[i]) {
            fb[idx] += query(n - yd[idx] + 1);
        }
        g[i] = query(n - a[i] + 1) + w[i];
        add(n - a[i] + 1, g[i]);
        h[i] = f[i] + g[i] - w[i];
        for (auto idx: xu[i]) {
            fa[idx] += query(n - yu[idx]);
        }
    }
    for (int i = 1; i <= m; i++) {
        ans[i] = max(fa[i], fb[i]);
    }


    for (int i = 1; i <= n; i++) d1[i] = 0;
    for (int i = 1; i <= n; i++) {
        for (auto idx: xu[i]) {
            ans[idx] = max(ans[idx], query(n - yu[idx]));
        }
        add(n - a[i] + 1, h[i]);
    }

    for (int i = 1; i <= n; i++) d1[i] = 0;
    for (int i = n; i >= 1; i--) {
        for (auto idx: xd[i]) {
            ans[idx] = max(ans[idx], query(yd[idx] - 1));
        }
        add(a[i], h[i]);
    }
    
    for (int i = 1; i <= m; i++) {
        cout << ans[i] << '\n';
    }


}

signed main() {

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int T;
    cin >> T;
    while (T--)solve();


    return 0;
}

  • 28
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值