这一场中bitset用得比较多,比较考验位运算和状态压缩思想。
1003-Backpack
题目大意:
将若干件物品装入背包,问是否存在一种方案将背包装满,且背包中的物品异或值最大。
思路:
01背包的变题,很容易就想到用dp[i][j][k]
来表示前i件物品中取若干件,异或和为j,体积为k的方案是否存在。
状态转移方程为dp[i][j][k] = dp[i-1][j][k] | dp[i-1][j^w][k-v]
,如果开三维数组的话,先不说时间复杂度能否接受,空间都不够用。
由于只记录是否存在,因此考虑用二进制来进行状态压缩。于是用bitset中的第j位表示体积为j的方案是否存在。复杂度为常数比较大的O(n2)。
再用滚动数组进行优化,只用开两个一维bitset数组即可。
AC代码:
#include <bits/stdc++.h>
const int N = 1024;
using namespace std;
bitset<N> f[N], g[N]; // f[i][j]表示异或值为i体积为j的情况是否存在
int n, m;
void solve()
{
for (int i = 0; i < N; i++)
f[i].reset(), g[i].reset();
f[0][0] = 1; //异或和为0,体积为0的情况存在
cin >> n >> m;
int v, w;
for (int i = 1; i <= n; i++)
{
cin >> v >> w;
for (int j = 0; j < N; j++)
{
g[j] = f[j ^ w];
g[j] <<= v;
}
for (int j = 0; j < N; j++)
f[j] |= g[j];
}
for (int i = N - 1; i >= 0; i--)
{
if (f[i][m])
{
cout << i << endl;
return;
}
}
cout << "-1" << endl;
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int T;
cin >> T;
while (T--)
solve();
return 0;
}
1004-Ball
题目大意:
给出n个点,求出所有三个点的组合,使得这三个点之间曼哈顿距离的中位数是素数。输出组合的个数。
思路:
最容易想到的方法是枚举两个点的组合,将这两个点的曼哈顿距离作为中位数,然后确定第三个点。但是这样的复杂度是O(n3),想要通过此题比较困难。
考虑对这种方法进行优化:
当枚举两个点时,如果能快速地获得符合的第三个点数,就可以复杂度降到O(n2)。
首先将所有的边进行排序,然后从小到大进行枚举。
因为边已经是有序的了,可以用bitset来维护曼哈顿距离小于当前边的点。
用f[i][j]
来表示i和j的曼哈顿距离是否小于当前两点的曼哈顿距离,对于枚举的两点a
和b
,只要将f[a]
和f[b]
作异或运算,结果中1的个数就是符合的第三点数,因为只有0和1异或才会得到1,因此所有的1一定是大于当前的曼哈顿距离和小于当前的曼哈顿距离的异或。枚举完a和b后,更新f[a]
和f[b]
,令f[a][b]=1;f[b][a]=1;
复杂度为O(n3/64)可以看作是常数较大的O(n2)。
AC代码:
#include <bits/stdc++.h>
const int M = 2e5 + 5;
const int N = 2e3 + 5;
using namespace std;
int prime[M];
bool isprime[M];
int get_prime(int n)
{
int cnt = 0;
for (int i = 1; i <= n; i++)
isprime[i] = 1;
isprime[1] = 0;
for (int i = 2; i <= n; i++)
{
if (isprime[i]) prime[++cnt] = i;
for (int j = 1; j <= cnt && i * prime[j] <= n; j++)
{
isprime[i * prime[j]] = 0;
if (i % prime[j] == 0) break;
}
}
return cnt;
}
struct edge
{
int x, y, w;
} e[N * N];
bool cmp(edge a, edge b) { return a.w < b.w; }
int x[N], y[N];
bitset<N> f[N]; // f[i][j]表示边(i,j)是否小于等于当前的边长
inline int dis(int a, int b) { return abs(x[a] - x[b]) + abs(y[a] - y[b]); }
void solve()
{
int n, m, cnt = 0, ans = 0;
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> x[i] >> y[i];
f[i].reset();
}
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++)
e[cnt++] = {i, j, dis(i, j)};
sort(e, e + cnt, cmp);
for (int i = 0; i < cnt; i++)
{
if (isprime[e[i].w]) ans += (f[e[i].x] ^ f[e[i].y]).count(); //只有0(大于当前边)和1(小于等于当前边)异或等于1
f[e[i].x][e[i].y] = 1;
f[e[i].y][e[i].x] = 1;
}
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
get_prime(M - 1);
int T;
cin >> T;
while (T--)
solve();
return 0;
}
1008-Path
题目大意:
这道题的英文题面写得比较差,不建议看英文题面。
有n个点和m条边,边有两种类型,一种是普通边,一种是特殊边。
经过特殊边后可以以0
的花费到达任何不相邻的点,以花费wi - k
到达相邻的点。
求出起始点到每个点的最短距离,如果到达不了输出-1。
思路:
首先这道题是最短路的问题,考虑用堆优化的dijkstra算法。
当经过一条特殊边后,可以将所有与当前点不相邻的点进行更新,如果更新成功,那么更新后的值一定是这个点的最短距离,于是将更新后的点入队。
因此建一个双层图,同时在队列中的状态要记录这个点的上一步是否经过了特殊边。同时用set来维护还没有确定最短距离的点。
#include <bits/stdc++.h>
using namespace std;
#define rep(i, h, t) for (int i = h; i <= t; i++)
#define dep(i, t, h) for (int i = t; i >= h; i--)
#define ll long long
const int N = 5e5;
int n, m, s, k;
ll dis[N][2];
int gg[N];
bool vis[N][2];
struct re
{
ll a, b, p;
bool operator<(const re x) const
{
return b > x.b;
}
};
vector<re> ve[N];
priority_queue<re> Q;
int main()
{
ios::sync_with_stdio(false);
int T;
cin >> T;
int cnt = 0;
while (T--)
{
cin >> n >> m >> s >> k;
rep(i, 1, m)
{
int x, y, z, zz;
cin >> x >> y >> z >> zz;
ve[x].push_back({y, z, zz});
}
set<int> S;
rep(i, 1, n) if (i != s) S.insert(i);
rep(i, 1, n) dis[i][0] = dis[i][1] = 1e18, vis[i][0] = vis[i][1] = 0;
dis[s][0] = 0;
Q.push({s, 0, 0});
while (!Q.empty())
{
int x = Q.top().a;
int yy = Q.top().p, y;
Q.pop();
if (vis[x][yy]) continue;
vis[x][yy] = 1;
S.erase(x);
if (yy == 1)
{
cnt++;
for (auto v : ve[x])
{
gg[v.a] = cnt;
if (dis[v.a][v.p] > dis[x][yy] + v.b - k)
{
dis[v.a][v.p] = dis[x][yy] + v.b - k;
Q.push({v.a, dis[v.a][v.p], v.p});
}
}
vector<int> ve;
for (auto v : S)
{
if (gg[v] != cnt)
{
dis[v][0] = dis[x][yy];
Q.push({v, dis[v][0], 0});
ve.push_back(v);
}
}
for (auto v : ve)
S.erase(v);
}
else
{
for (auto v : ve[x])
{
if (dis[v.a][v.p] > dis[x][yy] + v.b)
{
dis[v.a][v.p] = dis[x][yy] + v.b;
Q.push({v.a, dis[v.a][v.p], v.p});
}
}
}
}
rep(i, 1, n) ve[i].clear();
rep(i, 1, n) if (min(dis[i][0], dis[i][1]) != 1e18) cout << min(dis[i][0], dis[i][1]) << " ";
else cout << -1 << " ";
cout << "\n";
}
}