【多校训练】2022杭电多校1补题

官方数据和标程:2022杭电多校资料包

难度评价

5题快一百名,4题快两百名,3题慢四百名。补到6、7题还是很合理的,金牌补到9题以上。
B、K、L签到,A、C、H、I中等,D困难,E、F、G、J大难


A. String

字符串

待补


B. Dragon Slayer

暴力搜索

墙数量只有15,直接暴力枚举所有情况再搜索。坐标处理上可以扩大2倍,但会慢一些,容易被卡常,可以在枚举墙时剪枝。

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

const int mx[] = {-1, 0, 1, 0}, my[] = {0, 1, 0, -1};
bool ban[33][33], vis[33][33];

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while (t--)
    {
        int n, m, k, sx, sy, dx, dy;
        cin >> n >> m >> k, n *= 2, m *= 2;
        cin >> sx >> sy >> dx >> dy, sx = sx * 2 + 1, sy = sy * 2 + 1, dx = dx * 2 + 1, dy = dy * 2 + 1;
        vector<tuple<int, int, int, int>> wall(k);
        for (auto &[x1, y1, x2, y2] : wall)
            cin >> x1 >> y1 >> x2 >> y2, x1 *= 2, y1 *= 2, x2 *= 2, y2 *= 2;
        int ans = 15;
        for (int i = 0; i < (1 << k); i++)
        {
            memset(ban, 0, sizeof(ban));
            memset(vis, 0, sizeof(vis));
            int cnt = 0;
            for (int j = 0; j < k; j++)
            {
                if (i & (1 << j))
                    ++cnt;
                else
                {
                    auto [x1, y1, x2, y2] = wall[j];
                    if (x1 == x2)
                        for (int y = min(y1, y2); y <= max(y1, y2); y++)
                            ban[x1][y] = 1;
                    else
                        for (int x = min(x1, x2); x <= max(x1, x2); x++)
                            ban[x][y1] = 1;
                }
            }
            if (cnt >= ans)
                continue;

            queue<pair<int, int>> q;
            q.emplace(sx, sy);
            while (!q.empty())
            {
                auto [x, y] = q.front();
                q.pop();
                if (vis[x][y])
                    continue;
                vis[x][y] = 1;
                if (x == dx && y == dy)
                {
                    ans = min(ans, cnt);
                    break;
                }

                for (int j = 0; j < 4; j++)
                {
                    int nx = x + mx[j], ny = y + my[j];
                    if (nx >= 0 && nx <= n && ny >= 0 && ny <= m && !ban[nx][ny] && !vis[nx][ny])
                        q.emplace(nx, ny);
                }
            }
        }
        cout << ans << "\n";
    }

    return 0;
}

C. Backpack

DP暴力

考虑直接暴力dp,设 d p i , j , k dp_{i,j,k} dpi,j,k 表示对前 i i i 个物品,是否存在异或和为 j j j 且体积为 k k k 的方案,显然 d p i , j , k = d p i − 1 , j , k ∣ d p i − 1 , j ⊕ w [ i ] , k − v [ i ] dp_{i,j,k}=dp_{i-1,j,k}|dp_{i-1,j \oplus w[i],k-v[i]} dpi,j,k=dpi1,j,kdpi1,jw[i],kv[i],空间上可以滚动掉一维,但时间复杂度达到 O ( N 3 ) O(N^3) O(N3),太慢而无法通过。
考虑优化转移过程,对最后一维 k k k,如果用bitset直接移位,就无需遍历,时间复杂度降低至 O ( N 3 w ) O(\frac{N^3}{w}) O(wN3),可以通过。
本题关键在于想到枚举异或和,优化掉枚举体积,而不是反过来。

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

constexpr int MAXN = (2 << 9) + 5, N = 2 << 9;
int v[MAXN], w[MAXN];
bitset<MAXN> lst[MAXN], now[MAXN];

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while (t--)
    {
        int n, m;
        cin >> n >> m;
        for (int i = 0; i < n; i++)
            cin >> v[i] >> w[i];
        lst[0][0] = 1;
        for (int i = 0; i < n; i++)
        {
            for (int j = 0; j < N; j++)
                now[j] = lst[j ^ w[i]] << v[i], now[j] |= lst[j];
            for (int j = 0; j < N; j++)
                lst[j] = now[j];
        }
        int ans = -1;
        for (int i = N - 1; i >= 0; i--)
        {
            if (lst[i][m])
            {
                ans = i;
                break;
            }
        }
        cout << ans << "\n";

        for (int i = 0; i < N; i++)
            lst[i].reset();
    }

    return 0;
}

D. Ball

暴力排序

直接枚举点 O ( N 3 ) O(N^3) O(N3) 是不行的,考虑枚举边作为中位数,复杂度至少 O ( N 2 ) O(N^2) O(N2)
对当前边,长度为 l e n len len,两端点为 a , b a,b a,b,如果从小到大加入边,则现在已知所有从 a , b a,b a,b 分别出发的,长度不大于 l e n len len 的边,因为是两两距离,则同样已知所有长度不小于 l e n len len 的边。用 b s [ v ] bs[v] bs[v] 记录从点 v v v 出发且已加入的边的另一端,若 l e n len len 是质数,则答案加上 b s [ a ] ⊕ b s [ b ] bs[a] \oplus bs[b] bs[a]bs[b],用bitset优化,复杂度 O ( N 3 w ) O(\frac{N^3}{w}) O(wN3)。这是因为若一个点异或后值为1,一定同时存在一条已被加入的边和一条未被加入的边,所以该点可行,反之要么都已加入,要么都未加入,无效。
质数随便筛一下即可。

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

constexpr int MAXN = 2005;
int x[MAXN], y[MAXN], prime[200005];
bitset<MAXN> bs[MAXN];

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    prime[1] = 1;
    for (int i = 2; i <= 200000; i++)
        for (int j = i + i; j <= 200000; j += i)
            prime[j] = 1;
    int t;
    cin >> t;
    while (t--)
    {
        int n, m;
        cin >> n >> m;
        vector<tuple<int, int, int>> e;
        for (int i = 1; i <= n; i++)
            cin >> x[i] >> y[i];
        for (int i = 1; i <= n; i++)
            for (int j = i + 1; j <= n; j++)
                e.emplace_back(abs(x[i] - x[j]) + abs(y[i] - y[j]), i, j);
        sort(e.begin(), e.end());
        int ans = 0;
        for (auto [len, a, b] : e)
        {
            if (!prime[len])
                ans += (bs[a] ^ bs[b]).count();
            bs[a][b] = bs[b][a] = 1;
        }
        cout << ans << "\n";

        for (int i = 1; i <= n; i++)
            bs[i].reset();
    }

    return 0;
}

E. Grammar

图论

待补


F. Travel plan

图论数学DP

首要一点是观察出所给图是一个仙人掌,这是因为题目保证图中不存在长为偶数的环。简要证明如下:
假设有一条边 E E E 是两个环的公共边之一,若 E E E 长度为奇数,则这两个环其他部分的边权和必须都是偶数。无论其他公共边长度之和是奇数还是偶数,两环形成的大环的环长都将为偶数,与题设矛盾。 E E E 长度为偶数时同理。故没有一条边在多个环中,该图为仙人掌图。
于是用莫比乌斯反演变为求 g c d = k ∗ i   ( k ≥ 1 ) gcd=k*i \space (k \geq 1) gcd=ki (k1) 的路径数,分别对 i i i 个图,每次只考虑边权为 k ∗ i k*i ki 的边,建出圆方树,在树上dp即可求得。具体地,转移过程如下图所示。

在这里插入图片描述
注:本题极度卡常,本地0.6s提交3.7s,使用前向星和快读才能卡过。

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

constexpr int64_t MAXN = 2e5 + 5, MAXV = 1e5 + 5, mod = 998244353;
int n, m, L;
int st[MAXN], tp, low[MAXN], num[MAXN], dfn, nn;
int prime[MAXV], mu[MAXN];
bool vis[MAXV];
int64_t ans[MAXN], dp[MAXN];
vector<int> fac[MAXV];
vector<pair<int, int>> e[MAXN];
int head1[MAXN], head2[MAXN], tot1 = 1, tot2 = 1;
struct Edge
{
    int to, next;
} e1[MAXN << 3], e2[MAXN << 3];
void add1(int u, int v) { e1[++tot1].to = v, e1[tot1].next = head1[u], head1[u] = tot1; }
void add2(int u, int v) { e2[++tot2].to = v, e2[tot2].next = head2[u], head2[u] = tot2; }

namespace fastIO
{
#ifndef LOCAL
#define getchar() p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin), p1 == p2) ? EOF : *p1++
#define putchar(x) (p3 - obuf < 1000000) ? (*p3++ = x) : (fwrite(obuf, p3 - obuf, 1, stdout), p3 = obuf, *p3++ = x)
    char buf[1000000], *p1 = buf, *p2 = buf, obuf[1000000], *p3 = obuf;
    inline void flush() { fwrite(obuf, p3 - obuf, 1, stdout); }
#endif
    inline int read()
    {
        int s = 0;
        char ch = getchar();
        while (ch < '0' || ch > '9')
            ch = getchar();
        while (ch >= '0' && ch <= '9')
        {
            s = s * 10 + ch - '0';
            ch = getchar();
        }
        return s;
    }
    inline void write(int64_t x)
    {
        if (x > 9)
            write(x / 10);
        putchar(x % 10 + '0');
    }
};
using namespace fastIO;

void init()
{
    mu[1] = 1;
    for (int i = 2; i < MAXV; i++)
    {
        if (!vis[i])
            prime[++prime[0]] = i, mu[i] = -1;
        for (int j = 1; j <= prime[0] && i * prime[j] < MAXV; j++)
        {
            vis[i * prime[j]] = 1;
            if (i % prime[j] == 0)
            {
                mu[i * prime[j]] = 0;
                break;
            }
            else
                mu[i * prime[j]] = -mu[i];
        }
    }
    for (int i = 1; i < MAXV; i++)
        for (int j = i; j < MAXV; j += i)
            fac[j].push_back(i);
}

void tarjan(int v) // 广义圆方树
{
    low[v] = num[v] = ++dfn;
    st[++tp] = v;
    for (int i = head1[v]; i; i = e1[i].next)
    {
        int u = e1[i].to;
        if (!num[u])
        {
            tarjan(u);
            low[v] = min(low[u], low[v]);
            if (low[u] >= num[v])
            {
                ++nn;
                int z;
                do
                {
                    z = st[tp--], add2(z, nn), add2(nn, z);
                } while (z != u);
                add2(v, nn), add2(nn, v);
            }
        }
        else
            low[v] = min(low[v], num[u]);
    }
}

void dfs(int v, int fa, int id)
{
    for (int i = head2[v]; i; i = e2[i].next)
    {
        int u = e2[i].to;
        if (u != fa)
            dfs(u, v, id);
    }

    if (v <= n) // 圆点
    {
        dp[v] = 1;
        for (int i = head2[v]; i; i = e2[i].next)
        {
            int u = e2[i].to;
            if (u == fa)
                continue;
            ans[id] = (ans[id] + dp[v] * dp[u] % mod) % mod;
            dp[v] = (dp[v] + dp[u]) % mod;
        }
    }
    else // 方点,看是否对应环
    {
        dp[v] = 0;
        int deg = 0;
        for (int i = head2[v]; i; i = e2[i].next)
            ++deg;
        if (deg == 2) // 不对应,直接继承子结点dp值
        {
            for (int i = head2[v]; i; i = e2[i].next)
            {
                int u = e2[i].to;
                if (u != fa)
                    dp[v] = dp[u];
            }
        }
        else // 对应一个环,考虑除父结点外的环点对答案的贡献
        {
            for (int i = head2[v]; i; i = e2[i].next)
            {
                int u = e2[i].to;
                if (u == fa)
                    continue;
                ans[id] = (ans[id] + dp[v] * dp[u] % mod) % mod;
                dp[v] = (dp[v] + 2 * dp[u] % mod) % mod;
            }
        }
    }
}

int main()
{
    init();
    int t = read();
    while (t--)
    {
        n = read(), m = read(), L = read();
        while (m--)
        {
            int v = read(), u = read(), w = read();
            for (auto i : fac[w])
                e[i].emplace_back(v, u);
        }
        vector<int> vec;
        for (int i = 1; i <= L; i++)
        {
            for (auto [v, u] : e[i])
            {
                add1(v, u), add1(u, v);
                vec.push_back(v), vec.push_back(u);
            }
            nn = n, dfn = 0;
            for (auto v : vec)
                if (!num[v])
                    tp = 0, tarjan(v), dfs(v, 0, i);
            for (auto v : vec)
                head1[v] = head2[v] = num[v] = 0;
            for (int i = nn; i > n; i--)
                head2[i] = 0;
            tot1 = tot2 = 1;
            e[i].clear(), vec.clear();
        }

        int64_t res = 0;
        for (int i = 1; i <= L; i++)
        {
            for (int j = 2; i * j <= L; j++)
                ans[i] += (mu[j] * ans[i * j]) % mod + mod, ans[i] %= mod;
            res ^= ans[i];
        }
        write(res), putchar('\n');
        fill(ans + 1, ans + L + 1, 0);
    }
    flush();

    return 0;
}

G. Treasure

图论数据结构

注意到询问要求在图中移动时经过的边权 w ≤ y w \leq y wy ,这实际上提示我们建出kruskal重构树,转换为树上问题解决。
由kruskal重构树性质,按边权从小到大排序时,重构树根结点对应最大边权,从下往上,点对应的边权非降。于是对于询问,就是倍增往上跳到符合边权要求的最高点 v v v,答案为 v v v 子树中所有种类最大值之和。
考虑直接在每个结点处维护答案。注意到每种类型的点不超过10个,对于一种类型,如果建出其虚树,可以发现父结点 f a fa fa 和子结点 v v v 在原树路径上的所有点(不包括 f a fa fa)同时从 v v v 得到贡献 m x [ v ] mx[v] mx[v],这是一个路径加,可以在树剖后用树状数组维护。但是码量巨大,可以考虑转换为树上差分,则查询变为子树求和。对于初始答案,在虚树上跑一遍dfs即可求得;维护答案时,考虑跑两遍dfs,一次消去先前贡献,一次在 v a l [ x ] + = y val[x]+=y val[x]+=y 后计算新贡献。

注:
本题极度卡常,本地1.8s左右可过,也许赛时机子更快会影响小一些。然而标程本地就要跑4s
因为树剖爬链多带一个log,虽然在本题中应该很小,但建议不要写路径加,而是转换成树上差分,这样树状数组也省去引入差分数组维护区间加。
虚树有两种建法,一种是两次排序,另一种是单调栈,后者效率更高。
很大的效率瓶颈来自每次更新都从零重建虚树,考虑分别对每种类型保存虚树边,需要时直接加入。然而虚树上使用vector存边也存在一些效率弊端,本题只有十几个点,却需要零散使用 2 e 5 2e5 2e5 大小的vector数组,实测提交用时4.9s,极限卡过。考虑直接开一个几十大小的前向星,边的cache非常连续,提交用时3.7s左右。

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

constexpr int MAXN = 2e5 + 5;
int n, m, q, c[MAXN];
int64_t val[MAXN], mx[MAXN];
vector<int> G[MAXN], col[MAXN];

namespace fastIO
{
#ifndef LOCAL
#define getchar() p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin), p1 == p2) ? EOF : *p1++
#define putchar(x) (p3 - obuf < 1000000) ? (*p3++ = x) : (fwrite(obuf, p3 - obuf, 1, stdout), p3 = obuf, *p3++ = x)
    char buf[1000000], *p1 = buf, *p2 = buf, obuf[1000000], *p3 = obuf;
    inline void flush() { fwrite(obuf, p3 - obuf, 1, stdout); }
#endif
    inline int read()
    {
        int s = 0;
        char ch = getchar();
        while (ch < '0' || ch > '9')
            ch = getchar();
        while (ch >= '0' && ch <= '9')
            s = s * 10 + ch - '0', ch = getchar();
        return s;
    }
    inline void write(int64_t x)
    {
        if (x > 9)
            write(x / 10);
        putchar(x % 10 + '0');
    }
};
using namespace fastIO;

namespace Kruskal
{
    int tot, dsu[MAXN], ew[MAXN];
    struct edge
    {
        int x, y, w;
        edge(int x, int y, int w) : x(x), y(y), w(w) {}
        bool operator<(const edge &rhs) { return w < rhs.w; }
    };
    vector<edge> e;

    int find(int x) { return x == dsu[x] ? x : dsu[x] = find(dsu[x]); }

    void kruskal()
    {
        tot = n;
        iota(dsu + 1, dsu + n * 2 + 1, 1);
        sort(e.begin(), e.end());
        for (auto [v, u, w] : e)
        {
            v = find(v), u = find(u);
            if (v != u)
            {
                dsu[v] = dsu[u] = ++tot, ew[tot] = w;
                G[v].push_back(tot), G[tot].push_back(v);
                G[u].push_back(tot), G[tot].push_back(u);
            }
        }
        e.clear();
    }
}
using namespace Kruskal;

namespace FenwickTree
{
    int64_t sum[MAXN];

    inline int lowbit(int x) { return x & (-x); }

    void add(int pos, int64_t x)
    {
        for (; pos <= tot && pos; pos += lowbit(pos))
            sum[pos] += x;
    }

    int64_t query_presum(int pos)
    {
        int64_t ans = 0;
        for (; pos > 0; pos -= lowbit(pos))
            ans += sum[pos];
        return ans;
    }

    int64_t query_sum(int l, int r) { return query_presum(r) - query_presum(l - 1); }
}
using namespace FenwickTree;

namespace LCA
{
    int num[MAXN], bottom[MAXN], dep[MAXN], father[MAXN][18], lg[MAXN], dfn;

    void dfs(int v, int fa)
    {
        num[v] = ++dfn;
        dep[v] = dep[fa] + 1;
        father[v][0] = fa;
        for (int i = 1; i <= 17; i++)
            father[v][i] = father[father[v][i - 1]][i - 1];
        for (auto u : G[v])
            if (u != fa)
                dfs(u, v);
        bottom[v] = dfn;
    }

    int lca(int x, int y)
    {
        if (dep[x] < dep[y])
            swap(x, y);
        while (dep[x] > dep[y])
            x = father[x][lg[dep[x] - dep[y]] - 1];
        if (x == y)
            return x;
        for (int k = lg[dep[x]] - 1; k >= 0; k--)
            if (father[x][k] != father[y][k])
                x = father[x][k], y = father[y][k];
        return father[x][0];
    }

    int jump(int v, int y)
    {
        for (int i = 17; i >= 0; i--)
            if (father[v][i] != 0 && ew[father[v][i]] <= y)
                v = father[v][i];
        return v;
    }
}
using namespace LCA;

namespace VirtualTree
{
    int st[25], tp;
    int head[MAXN], tote = 1;
    struct Edge
    {
        int to, next;
    } le[25];
    void adde(int u, int v) { le[++tote].to = v, le[tote].next = head[u], head[u] = tote; }
    vector<pair<int, int>> E[MAXN];

    void update(int v, int fa, int op)
    {
        mx[v] = head[v] == 0 ? val[v] : 0;
        for (int i = head[v]; i; i = le[i].next)
        {
            int u = le[i].to;
            update(u, v, op);
            mx[v] = max(mx[v], mx[u]);
        }
        add(num[v], op * mx[v]), add(num[fa], -op * mx[v]);
    }

    void build(int color, int x, int y)
    {
        auto &ee = E[color];
        if (y == 0)
        {
            auto &p = col[color];
            sort(p.begin(), p.end(), [](int v, int u)
                 { return num[v] < num[u]; });
            st[tp = 1] = tot;
            for (auto v : p)
            {
                if (v != tot)
                {
                    auto LCA = lca(v, st[tp]);
                    if (LCA != st[tp])
                    {
                        while (num[LCA] < num[st[tp - 1]])
                            ee.emplace_back(st[tp - 1], st[tp]), --tp;
                        if (num[LCA] > num[st[tp - 1]])
                            ee.emplace_back(LCA, st[tp]), st[tp] = LCA;
                        else
                            ee.emplace_back(LCA, st[tp--]);
                    }
                    st[++tp] = v;
                }
            }
            for (int i = 1; i < tp; i++)
                ee.emplace_back(st[i], st[i + 1]);
        }
        for (auto [v, u] : ee)
            adde(v, u);
        if (y == 0)
            update(tot, 0, 1);
        else
            update(tot, 0, -1), val[x] += y, update(tot, 0, 1);
        for (auto [v, u] : ee)
            head[v] = 0;
        tote = 1;
    }
}
using namespace VirtualTree;

int main()
{
    for (int i = 1; i < MAXN; i++)
        lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
    int t = read();
    while (t--)
    {
        n = read(), m = read(), q = read();
        for (int i = 1; i <= n; i++)
            c[i] = read(), col[c[i]].push_back(i);
        for (int i = 1; i <= n; i++)
            val[i] = read();
        while (m--)
        {
            int x = read(), y = read(), w = read();
            e.emplace_back(x, y, w);
        }
        kruskal();
        dfn = 0;
        dfs(tot, 0);
        for (int i = 1; i <= n; i++)
            if (!col[i].empty())
                build(i, 0, 0);
        while (q--)
        {
            int op, x, y;
            op = read(), x = read(), y = read();
            if (op == 0)
                build(c[x], x, y);
            else
            {
                x = jump(x, y);
                write(query_sum(num[x], bottom[x])), putchar('\n');
            }
        }

        fill(sum + 1, sum + tot + 1, 0);
        for (int i = 1; i <= tot; i++)
            G[i].clear();
        for (int i = 1; i <= n; i++)
            col[i].clear(), E[i].clear();
    }
    flush();

    return 0;
}

H. Path

图论

很明显的分层图最短路,关键只在于处理经过特殊边后的跳转。如果没有经过特殊边,直接正常走,否则对于所有非出点,如果从未跳转过,尝试更新最短距离和入队,然后标记其已被跳转过,对于出点,正常走,只是边权减去 k k k
维护跳转点集需要频繁删除,可以考虑用set。由于最短路性质,每个点只会被跳转一次,因为后续跳转不会更优,因此正确性和复杂度都有保证。

注:本题卡常,换unordered_set或者pbds的hash_table,或者甚至你可以用链表;加fread快读。

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace __gnu_pbds;
using namespace std;

#define int int64_t
constexpr int MAXN = 1e6 + 5, INF = 1e18;
using tpl = tuple<int, int, int>;
vector<tpl> G[MAXN];
int n, m, s, k, d[MAXN][2], out[MAXN], num;
bool vis[MAXN][2];

namespace fastIO
{
#ifndef LOCAL
#define getchar() p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin), p1 == p2) ? EOF : *p1++
#define putchar(x) (p3 - obuf < 1000000) ? (*p3++ = x) : (fwrite(obuf, p3 - obuf, 1, stdout), p3 = obuf, *p3++ = x)
    char buf[1000000], *p1 = buf, *p2 = buf, obuf[1000000], *p3 = obuf;
    inline void flush() { fwrite(obuf, p3 - obuf, 1, stdout); }
#endif
    inline int read()
    {
        int s = 0;
        char ch = getchar();
        while (ch < '0' || ch > '9')
            ch = getchar();
        while (ch >= '0' && ch <= '9')
            s = s * 10 + ch - '0', ch = getchar();
        return s;
    }
    inline void write(int x)
    {
        if (x < 0)
            putchar('-'), x = -x;
        if (x > 9)
            write(x / 10);
        putchar(x % 10 + '0');
    }
};
using namespace fastIO;

void dijkstra()
{
    gp_hash_table<int, bool> ss;
    for (int i = 1; i <= n; i++)
        ss[i] = 1;
    for (int i = 1; i <= n; i++)
        vis[i][0] = vis[i][1] = 0, d[i][0] = d[i][1] = INF;
    d[s][0] = 0;
    priority_queue<tpl, vector<tpl>, greater<tpl>> q;
    q.emplace(0, s, 0);

    while (!q.empty())
    {
        auto [dis, v, stat] = q.top();
        q.pop();
        if (vis[v][stat])
            continue;
        vis[v][stat] = 1, ss.erase(v);

        if (!stat)
        {
            for (auto [u, w, sp] : G[v])
                if (!vis[u][sp] && dis + w < d[u][sp])
                    d[u][sp] = dis + w, q.emplace(d[u][sp], u, sp);
        }
        else
        {
            ++num;
            vector<int> del;
            for (auto [u, _, __] : G[v])
                out[u] = num;
            for (auto [u, _] : ss)
            {
                if (out[u] != num)
                {
                    if (dis < d[u][0])
                        d[u][0] = dis, q.emplace(dis, u, 0);
                    del.push_back(u);
                }
            }
            for (auto u : del)
                ss.erase(u);

            for (auto [u, w, sp] : G[v])
                if (!vis[u][sp] && dis + w - k < d[u][sp])
                    d[u][sp] = dis + w - k, q.emplace(d[u][sp], u, sp);
        }
    }
}

int32_t main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = read();
    while (t--)
    {
        n = read(), m = read(), s = read(), k = read();
        while (m--)
        {
            int x = read(), y = read(), w = read(), sp = read();
            G[x].emplace_back(y, w, sp);
        }
        dijkstra();
        for (int i = 1; i <= n; i++)
        {
            if (d[i][0] == INF && d[i][1] == INF)
                write(-1), putchar(' ');
            else
                write(min(d[i][0], d[i][1])), putchar(' ');
            G[i].clear();
        }
        putchar('\n');
    }
    flush();

    return 0;
}

I. Laser

计算几何

如果有解,那么一个点一定在横线/竖线/45度直线/135度直线上,不妨直接对第一个点枚举这四种情况,对于不同的情况,可以通过旋转坐标轴来统一化判断过程。于是对当前情况,永远视作第一个点在横线上,只是所有点坐标变为旋转后的坐标,当找到第一个不在横线上的点时,枚举其在竖线/45度直线/135度直线上的情况,分别与横线有1个交点,则该交点就为对应情况下的中心点,检查该中心点是否合法即可。

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

constexpr int MAXN = 1e5 + 5;
int n, x[MAXN], y[MAXN], nx[MAXN], ny[MAXN];

bool check(int cx, int cy)
{
    for (int i = 1; i <= n; i++)
    {
        int xx = nx[i] - cx, yy = ny[i] - cy;
        if (!(xx == 0 || yy == 0 || xx + yy == 0 || xx - yy == 0))
            return 0;
    }
    return 1;
}

bool judge()
{
    for (int i = 1; i <= n; i++)
        if (ny[i] != ny[1])
            return check(nx[i], ny[1]) || check(nx[i] - (ny[i] - ny[1]), ny[1]) || check(nx[i] + (ny[i] - ny[1]), ny[1]);
    return 1;
}

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while (t--)
    {
        cin >> n;
        for (int i = 1; i <= n; i++)
            cin >> x[i] >> y[i];
        bool ok = 0;
        // 枚举横线
        for (int i = 1; i <= n; i++)
            nx[i] = x[i], ny[i] = y[i];
        ok |= judge();
        // 枚举竖线
        for (int i = 1; i <= n; i++)
            nx[i] = y[i], ny[i] = x[i];
        ok |= judge();
        // 枚举45度直线
        for (int i = 1; i <= n; i++)
            nx[i] = x[i] - y[i], ny[i] = x[i] + y[i];
        ok |= judge();
        // 枚举135度直线
        for (int i = 1; i <= n; i++)
            nx[i] = x[i] + y[i], ny[i] = x[i] - y[i];
        ok |= judge();
        cout << (ok ? "YES\n" : "NO\n");
    }

    return 0;
}

J. Walk

数学分治DP

过难


K. Random

数学

可以认为所有数都是0.5,所以操作没有区别,那么答案就是 ( n − m ) / 2 (n-m)/2 (nm)/2 的逆元。

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

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while (t--)
    {
        int n, m;
        cin >> n >> m;
        cout << (n - m) * 500000004ll % 1000000007ll << "\n";
    }

    return 0;
}

I. Alice and Bob

博弈论

假设只有一种数,容易发现, c n t 0 ≥ 1 cnt_0 \geq 1 cnt01 c n t 1 ≥ 2 cnt_1 \geq 2 cnt12 c n t 2 ≥ 4 cnt_2 \geq 4 cnt24 c n t 3 ≥ 8 cnt_3 \geq 8 cnt38 等情况时Alice必胜,则数之间的大小转换显然。
推广到多种数,每次操作实际上要将集合等分,考虑类似1 2 2和2 2 3 3 3 3的情况,分法是1 | 2 2和2 2 | 3 3 3 3,最后实际上依然得到 c n t 0 ≥ 1 cnt_0 \geq 1 cnt01,因此只需判断是否有 ∑ i = 0 n c n t i 2 i ≥ 1 \sum_{i=0}^{n}\cfrac{cnt_i}{2^i} \geq 1 i=0n2icnti1,注意这里的除法不是下取整的,但由于范围巨大也不可能用浮点除法,不妨把位权乘上 2 n 2^n 2n,那么逆序加就行了。

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

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while (t--)
    {
        int n;
        cin >> n;
        vector<int> a(n + 1);
        for (auto &x : a)
            cin >> x;
        for (int i = n; i > 0; i--)
            a[i - 1] += a[i] / 2;
        cout << (a[0] ? "Alice\n" : "Bob\n");
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NoobDream_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值