2021gdcpc完整题解,(几乎完整,C、E是真滴不会)

GDCPC2021

比赛链接


A

  • 题意
    • 给定 n,m
    • 求第 k 大的 ∣ x y ∣ , x ∈ [ 0 , n ] , y ∈ [ 0 , m ] |xy|, \quad x \in [0, n], y \in [0, m] xy,x[0,n],y[0,m]
  • 题解
    • 暴力堆搜索
#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
#include <set>
using namespace std;
using ll = long long;
using T = int;
T rad(); // quick read
const int inf = 0x3f3f3f3f;
#define rf(i, n) for (int i = 1; i <= (n); ++i)
const int max_size = 5 + 100;
const int maxn = 5 + 100;

struct Node {
    int x, y;
    bool operator<(const Node &r) const { return (ll)x * y < (ll)r.x * r.y; }
};

priority_queue<Node> qe;
set<pair<int, int>> s;

void insert(int x, int y) {
    if (s.find({x, y}) != s.end()) return;
    qe.push({x, y}), s.insert({x, y});
}

int main() {
    int n = rad(), m = rad(), k = rad();
    insert(n, m);
    for (int i = 1; i < k; ++i) {
        int x = qe.top().x, y = qe.top().y;
        // printf("%d %d\n", x, y);
        qe.pop(), s.erase(s.find({x, y}));
        insert(x - 1, y), insert(x, y - 1);
    }
    printf("%lld", qe.top().x * (ll)qe.top().y);
}

T rad() {
    T back = 0;
    int ch = 0, posi = 0;
    for (; ch < '0' || ch > '9'; ch = getchar())
        posi = ch ^ '-';
    for (; ch >= '0' && ch <= '9'; ch = getchar())
        back = (back << 1) + (back << 3) + (ch & 0xf);
    return posi ? back : -back;
}

B

  • 题意

    • F F F 是兔子数列的集合
    • 将某个数 n 分解成兔子数列的某种表示,即求出 F F F 的某个子集 F ′ F^{\prime} F,使集合累加值为 n, ∑ f i ∈ F ′ = n \sum_{f_i \in F ^{\prime}} = n fiF=n
    • 求 n 的所有表示的累乘值之和,
    • 例: 7 = 5 + 2 = 5 + 1 + 1 = 3 + 2 + 1 + 1 7=5+2=5+1+1=3+2+1+1 7=5+2=5+1+1=3+2+1+1
    • 得: 5 ∗ 2 + 5 ∗ 1 ∗ 1 + 3 ∗ 2 ∗ 1 ∗ 1 = 21 5*2+5*1*1+3*2*1*1=21 52+511+3211=21
  • 题解

    • 压维线性dp && 数学优化(离谱)
    • 首先,我们默认兔子数列是从下标 1 开始的,即 f 1 = 1 , f 2 = 1 , f 3 = 2 f_1 = 1, f_2 = 1, f_3 = 2 f1=1,f2=1,f3=2
    • 我们指出兔子数列的一个性质: ∑ i = 1 n f i < 3 f n \sum_{i = 1}^n f_i < 3f_n i=1nfi<3fn
      • 证明:
        假设: ∑ i = 1 n f i < k f n \sum_{i = 1}^n f_i < k f_n i=1nfi<kfn 成立
        ∴ ∑ i = 1 n + 1 f i = ∑ i = 1 n f i + f n + 1 < k f n + f n + 1 \therefore \sum_{i = 1}^{n + 1} f_i = \sum_{i = 1}^{n} f_i + f_{n + 1} < k f_n + f_{n + 1} i=1n+1fi=i=1nfi+fn+1<kfn+fn+1
        考察: k f n + f n + 1 < k f n + 1 kf_n + f_{n + 1} < kf_{n + 1} kfn+fn+1<kfn+1
        ⇔ k f n + ( 1 − k ) f n + 1 < 0 \Leftrightarrow kf_n + (1 - k)f_{n + 1} < 0 kfn+(1k)fn+1<0
        ⇔ k f n + ( 1 − k ) f n + ( 1 − k ) f n − 1 < 0 \Leftrightarrow kf_n + (1 - k)f_{n} + (1-k)f_{n - 1} < 0 kfn+(1k)fn+(1k)fn1<0
        ⇔ f n + ( 1 − k ) f n − 1 < 0 \Leftrightarrow f_n + (1 - k)f_{n - 1} < 0 fn+(1k)fn1<0
        ⇔ f n − 2 + ( 2 − k ) f n − 1 < 0 \Leftrightarrow f_{n - 2} + (2 - k)f_{n - 1} < 0 fn2+(2k)fn1<0
        ⇔ f n − 2 < ( k − 2 ) f n − 1 \Leftrightarrow f_{n - 2} < (k - 2)f_{n - 1} fn2<(k2)fn1
        显然,这对 k ≥ 3 k \ge 3 k3 成立
        ∴ ∑ i = 1 n + 1 f i < 3 f n + 1 \therefore \sum_{i = 1}^{n + 1} f_i < 3f_{n + 1} i=1n+1fi<3fn+1
        那么由数学归纳法: ∑ i = 1 n + 1 f i < 3 f n + 1 , ∀ n ∈ R \sum_{i = 1}^{n + 1} f_i < 3f_{n + 1}, \quad \forall n \in R i=1n+1fi<3fn+1,nR 成立
    • ( i , j ) (i,j) (i,j) 表示前 i 项(包含第 i 项)构造 j 的方案,那么 f i < j < p r e j f_i < j < pre_j fi<j<prej 时,方案才有意义。这意味着前 i 项只能转移到 j ∈ [ f i , 3 f i ) j \in [f_i, 3f_i) j[fi,3fi)
    • d p ( j ) dp(j) dp(j) 表示题目中的 W ( F ( j ) ) W(F(j)) W(F(j)),那么状态转移方程为: d p j = ∑ f i × d p i dp_j = \sum f_i \times dp_i dpj=fi×dpi
    • 由于上述 dp 方程合法范围难以把握,所以直接枚举 i 的扩散: d p j + = f i × d p i , j ∈ [ f i , 3 f i ] dp_j += f_i \times dp_i, j \in [f_i, 3f_i] dpj+=fi×dpi,j[fi,3fi]
    • 值得一提的是,这不就是个压维0-1 背包(队友提醒了我才反应过来)
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
using ll = long long;
using T = int;
T rad(); // quick read
const int inf = 0x3f3f3f3f;
#define rf(i, n) for (int i = 1; i <= (n); ++i)
const int max_size = 5 + 100;
const int maxn = 5 + 1e7;

const int mod = 998244353;
int f[max_size];
ll pre[max_size];
int fsiz;
inline void init_fi() {
    f[++fsiz] = 1, pre[fsiz] = 1;
    while (f[fsiz] < maxn) {
        ++fsiz, f[fsiz] = f[fsiz - 1] + f[fsiz - 2];
        pre[fsiz] = pre[fsiz - 1] + f[fsiz];
    }
}

int dp[maxn];

int main() {
    init_fi();
    dp[0] = 1, dp[1] = 1;
    for (int i = 2; i <= fsiz; ++i) {
        for (int j = min(3 * f[i], maxn - 1); j >= f[i]; --j) {
            dp[j] = (dp[j] + (ll)f[i] * dp[j - f[i]]) % mod;
        }
    }
    int Q = rad();
    while (Q--) {
        int x = rad();
        printf("%d\n", dp[x]);
    }
}

T rad() {
    T back = 0;
    int ch = 0, posi = 0;
    for (; ch < '0' || ch > '9'; ch = getchar())
        posi = ch ^ '-';
    for (; ch >= '0' && ch <= '9'; ch = getchar())
        back = (back << 1) + (back << 3) + (ch & 0xf);
    return posi ? back : -back;
}

C

  • 题意

    • 给定主串 s,数值 k
    • 求长度不小于 k 的子串在 s 中出现次数的最大值
  • 题解?

    • 后缀数组?

D

  • 题意

    • 给定一个序列 a 1 a_1 a1 ~ a n a_n an
    • 操作:选择相邻的两个数,删除较小的那个,并将较大的元素的数值翻倍
      • 如果相等,则随机视某个数较大
    • 求操作 n - 1 次操作后,哪些元素可能是最后剩下的那个
  • 题解

    • dfs/双指针 && 剪枝
    • 检查每个数,用双指针模拟能否吃掉相邻的数
    • 当当前值不小于最大值时,剪枝,返回 1
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
using ll = long long;
using T = int;
T rad(); // quick read
const int inf = 0x3f3f3f3f;
#define rf(i, n) for (int i = 1; i <= (n); ++i)
const int max_size = 5 + 100;
const int maxn = 5 + 5e5;

int n;
int a[maxn];

int up;
bool visit[maxn];
bool dfs(int num, int l, int r) {
    // printf("%d %d\n", l, r);
    if (l == 0 && r == n + 1) return 1;
    if (num >= up) return 1;
    if (l > 0 && num >= a[l])
        return dfs(num * 2, l - 1, r);
    else if (r <= n && num >= a[r])
        return dfs(num * 2, l, r + 1);
    return 0;
}

int main() {
    n = rad();
    up = 0;
    rf(i, n) a[i] = rad(), up = max(up, a[i]);
    int ans = 0;
    rf(i, n) visit[i] = dfs(a[i], i - 1, i + 1), ans += visit[i];
    printf("%d\n", ans);
    rf(i, n) if (visit[i]) printf("%d ", i);
}

T rad() {
    T back = 0;
    int ch = 0, posi = 0;
    for (; ch < '0' || ch > '9'; ch = getchar())
        posi = ch ^ '-';
    for (; ch >= '0' && ch <= '9'; ch = getchar())
        back = (back << 1) + (back << 3) + (ch & 0xf);
    return posi ? back : -back;
}

E

  • 题意
    • [ 0 , 1 0 n ] [0, 10^n] [0,10n] 中,能被 11 整除,或包含数码 k 的数的数量
    • n ∈ 1 e 6 , k ∈ 2 e 5 n \in 1e6, k \in 2e5 n1e6,k2e5
  • 题解?
    • 数位 dp

F

  • 题意

    • 给定序列 a 0 a_0 a0 ~ a n a_n an
    • ∑ i = 0 n ∑ j = 0 a i A ( i , j ) \sum_{i = 0}^n\sum_{j = 0}^{a_i}A(i,j) i=0nj=0aiA(i,j)
    • A ( n , m ) = { 1 , m = 0 0 , m > n n ! ( n − m ) ! , m < = n A(n, m) = \begin{cases} 1, &m = 0\\0, &m > n \\ \frac{n!}{(n-m)!}, &m <= n\end{cases} A(n,m)=1,0,(nm)!n!,m=0m>nm<=n
  • 题解

    • 数学 && 线段树
    • g ( x , l , r ) = ∑ i = l r A x i g(x, l, r) = \sum_{i = l}^r A_x^i g(x,l,r)=i=lrAxi f ( x , y ) = s u m ( x , 0 , y ) f(x, y) = sum(x, 0, y) f(x,y)=sum(x,0,y)
    • 那么问题转化:求 ∑ i = 0 n f ( i , a i ) \sum_{i = 0}^n f(i, a_i) i=0nf(i,ai)
    • 现在考察用最少时间获得任意的 f ( x , y ) f(x,y) f(x,y)
      我们给出两个公式:
      f ( x , y ) = 1 + x f ( x − 1 , y − 1 ) f(x, y) = 1 + xf(x - 1, y - 1) f(x,y)=1+xf(x1,y1)
      f ( x , y ) = f ( x , x ) − ∏ i = x − y x i × f ( x − y − 1 , x − y − 1 ) f(x, y) = f(x, x) - \prod_{i = x - y}^x i \times f(x - y - 1, x - y - 1) f(x,y)=f(x,x)i=xyxi×f(xy1,xy1)
      • 证明公式 2:
        ∵ A n m = n A n − 1 m − 1 \because A_n^m = n A_{n - 1}^{m - 1} Anm=nAn1m1
        ∴ g ( x , l , r ) = x × g ( x − 1 , l − 1 , r − 1 ) \therefore g(x, l, r) = x \times g(x - 1, l - 1, r - 1) g(x,l,r)=x×g(x1,l1,r1)
        由上述递推式得: g ( x , l , r ) = ∏ i = x − l + 1 x i × f ( x − l , r − l ) g(x, l, r) = \prod_{i=x - l + 1}^x i \times f(x - l, r - l) g(x,l,r)=i=xl+1xi×f(xl,rl)
        ∵ f ( x , y ) = f ( x , x ) − g ( x , y + 1 , x ) \because f(x, y) = f(x, x) - g(x, y + 1, x) f(x,y)=f(x,x)g(x,y+1,x)
        ∴ f ( x , y ) = f ( x , x ) − ∏ i = x − y x i × f ( x − y − 1 , x − y − 1 ) \therefore f(x, y) = f(x, x) - \prod_{i = x - y}^x i \times f(x - y - 1, x - y - 1) f(x,y)=f(x,x)i=xyxi×f(xy1,xy1)
    • 那么,使用公式 1 预处理所有 f ( x , x ) f(x,x) f(x,x),并使用线段树维护累乘值即可
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
using ll = long long;
using T = int;
T rad(); // quick read
const int inf = 0x3f3f3f3f;
#define rf(i, n) for (int i = 1; i <= (n); ++i)
const int max_size = 5 + 100;
const int maxn = 5 + 1e5;

const int mod = 998241383;

ll fxx[maxn]; // f(x, x)
inline void init() {
    fxx[0] = 1;
    for (int i = 1; i < maxn; ++i) fxx[i] = (1LL + i * fxx[i - 1]) % mod;
}

ll val[maxn << 2];
inline void maintain(int u) { val[u] = val[u << 1] * val[u << 1 | 1] % mod; }
void build(int u, int l, int r) {
    if (l == r) {
        val[u] = l;
        return;
    }
    int mid = l + r >> 1;
    build(u << 1, l, mid);
    build(u << 1 | 1, mid + 1, r);
    maintain(u);
}

ll ask(int u, int l, int r, int s, int t) {
    if (s <= l && r <= t) return val[u];
    int mid = l + r >> 1;
    if (t <= mid) return ask(u << 1, l, mid, s, t);
    if (s > mid) return ask(u << 1 | 1, mid + 1, r, s, t);
    return ask(u << 1, l, mid, s, t) * ask(u << 1 | 1, mid + 1, r, s, t) % mod;
}

inline int f(int x, int y) {
    if (x == y) return fxx[x];
    return ((fxx[x] - ask(1, 1, maxn, x - y, x) * fxx[x - y - 1]) % mod + mod) % mod;
}

int main() {
    init();
    build(1, 1, maxn);
    int Q = rad();
    while (Q--) {
        int n = rad();
        ll ans = 0;
        for (int i = 0; i <= n; ++i) {
            int ai = rad();
            ans += f(i, ai), ans %= mod;
        }
        printf("%d\n", ans);
    }
}

T rad() {
    T back = 0;
    int ch = 0, posi = 0;
    for (; ch < '0' || ch > '9'; ch = getchar())
        posi = ch ^ '-';
    for (; ch >= '0' && ch <= '9'; ch = getchar())
        back = (back << 1) + (back << 3) + (ch & 0xf);
    return posi ? back : -back;
}

G

  • 题意
    • 给出序列 a 1 a_1 a1 ~ a n a_n an
    • 两人轮流操作,Alice 先手
    • Alice 可以选择一个奇数并将其分成两个正数,或者删除一个等于1的数。
    • Bob 可以选择一个偶数并将其分成两个正数。
    • 若某个人无法移动,则输掉比赛。
    • 如果两个玩家的移动最佳,求谁是赢家。
  • 题解
    • 博弈论
    • 首先,最优决策应该是操作后使自己还能剩余更多的操作步骤
    • 考虑 bob 分解偶数
      • 若分解 2,则会给 alice 两步
      • 若分解非 2,则会给自己两步
    • 考虑 alice 分解奇数
      • 分解任何奇数都能给对方一步,给自己一步
      • 消除 1,则拖一回合
    • 最优决策:
      • bob 总是最后分解 2
      • alice 总是使偶数部分为 2,给 bob 最烂的牌
      • 那么整个过程变成:bob 尽量不分解 2,alice 拖足够多的回合。
    • 仔细一想就能发现,两人的最优决策都是把 x 拆成 x - 2 和 2。
    • 那么统计序列中大于 2 的偶数能被分解的次数,这是 bob 的操作次数;统计序列中的奇数能被分解的次数,这是 alice 的操作次数。作比较即可。
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
using ll = long long;
using T = int;
T rad(); // quick read
const int inf = 0x3f3f3f3f;
#define rf(i, n) for (int i = 1; i <= (n); ++i)
const int max_size = 5 + 100;
const int maxn = 5 + 1e5;

int n;
int a[maxn];

int main() {
    int Q = rad();
    while (Q--) {
        n = rad();
        ll alice = 0, bob = 0;
        rf(i, n) {
            int tmp = rad();
            if (tmp == 2) continue;
            if (tmp & 1)
                alice += tmp / 2 + 1;
            else
                bob += tmp / 2 - 1;
        }
        if (bob >= alice)
            puts("Bob");
        else
            puts("Alice");
    }
}

T rad() {
    T back = 0;
    int ch = 0, posi = 0;
    for (; ch < '0' || ch > '9'; ch = getchar())
        posi = ch ^ '-';
    for (; ch >= '0' && ch <= '9'; ch = getchar())
        back = (back << 1) + (back << 3) + (ch & 0xf);
    return posi ? back : -back;
}

H

  • 题意

    • 给定一个无向图,带边权,n 个点,m 条边,没有自环
    • 每次移动,其它的边权将变成 f ( x ) = 1 + x 1 − x ( m o d p ) f(x) = \frac{1 + x}{1 - x} \pmod p f(x)=1x1+x(modp)
    • 求从点 1 到点 n 的最短路径
  • 题解

    • 我们发现边权的变化具有周期 4,那么我们考虑转化边权
    • 将图中每个点复制 4 份,
      • 那么从点 i 的拷贝 0 到点 j 的拷贝 1,是通过 w i , j w_{i,j} wi,j
      • 那么从点 i 的拷贝 1 到点 j 的拷贝 2,是通过 f ( w i , j ) f(w_{i,j}) f(wi,j)
    • 这样便转化成求从 1 到 n 的四个拷贝中,最短路的最小值
#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
using ll = long long;
using T = int;
T rad(); // quick read
const int inf = 0x3f3f3f3f;
#define rf(i, n) for (int i = 1; i <= (n); ++i)
const int max_size = 5 + 100;
const int maxn = 5 + 2e5;

int n, m, mod;

void exgcd(ll a, ll b, ll &x, ll &y, ll &g) {
    if (b == 0)
        x = 1, y = 0, g = a;
    else
        exgcd(b, a % b, y, x, g), y -= a / b * x;
}

ll inv(ll a, ll b) { // ax = 1 mod b
    ll x, y, g;
    exgcd(a, b, x, y, g);
    return g == 1 ? (x + b) % b : -1;
}

inline ll fx(ll x) { return ((1 + x) * inv((1 - x + mod) % mod, mod)) % mod; }

struct Node {
    int to, w;
};
vector<Node> adj[maxn * 4];

inline void add(int l, int r, int w) {
    adj[l + 0 * n].push_back({r + 1 * n, w}), w = fx(w);
    adj[l + 1 * n].push_back({r + 2 * n, w}), w = fx(w);
    adj[l + 2 * n].push_back({r + 3 * n, w}), w = fx(w);
    adj[l + 3 * n].push_back({r + 0 * n, w}), w = fx(w);
}

bool visit[maxn * 4];
ll dis[maxn * 4];

struct cmp {
    bool operator()(int l, int r) { return dis[l] > dis[r]; }
};

void dijkstra() {
    priority_queue<int, vector<int>, cmp> qe;
    memset(dis, inf, sizeof(dis));
    dis[1] = 0, visit[1] = 1, qe.push(1);
    while (!qe.empty()) {
        int now = qe.top();
        qe.pop();
        for (auto p : adj[now]) {
            if (visit[p.to]) continue;
            visit[p.to] = 1, dis[p.to] = dis[now] + p.w, qe.push(p.to);
        }
    }
}

int main() {
    n = rad(), m = rad(), mod = rad();
    rf(i, m) {
        int l = rad(), r = rad(), w = rad();
        add(l, r, w);
        // rf(j, 4) printf("%d ", w), w = fx(w);
        // puts("ed");
    }
    dijkstra();
    // rf(i, 4) printf("%d ", dis[i * n]);
    // puts("ed");
    printf("%lld", min(min(min(dis[n], dis[2 * n]), dis[3 * n]), dis[4 * n]));
}

T rad() {
    T back = 0;
    int ch = 0, posi = 0;
    for (; ch < '0' || ch > '9'; ch = getchar())
        posi = ch ^ '-';
    for (; ch >= '0' && ch <= '9'; ch = getchar())
        back = (back << 1) + (back << 3) + (ch & 0xf);
    return posi ? back : -back;
}

/*
5 5 11
1 2 3
2 3 4
3 4 5
4 5 6
1 3 7
*/

I

  • 题意

    • 在三维空间,给出三个平面
    • 查询两点间是否被平面分隔
  • 题解

    • 曲面方程 && 异侧判断
    • 对于曲面方程 F ( x , y , z ) = 0 F(x, y, z) = 0 F(x,y,z)=0,若两点在同侧,则 F ( x 1 , y 1 , z 1 ) × F ( x 2 , y 2 , z 2 ) > 0 F(x_1, y_1, z_1)\times F(x_2, y_2, z_2)>0 F(x1,y1,z1)×F(x2,y2,z2)>0
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
using ll = long long;
using T = int;
T rad(); // quick read
const int inf = 0x3f3f3f3f;
#define rf(i, n) for (int i = 1; i <= (n); ++i)
const int max_size = 5 + 100;
const int maxn = 5 + 100;

// 曲面方程
inline ll fx1(ll x, ll y, ll z) { return 1000 * x - y * y - z * z; }
inline ll fx2(ll x, ll y, ll z) { return -1000 * x - y * y - z * z; }

inline bool aside(int x1, int y1, int z1, int x2, int y2, int z2) {
    return (fx1(x1, y1, z1) > 0) ^ (fx1(x2, y2, z2) > 0) || (fx2(x1, y1, z1) > 0) ^ (fx2(x2, y2, z2) > 0);
}
inline bool check(int x1, int y1, int z1, int x2, int y2, int z2) {
    return aside(x1, y1, z1, x2, y2, z2) || aside(y1, z1, x1, y2, z2, x2) || aside(z1, x1, y1, z2, x2, y2);
}

inline bool solve(int x1, int y1, int z1, int x2, int y2, int z2) {
    return check(x1, y1, z1, x2, y2, z2) || check(y1, x1, z1, y2, x2, z2);
}

int main() {
    int Q = rad();
    while (Q--) {
        int x1 = rad(), y1 = rad(), z1 = rad(), x2 = rad(), y2 = rad(), z2 = rad();
        if (solve(x1, y1, z1, x2, y2, z2))
            puts("No");
        else
            puts("Yes");
    }
}

T rad() {
    T back = 0;
    int ch = 0, posi = 0;
    for (; ch < '0' || ch > '9'; ch = getchar())
        posi = ch ^ '-';
    for (; ch >= '0' && ch <= '9'; ch = getchar())
        back = (back << 1) + (back << 3) + (ch & 0xf);
    return posi ? back : -back;
}

J

  • 题意

    • 每次查询给出一个数 n,
    • 求 n 最少能被分解成几个完全平方数的和/差
  • 题解

    • 枚举 [ 1 , 1 e 5 ] [1,1e5] [1,1e5] 范围内的完全平方数,BFS 即可
#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
using ll = long long;
using T = int;
T rad(); // quick read
const int inf = 0x3f3f3f3f;
#define rf(i, n) for (int i = 1; i <= (n); ++i)
const int max_size = 5 + 100;
const int maxn = 5 + 1e5;

int num[maxn], siz;
int dp[maxn];

void bfs() {
    queue<int> qe;
    memset(dp, inf, sizeof(dp));
    qe.push(0), dp[0] = 0;
    while (!qe.empty()) {
        int now = qe.front();
        qe.pop();
        for (int i = 1; i <= siz; ++i) {
            int to = now + num[i];
            if (to < maxn && dp[to] == inf) dp[to] = dp[now] + 1, qe.push(to);
            to = now - num[i];
            if (to > 0 && dp[to] == inf) dp[to] = dp[now] + 1, qe.push(to);
        }
    }
}

int main() {
    for (int i = 1; i * i < maxn; ++i) num[++siz] = i * i;
    bfs();
    int Q = rad();
    while (Q--) {
        int x = rad();
        printf("%d\n", dp[x]);
    }
}

T rad() {
    T back = 0;
    int ch = 0, posi = 0;
    for (; ch < '0' || ch > '9'; ch = getchar())
        posi = ch ^ '-';
    for (; ch >= '0' && ch <= '9'; ch = getchar())
        back = (back << 1) + (back << 3) + (ch & 0xf);
    return posi ? back : -back;
}

K

  • 题意

    • 强制在线
    • 每条线段有一个权值
    • 操作 1:添加一条线段,权值为 w
    • 操作 2:查询区间内所有完整线段权值的最大差值
  • 题解

    • 线段树套线段树
    • 第一维负责管理线段左端点,第二维负责管理线段右端点
    • 查询则查左右端点(即第一二维)都在范围内的即可
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
using ll = long long;
using T = int;
T rad(); // quick read
const int inf = 0x3f3f3f3f;
#define rf(i, n) for (int i = 1; i <= (n); ++i)
const int maxn = 5 + 3e3;
const int max_size = 5 + maxn * 4 + 200000 * 30;
const int maxp = 5 + 3e3;

struct Node {
    int up, dwn;
    Node(int a = -inf, int b = inf) { up = a, dwn = b; }
};
Node maintain(Node l, Node r) { return {max(l.up, r.up), min(l.dwn, r.dwn)}; }

struct Segtree0 {

    int tot;
    Node val[max_size];
    int ch[max_size][2];
    int new_node() { return val[++tot] = Node(), ch[tot][0] = 0, ch[tot][1] = 0, tot; }

    int update(int u, int l, int r, int i, int x) {
        if (u == 0) u = new_node();
        if (l == r) {
            val[u] = maintain(val[u], {x, x});
            return u;
        }
        int mid = l + r >> 1;
        if (i <= mid)
            ch[u][0] = update(ch[u][0], l, mid, i, x);
        else
            ch[u][1] = update(ch[u][1], mid + 1, r, i, x);
        val[u] = maintain(val[ch[u][0]], val[ch[u][1]]);
        return u;
    }
    Node ask(int u, int l, int r, int s, int t) {
        if (u == 0) return Node();
        if (s <= l && r <= t) return val[u];
        int mid = l + r >> 1;
        if (t <= mid) return ask(ch[u][0], l, mid, s, t);
        if (s > mid) return ask(ch[u][1], mid + 1, r, s, t);
        return maintain(ask(ch[u][0], l, mid, s, t), ask(ch[u][1], mid + 1, r, s, t));
    }
} seg0;

struct Segtree {
    int root[maxp << 2];
    void build(int u, int l, int r) {
        root[u] = seg0.new_node();
        if (l == r) return;
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
    }
    void update(int u, int l, int r, int p1, int p2, int x) { // (p1, p2) insert(x)
        seg0.update(root[u], 1, maxp, p2, x);
        if (l == r) return;
        int mid = l + r >> 1;
        if (p1 <= mid)
            update(u << 1, l, mid, p1, p2, x);
        else
            update(u << 1 | 1, mid + 1, r, p1, p2, x);
    }

    Node ask(int u, int l, int r, int s1, int t1, int s2, int t2) {
        if (s1 <= l && r <= t1) return seg0.ask(root[u], 1, maxp, s2, t2);
        int mid = l + r >> 1;
        if (t1 <= mid) return ask(u << 1, l, mid, s1, t1, s2, t2);
        if (s1 > mid) return ask(u << 1 | 1, mid + 1, r, s1, t1, s2, t2);
        return maintain(ask(u << 1, l, mid, s1, t1, s2, t2), ask(u << 1 | 1, mid + 1, r, s1, t1, s2, t2));
    }

} seg;

int main() {
    seg.build(1, 1, maxp);
    int n = rad(), m = rad();
    rf(i, n) {
        int l = rad(), r = rad(), x = rad();
        seg.update(1, 1, maxp, l, r, x);
    }
    int last = 0;
    rf(i, m) {
        int op = rad();
        if (op == 1) { // update
            int p1 = rad() ^ last, p2 = rad() ^ last, x = rad();
            seg.update(1, 1, maxp, p1, p2, x);
        } else {
            int l = rad() ^ last, r = rad() ^ last;
            Node tmp = seg.ask(1, 1, maxp, l, r, l, r);
            int ans = tmp.up - tmp.dwn;
            printf("%d\n", ans);
            last = ans;
        }
    }
}

T rad() {
    T back = 0;
    int ch = 0, posi = 0;
    for (; ch < '0' || ch > '9'; ch = getchar())
        posi = ch ^ '-';
    for (; ch >= '0' && ch <= '9'; ch = getchar())
        back = (back << 1) + (back << 3) + (ch & 0xf);
    return posi ? back : -back;
}

/*
3 5
2 3 1
5 6 4
4 10 5

2 2 5
2 2 6
2 2 9
1 2 3 20
2 5 14
*/

L

  • 题意

    • 有三个队相互竞争。
    • 首先,以相等的概率选择两个队进行比赛。
    • 在每一轮比赛中,败队暂时离开,获胜队继续与未参加本轮比赛的三支球队中的另一支比赛。
    • 当一个队总共输掉两轮比赛时,淘汰赛结束。
    • 为了简单起见,任何两支球队都有50%的机会战胜对方。
    • 求本次淘汰赛的预期轮数。
  • 题解

    • 概率论
    • DFS
#include <algorithm>
#include <cstring>
#include <iostream>
#include <map>
using namespace std;
using ll = long long;
using T = int;
T rad(); // quick read
const int inf = 0x3f3f3f3f;
#define rf(i, n) for (int i = 1; i <= (n); ++i)
const int max_size = 5 + 100;
const int maxn = 5 + 100;

map<int, double> order;
int cnt[maxn];
void dfs(int jump, int left, double p) {
    if (cnt[1 << 0] == 2 || cnt[1 << 1] == 2 || cnt[1 << 2] == 2) {
        order[cnt[1 << 0] + cnt[1 << 1] + cnt[1 << 2]] += p;
        return;
    }

    if (jump == 0) {
        for (int mask = 0x4; mask; mask >>= 1)
            dfs(mask, left ^ mask, p / 3);
    } else {
        for (int mask = 0x4; mask; mask >>= 1) {
            if ((left & mask) == 0) continue;
            ++cnt[mask], dfs(mask, (left ^ mask) | jump, p / 2), --cnt[mask];
        }
    }
}

int main() {
    dfs(0, 0x7, 1);
    double ans = 0;
    for (auto p : order) ans += p.first * p.second;
    cout << ans;
}

T rad() {
    T back = 0;
    int ch = 0, posi = 0;
    for (; ch < '0' || ch > '9'; ch = getchar())
        posi = ch ^ '-';
    for (; ch >= '0' && ch <= '9'; ch = getchar())
        back = (back << 1) + (back << 3) + (ch & 0xf);
    return posi ? back : -back;
}

这套题的质量其实很高,主要考察了许多算法与数学的结合,非常考验选手算法池的广度。这次补题,我也是慢吞吞地磨蹭了 2 天才写完这些。比赛上,我可能会直接 g。我感觉到了我做题是真滴慢啊。。。。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

岚花落_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值