分组
给定 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 个矩形。
扫描线+树状数组
不经过左下或者右上区域任何一个点的,去枚举他们经过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;
}