2022”杭电杯“中国大学生算法设计超级联赛(1)3 4 8题解

这一场中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的曼哈顿距离是否小于当前两点的曼哈顿距离,对于枚举的两点ab,只要将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";
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值