2020ICPC南京区域赛 补题 & 总结

前言

第一次打线上 ICPC \text{ICPC} ICPC ,记录一下。听说鸭血粉丝汤很好吃,虽然我没吃到,衣服也不赖。比赛环境方面,由于使用自己的设备,还是比较舒服的。

不晓得怎么,一到正式赛,前期脑子就犯懵,基本没有办法快速想出做法,现在觉得可能是平时训练出榜比较快,直接跟榜的缘故吧,正式赛前期榜单还是不太明显的,所以前期缺乏开题自信。另一个原因可能是疏于训练吧,正式赛前除了每周次的常规训练,其他题目倒是没怎么写了,而比赛当周还是考试周。

回顾了一下,前期 zzy \text{zzy} zzy 大力开签到 K \text{K} K 题,差点一血,但是后面 L \text{L} L 题写了相对复杂的扫描线做法,卡了比较久跳了题。期间 xbx \text{xbx} xbx 上机写 E \text{E} E 题,但是也是比较麻烦的分类讨论写法,我则是帮忙看了看卡的 L \text{L} L 题代码,但是没有发现问题(看榜单是签到题,应该直接接手重构的)。之后 zzy \text{zzy} zzy 秒了 F \text{F} F ,直接喂给我打,很快过了,接着 xbx \text{xbx} xbx 也过了 E \text{E} E 题。之后我跟 zzy \text{zzy} zzy 交流了下 M \text{M} M 题,确定树上背包做法后上机,但是打打调调到 3h \text{3h} 3h 才过,期间 xbx \text{xbx} xbx 重构 L \text{L} L 题过了。 zzy \text{zzy} zzy 接过键盘也很快过了 H \text{H} H 题。 3.5h \text{3.5h} 3.5h 的时候 6 6 6 题,但是罚时由于全员前期拉跨直接垮掉。这时候我在 D \text{D} D 题和 J \text{J} J 题反复横跳,直到 4h \text{4h} 4h 的时候 zzy \text{zzy} zzy 点醒了 J \text{J} J 题的最后一个结论,我接手打 J \text{J} J 题,他们则在纸上构造 A \text{A} A 题。最后由于判断失误,我误以为用 segment tree beats \text{segment tree beats} segment tree beats 可能会 TLE \text{TLE} TLE,敲了一个自以为等价的可能小常的势能线段树做法,遂 WA \text{WA} WA 穿。

最后 6 6 6 题罚时炸裂 rk43 \text{rk43} rk43 6 6 6 题从 rk18 \text{rk18} rk18 排到 rk47 \text{rk47} rk47,如果前期没卡太久或者后期决策正确,应该是稳金的吧,可惜没如果。自诩数据结构选手却没写对 J \text{J} J 题,也是挺遗憾的,而且是做法对了、做法会打的情况下。

终归是前期没信心快速签到,回顾几场正式赛,前 1h \text{1h} 1h 我几乎是处于读题状态,很难说精准秒题,即便是签到题?赛后还是多开了几场 cf \text{cf} cf 来练了练手感,为了一周后的济南站准备。

题目链接

https://ac.nowcoder.com/acm/contest/10272

参考题解

A - Ah, It’s Yesterday Once More

简要题意:

对于给定的 n × m n \times m n×m 的方格, 0 0 0 代表障碍, 1 1 1 代表袋鼠。有一串随机生成的长为 5 × 1 0 4 5 \times 10^4 5×104 的指令,仅包含 LRUD \text{LRUD} LRUD 字符,分别表示将所有袋鼠同时向某个方向移动(若能移动,即不经过障碍、不超出方格范围)。现要求构造一个 n × m n \times m n×m 方格图,使得对于随机生成的 500 500 500 串指令,至少有 125 125 125 个满足执行后,所有袋鼠不都在同一个方格。要求构造的袋鼠方格连通、且不含环。

1 ≤ n , m ≤ 20 1 \le n, m \le 20 1n,m20

解题思路:

构造尽可能长且不对称的路径让袋鼠移动,还有就是构造一些分岔路口。可以构造一条沿对角线方向的 Z \text{Z} Z 形路径。

参考代码:
#include
using namespace std;

int main(){
 
    ios::sync_with_stdio(0); cin.tie(0);
    char ans[21][21] = {
    "20 20",
    "11011111011111011111",
    "10110100110100110100",
    "11101101101101101101",
    "10011011011011011001",
    "10110110110110110111",
    "01101101101101101101",
    "11011011011011011011",
    "10110110110110110110",
    "11101101101101101101",
    "10011011011011011001",
    "10110110110110110111",
    "01101101101101101101",
    "11011011011011011011",
    "10110110110110110110",
    "11101101101101101101",
    "10011011011011011001",
    "10110110110110110111",
    "01101101101101101101",
    "01011001011001011001",
    "11110111110111110111",
    };
    for(int i = 0; i <= 20; ++i){

        cout << ans[i] << endl;
    }
    return 0;
}

 
附上顺手写的 checker \text{checker} checker,检验袋鼠方格是否连通、无环,输出值为 500 500 500 组随机指令中通过的组数。

#include
using namespace std;
typedef pair<int, int> pii;

const int xp[] = {0, 0, -1, 1};
const int yp[] = {-1, 1, 0, 0};
char ss[23][23];
pii pre[23][23];
int a[23][23], b[23][23], siz[23][23];
int n, m;
mt19937 rnd(time(0));

void mov(int d){

    memset(b, 0, sizeof b);
    for(int i = 1; i <= n; ++i){

        for(int j = 1; j <= m; ++j){

            if(!a[i][j]) continue;
            int x = i + xp[d], y = j + yp[d];
            if(x < 1 || x > n || y < 1 || y > m || ss[x][y] == '0') x = i, y = j;
            if(a[i][j]) b[x][y] += a[i][j];
        }
    }
    memcpy(a, b, sizeof b);
}

int check(){

    int ret = 0;
    for(int i = 1; i <= n; ++i){

        for(int j = 1; j <= m; ++j){

            if(a[i][j]) ++ret;
        }
    }
    return ret > 1;
}

int isHack(){

    for(int i = 1; i <= n; ++i){

        for(int j = 1; j <= m; ++j) a[i][j] = ss[i][j] - '0';
    }
    for(int i = 1; i <= 50000; ++i){

        mov(rnd() % 4);
    }
    return check();
}

pii fin(pii x){

    auto &fx = pre[x.first][x.second];
    return x == fx ? x : fx = fin(fx);
}

int unite(pii x, pii y){

    auto fx = fin(x), fy = fin(y);
    if(fx == fy) return 0;
    pre[fx.first][fx.second] = fy;
    siz[fy.first][fy.second] += siz[fx.first][fx.second];
    return 1;
}

int isValid(){

    int tot = 0, x = 0, y = 0;
    for(int i = 1; i <= n; ++i){

        for(int j = 1; j <= m; ++j){

            pre[i][j] = pii{i, j};
            siz[i][j] = 1;
            if(ss[i][j] == '1') ++tot, x = i, y = j;
        }
    }
    if(!x) return 0;
    for(int i = 1; i <= n; ++i){

        for(int j = 1; j <= m; ++j){

            if(ss[i][j] != '1') continue;
            if(i > 1 && ss[i - 1][j] == '1'){

                if(!unite(pii{i - 1, j}, pii{i, j})) return 0;
            }
            if(j > 1 && ss[i][j - 1] == '1'){

                if(!unite(pii{i, j - 1}, pii{i, j})) return 0;
            }
        }
    }
    auto fx = fin(pii{x, y});
    return siz[fx.first][fx.second] == tot;
}

int main(){
 
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n >> m;
    for(int i = 1; i <= n; ++i){

        cin >> ss[i] + 1;
    }
    if(!isValid()){

        cout << "0" << endl;
        return 0;
    }
    int ret = 0;
    for(int i = 1; i <= 500; ++i){

        if(isHack()) ++ret;
    }
    cout << ret << endl;
    return 0;
}

B - Baby’s First Suffix Array Problem

咕。

C - Certain Scientific Railgun

咕咕。

D - Degree of Spanning Tree

简要题意:

给定一个 n n n 个顶点、 m m m 条边的无向图,保证图连通,可能含有自环和重边。判断是否存在一棵生成树使得没有顶点的度数大于 n 2 \cfrac{n}{2} 2n ,存在则输出任意方案。

多组数据, 2 ≤ n ≤ 1 0 5 , n − 1 ≤ m ≤ 2 × 1 0 5 , ∑ n i ≤ 5 × 1 0 5 , ∑ m i ≤ 1 0 6 2 \le n \le 10^5, n - 1 \le m \le 2 \times 10^5, \sum n_i \le 5 \times 10^5, \sum m_i \le 10^6 2n105,n1m2×105,ni5×105,mi106

解题思路:

对于 n n n 个顶点的树,用 d ( i ) d(i) d(i) 表示顶点 i i i 的度数,对任意两个顶点 u , v u, v u,v,有 d ( u ) + d ( v ) ≤ n d(u) + d(v) \le n d(u)+d(v)n,取等号当且仅当 u , v u, v u,v 邻接。故任意取一棵生成树,度数大于 n 2 \cfrac{n}{2} 2n 的顶点最多只有一个,若没有这样的点,则输出答案。否则将其设为根 r t rt rt 并试图调整其度数。接下来遍历其他所有边,若边 e ( u , v ) e(u, v) e(u,v) 能替换 r t rt rt 邻接的一条边,则替换,直到 d ( r t ) = ⌊ n 2 ⌋ d(rt) = \lfloor\cfrac{n}{2}\rfloor d(rt)=2n

注意到这个过程可能让某个顶点 u u u 的度数大于 n 2 \cfrac{n}{2} 2n,这样的 u u u 只可能是 r t rt rt 的邻接点,因为当 d ( r t ) = ⌊ n 2 ⌋ d(rt) = \lfloor\cfrac{n}{2}\rfloor d(rt)=2n 时, d ( u ) + d ( r t ) = ( ⌊ n 2 ⌋ + 1 ) + ⌊ n 2 ⌋ ≥ n d(u) + d(rt) = (\lfloor\cfrac{n}{2}\rfloor + 1) + \lfloor\cfrac{n}{2}\rfloor \ge n d(u)+d(rt)=(2n+1)+2nn ,上面提到若两顶点不邻接,度数和将小于 n n n,故得证。

故替换边的过程,优先让 r t rt rt 的非邻接顶点度数增加,若枚举的边 e ( u , v ) e(u, v) e(u,v) 都是 r t rt rt 的邻接顶点呢?优先让度数小的度数增加,可以证明只有 n = 3 n = 3 n=3 时可能出现 d ( u ) > n 2 d(u) \gt \cfrac{n}{2} d(u)>2n,而 n = 3 n = 3 n=3 是无解情况,特判即可。

参考代码:
#include
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
const int maxn = 2e5 + 5;

struct Edge{
    int u, v, p;
} ei[maxn];
vector<Edge> G[maxn];
int deg[maxn], pre[maxn], vis[maxn], fe[maxn], dep[maxn];
int n, m;

int fin(int x){

    return x == pre[x] ? x : pre[x] = fin(pre[x]);
}

void dfs(int u, int f, int t){

    pre[u] = t, dep[u] = dep[f] + 1;
    for(auto &e : G[u]){

        int v = e.v;
        if(v == f) continue;
        dfs(v, u, t);
    }
}

int main(){

    ios::sync_with_stdio(0); cin.tie(0);
    int T; cin >> T;
    while(T--){

        cin >> n >> m;
        for(int i = 1; i <= n; ++i){

            G[i].clear();
            deg[i] = 0;
            pre[i] = i;
        }
        for(int i = 1; i <= m; ++i){

            vis[i] = 0;
        }
        for(int i = 1; i <= m; ++i){

            int u, v; cin >> u >> v;
            ei[i] = Edge{u, v, i};
            if(fin(u) == fin(v)) continue;
            G[u].pb(Edge{u, v, i}), G[v].pb(Edge{v, u, i});
            pre[fin(u)] = fin(v);
            ++deg[u], ++deg[v], vis[i] = 1;
        }
        int rt = max_element(deg + 1, deg + 1 + n) - deg;
        auto print = [](const string &ans){

            cout << ans << "\n";
            if(ans == "No") return;
            for(int i = 1; i <= m; ++i){

                if(vis[i]) cout << ei[i].u << " " << ei[i].v << "\n";
            }
        };
        if(n == 3){

            print("No");
            continue;
        }
        if(deg[rt] <= n / 2){

            print("Yes");
            continue;
        }
        pre[rt] = rt, dep[rt] = 0;
        for(auto &e : G[rt]){

            int v = e.v;
            fe[v] = e.p;
            dfs(v, rt, v);
        }
        for(int i = 1; i <= m; ++i){

            int u = ei[i].u, v = ei[i].v;
            if(u == rt || v == rt) continue;
            int fu = fin(u), fv = fin(v);
            if(fu == fv) continue;
            if(dep[u] < dep[v]) swap(u, v), swap(fu, fv);
            if(dep[v] == 1 && deg[u] > deg[v]) swap(u, v), swap(fu, fv);
            ++deg[u], ++deg[v];
            --deg[rt], --deg[fv];
            vis[ei[i].p] = 1, vis[fe[fv]] = 0;
            pre[fv] = fu;
            if(deg[rt] <= n / 2) break;
        }
        print(deg[rt] <= n / 2 ? "Yes" : "No");
    }
    return 0;
}

E - Evil Coordinate

简要题意:

二维网格图上,给定起点 ( 0 , 0 ) (0, 0) (0,0),再给一个障碍 ( m x , m y ) (m_x, m_y) (mx,my) 和一串长为 n n n 的指令,仅包含 LRUD \text{LRUD} LRUD 字符,分别表示向某个方向移动,求调整指令顺序让移动过程不经过 ( m x , m y ) (m_x, m_y) (mx,my),无解输出 impossible \text{impossible} impossible

多组数据, 1 ≤ n ≤ 1 0 5 , − 1 0 9 ≤ m x , m y ≤ 1 0 9 , ∑ n i ≤ 1 0 6 1 \le n \le 10^5, -10^9 \le m_x, m_y \le 10^9, \sum n_i \le 10^6 1n105,109mx,my109,ni106

解题思路:

可以证明如果有解,一定存在一种解是每个方向都连续走完,故枚举四个方向的排列即可。证明免了,看我代码分类讨论吧。

参考代码:
#include
using namespace std;
const int maxn = 2e5 + 5;

const int xp[] = {0, 0, -1, 1};
const int yp[] = {1, -1, 0, 0};
int has[256];
char s[maxn];
int n, mx, my;

int main(){
    
    ios::sync_with_stdio(0); cin.tie(0);
    has['U'] = 0, has['D'] = 1, has['L'] = 2, has['R'] = 3;
    int T; cin >> T;
    while(T--){
        cin >> mx >> my;
        cin >> s + 1;
        n = strlen(s + 1);
        int x = 0, y = 0;
        map<int, int> cnt;
        for(int i = 1; i <= n; ++i){
            ++cnt[s[i]];
            x += xp[has[s[i]]], y += yp[has[s[i]]];
        }
        string ans = "";
        auto walk4 = [&](const string &s){
            for(int i = 0; i < 4; ++i){
                ans += string(cnt[s[i]], s[i]);
            }
        };
        auto sign = [](int x){
            return x > 0 ? 1 : -1;
        };
        int ret = 1;
        if(mx == 0 && my == 0 || x == mx && y == my) ret = 0;
        else if(mx == 0){
            if(x != 0) walk4("LRUD");
            else{
                int dy = cnt['U'] - cnt['D'];
                if(sign(dy) == sign(my) && abs(dy) >= abs(my)){
                    if(!cnt['L']) ret = 0;
                    else walk4("LUDR");
                }
                else if(my > 0) walk4("DULR");
                else walk4("UDLR");
            }
        }
        else if(my == 0){
            if(y != 0) walk4("UDLR");
            else{
                int dx = cnt['R'] - cnt['L'];
                if(sign(dx) == sign(mx) && abs(dx) >= abs(mx)){
                    if(!cnt['U']) ret = 0;
                    else walk4("ULRD");
                }
                else if(mx > 0) walk4("LRUD");
                else walk4("RLUD");
            }
        }
        else if(mx == x){
            walk4("UDLR");
        }
        else if(my == y){
            walk4("LRUD");
        }
        else{
            walk4("UDLR");
        }
        if(ret) cout << ans << endl;
        else cout << "Impossible" << endl;
    }
    return 0;
}

F - Fireworks

简要题意:

制作一支烟花需要花费 n n n 分钟,每支烟花有 p × 1 0 − 4 p \times 10^{-4} p×104 的概率是完美的,每次可以花费 m m m 分钟点燃之前制作的所有烟花,若发现至少有一支完美的,则停止。问最优策略下,最短的停下的时间期望是多少?

多组数据, 1 ≤ T ≤ 1 0 4 , 1 ≤ n , m ≤ 1 0 9 , 1 ≤ p ≤ 1 0 4 1 \le T \le 10^4, 1 \le n, m \le 10^9, 1 \le p \le 10^4 1T104,1n,m109,1p104

解题思路:

不是完美烟花的概率 q = 1 − ( p × 1 0 − 4 ) q = 1 - (p \times 10^{-4}) q=1(p×104),假设最优策略为连续制作 k k k 支烟花后点燃检验,若发现有完美烟花则停止,若没有则回到初始状态,继续执行最优策略,制作轮数 X X X 服从几何分布,故期望制作轮数 E ( X ) = 1 1 − q k E(X) = \cfrac{1}{1 - q^k} E(X)=1qk1,期望时间为 f ( k ) = n × k + m 1 − q k f(k) = \cfrac{n \times k + m}{1 - q^k} f(k)=1qkn×k+m ,这是一个单峰函数,可以直接三分答案。

参考代码:
#include
using namespace std;
const int maxn = 2e5 + 5;
typedef long long ll;
#define eps 1e-7

ll n, m;
double p;

double cal(ll k){
    return ((double)k * n + m) / (1 - pow(1 - p, k));
}

double solve(){
    ll L = 0, R = 2e18, x = L, pw = R - L;
    double fx = cal(x);
    while(pw > 0){
        ll l = max(L, x - pw), r = min(R, x + pw);
        double fl = cal(l), fr = cal(r);
        if(fl < fx) x = l, fx = fl;
        else if(fr < fx) x = r, fx = fr;
        else pw >>= 1;
    }
    return fx;
}

int main(){
    
    ios::sync_with_stdio(0); cin.tie(0);
    cout << fixed << setprecision(10);
    int T; cin >> T;
    while(T--){
        ll P; cin >> n >> m >> P;
        p = P * 0.0001;
        cout << solve() << endl;
    }
    return 0;
}

G - Go

咕咕咕。

H - Harmonious Rectangle

简要题意:

给定 n n n m m m ,问有多少个不同的 n × m n \times m n×m 矩阵满足:矩阵元素 a i , j ∈ { 1 , 2 , 3 } a_{i, j} \in \{1,2,3\} ai,j{1,2,3},并且存在 1 ≤ x 1 < x 2 ≤ n 1 \le x_1 \lt x_2 \le n 1x1<x2n 1 ≤ y 1 < y 2 ≤ m 1 \le y_1 \lt y_2 \le m 1y1<y2m 满足
{ a ( x 1 , y 1 ) = a ( x 1 , y 2 ) a ( x 2 , y 1 ) = a ( x 2 , y 2 )  或  { a ( x 1 , y 1 ) = a ( x 2 , y 1 ) a ( x 1 , y 2 ) = a ( x 2 , y 2 ) \left\{\begin{aligned} a(x_1, y_1) = a(x_1, y_2) \\ a(x_2, y_1) = a(x_2, y_2) \end{aligned}\right. ~\text{或}~ \left\{\begin{aligned} a(x_1, y_1) = a(x_2, y_1) \\ a(x_1, y_2) = a(x_2, y_2) \end{aligned}\right. {a(x1,y1)=a(x1,y2)a(x2,y1)=a(x2,y2)  {a(x1,y1)=a(x2,y1)a(x1,y2)=a(x2,y2)
输出答案模上 1 0 9 + 7 10^9 + 7 109+7

多组数据, 1 ≤ T ≤ 1 0 4 , 1 ≤ n , m ≤ 2 × 1 0 3 1 \le T \le 10^4, 1 \le n, m \le 2 \times 10^3 1T104,1n,m2×103

解题思路:

由于不同的两个元素组合只有 9 9 9 种,当 max ⁡ ( n , m ) > 9 \max(n, m) \gt 9 max(n,m)>9 时,根据抽屉原理,任意矩阵都满足条件。对于其他情况,暴搜打表找出不符合的情况即可。

参考代码:
#include
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int mod = 1e9 + 7;

// int a[10][10], row[10][10][16], col[10][10][16];
// int n, m;

// int check(int x, int y){

//     for(int i = 1; i < y; ++i){

//         if(row[i][y][a[x][i] * 4 + a[x][y]]) return 0;
//     }
//     for(int i = 1; i < x; ++i){

//         if(col[i][x][a[i][y] * 4 + a[x][y]]) return 0;
//     }
//     return 1;
// }

// void cal(int x, int y, int d){

//     for(int i = 1; i < y; ++i){

//         row[i][y][a[x][i] * 4 + a[x][y]] += d;
//     }
//     for(int i = 1; i < x; ++i){

//         col[i][x][a[i][y] * 4 + a[x][y]] += d;
//     }
// }

// int dfs(int x, int y){

//     if(x > n) return 1;
//     if(y > m) return dfs(x + 1, 1);
//     int ret = 0;
//     for(int i = 1; i <= 3; ++i){

//         a[x][y] = i;
//         if(!check(x, y)) continue;
//         cal(x, y, 1);
//         ret += dfs(x, y + 1);
//         cal(x, y, -1);
//     }
//     return ret;
// }

ll qpow(ll a, ll b){

    ll ret = 1;
    while(b){

        if(b & 1) ret = ret * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ret;
}

int ans[10][10] = {
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 15, 339, 4761, 52929, 517761, 4767849, 43046721, 387420489, 
    0, 0, 339, 16485, 518265, 14321907, 387406809, 460338013, 429534507, 597431612,
    0, 0, 4761, 518265, 43022385, 486780060, 429534507, 792294829, 175880701, 246336683,
    0, 0, 52929, 14321907, 486780060, 288599194, 130653412, 748778899, 953271190, 644897553,
    0, 0, 517761, 387406809, 429534507, 130653412, 246336683, 579440654, 412233812, 518446848,       
    0, 0, 4767849, 460338013, 792294829, 748778899, 579440654, 236701429, 666021604, 589237756,      
    0, 0, 43046721, 429534507, 175880701, 953271190, 412233812, 666021604, 767713261, 966670169,     
    0, 0, 387420489, 597431612, 246336683, 644897553, 518446848, 589237756, 966670169, 968803245, 
};

int main(){
 
    ios::sync_with_stdio(0); cin.tie(0);
    int T; cin >> T;
    while(T--){

        int n, m; cin >> n >> m;
        ll ret = n <= 9 && m <= 9 ? ans[n][m] : qpow(3, n * m);
        if(n == 1 || m == 1) ret = 0;
        cout << ret << "\n";
    }
    return 0;
}

I - Interested in Skiing

简要题意:

二维平面 [ − m , m ] × R [-m, m] \times \mathbb{R} [m,m]×R 上有 n n n 条线段作为障碍,以端点坐标 ( x 1 , y 1 ) (x_1, y_1) (x1,y1) ( x 2 , y 2 ) (x_2, y_2) (x2,y2) 表示,起点为 ( 0 , − inf ⁡ ) (0, -\inf) (0,inf) 的一个点以 v y v_y vy 的速度向 y \text{y} y 轴正方向移动,终点为 ( 0 , inf ⁡ ) (0, \inf) (0,inf) 。求最小的 v x ∗ v_x^* vx,使得当水平速度 v x > v x ∗ v_x \gt v_x^* vx>vx 时,能从起点移动到终点,无解则输出 − 1 -1 1

1 ≤ n ≤ 100 , 1 ≤ m ≤ 1 0 4 , 1 ≤ v y ≤ 10 , − m ≤ x 1 , x 2 ≤ m , − 1 0 − 4 ≤ y 1 , y 2 ≤ 1 0 4 1 \le n \le 100, 1 \le m \le 10^4, 1 \le v_y \le 10, -m \le x_1, x_2 \le m, -10^{-4} \le y_1, y_2 \le 10^4 1n100,1m104,1vy10,mx1,x2m,104y1,y2104

解题思路:

最优策略是经过一些线段的端点,将所有端点按 y \text{y} y 升序排序,以该顺序拓扑转移, d p i dp_i dpi 表示到达第 i i i 个点所需的最小 v x v_x vx ,转移时枚举 O ( n ) O(n) O(n) 判断两点间是否能判断,总时间复杂度 O ( n 3 ) O(n^3) O(n3)

参考代码:
#include
using namespace std;

struct Point{
    int x, y;
};
struct Line{
    Point s, e;
};

inline int sign(int x){
 
    return x == 0 ? 0 : x > 0 ? 1 : -1;
}
 
inline int cross(const Point &a, const Point &b, const Point &c){
 
    return (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y);
}
 
inline int isIns(const Line &l1, const Line &l2){
 
    return 
        max(l1.s.x, l1.e.x) > min(l2.s.x, l2.e.x) && 
        max(l2.s.x, l2.e.x) > min(l1.s.x, l1.e.x) &&
        max(l1.s.y, l1.e.y) > min(l2.s.y, l2.e.y) &&
        max(l2.s.y, l2.e.y) > min(l1.s.y, l1.e.y) &&
        sign(cross(l1.s, l2.s, l1.e)) * sign(cross(l1.s, l2.e, l1.e)) < 0 && 
        sign(cross(l2.s, l1.s, l2.e)) * sign(cross(l2.s, l1.e, l2.e)) < 0; 
}

int main(){

    ios::sync_with_stdio(0); cin.tie(0);
    cout << fixed << setprecision(10);
    int n, m, vy; cin >> n >> m >> vy;
    vector<Point> pi(2 * n);
    vector<Line> li(n);
    for(int i = 0; i < n; ++i){

        cin >> pi[i << 1].x >> pi[i << 1].y;
        cin >> pi[i << 1 | 1].x >> pi[i << 1 | 1].y;
        li[i] = Line{pi[i << 1], pi[i << 1 | 1]};
    }
    auto isOK = [&](const Point &a, const Point &b) -> int{

        Line l = Line{a, b};
        for(int i = 0; i < n; ++i){

            if(isIns(l, li[i])) return 0;
        }
        return 1;
    };
    sort(pi.begin(), pi.end(), [](const Point &a, const Point &b){ return a.y < b.y; });
    vector<double> dp(2 * n, 1e20);
    double ans = 1e20;
    for(int i = 0; i < 2 * n; ++i){

        if(pi[i].x <= -m || pi[i].x >= m) continue;
        if(isOK(Point{pi[i].x, -10001}, pi[i])) dp[i] = 0;
        for(int j = 0; j < i; ++j){

            if(pi[j].y == pi[i].y) break;
            if(isOK(pi[j], pi[i])){

                double ki = 1.0 * (pi[i].x - pi[j].x) / (pi[i].y - pi[j].y);
                dp[i] = min(dp[i], max(dp[j], abs(ki)));
            }
        }
        if(isOK(Point{pi[i].x, 10001}, pi[i])) ans = min(ans, dp[i]);
        // cout << i << " " << pi[i].x << " " << pi[i].y << " " << ans << endl;
    }
    if(isOK(Point{0, -10001}, Point{0, 10001})) ans = 0;
    if(ans > 1e10) cout << "-1" << endl;
    else cout << ans * vy << endl;
    return 0;
}

J - Just Another Game of Stones

简要题意:

给定一个长度为 n n n 的数组 a i a_i ai ,有 q q q 次操作:

  • 1   l   r   x 1~l~r~x 1 l r x ,表示令 a i = max ⁡ ( a i , x ) , i ∈ [ l , r ] a_i = \max(a_i, x), i \in [l, r] ai=max(ai,x),i[l,r]
  • 2   l   r   x 2~l~r~x 2 l r x ,表示询问用 [ l , r ] [l, r] [l,r] 的石堆和额外的一堆有 x x x 个石子的石堆一起,进行 Nim \text{Nim} Nim 博弈的必胜情况的第一步操作种数。

1 ≤ n , q ≤ 2 × 1 0 5 , 1 ≤ l , r ≤ n , 0 ≤ a i , x < 2 30 − 1 1 \le n, q \le 2 \times 10^5, 1 \le l, r \le n, 0 \le a_i, x \lt 2^{30} - 1 1n,q2×105,1l,rn,0ai,x<2301

解题思路:

Nim \text{Nim} Nim 博弈,当石子数量异或和不为 0 0 0 时为必胜态,否则为必败态。假设异或和为 S S S,第一步操作就是取一堆石子,让异或和变成 0 0 0,即对石子个数为 a i a_i ai 的石堆,取走 a i − a i ⊕ S a_i - a_i \oplus S aiaiS 个石子,故询问答案就是 a i ≥ a i ⊕ S a_i \ge a_i \oplus S aiaiS i i i 的个数。

考虑 S S S 二进制最高位 1 1 1,若 a i a_i ai 对应位也是 1 1 1 ,则必有 a i ≥ a i ⊕ S a_i \ge a_i \oplus S aiaiS ,反之不成立。修改操作使用 segment tree beats \text{segment tree beats} segment tree beats 的维护技巧。

参考代码:
#include
using namespace std;
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int inf = 1 << 30;
const int maxn = 2e5 + 5;
 
int ans[30];
int a[maxn], n, q;
 
struct SegTree{
 
    int mn[maxn << 2], mn2[maxn << 2], sum[maxn << 2][30], cov[maxn << 2], num[maxn << 2];
    void pushUp(int rt){
 
        for(int i = 0; i < 30; ++i) sum[rt][i] = sum[lson][i] + sum[rson][i];
        if(mn[lson] == mn[rson]){
 
            mn[rt] = mn[lson], num[rt] = num[lson] + num[rson];
            mn2[rt] = min(mn2[lson], mn2[rson]);
        }
        else if(mn[lson] < mn[rson]){
 
            mn[rt] = mn[lson], num[rt] = num[lson];
            mn2[rt] = min(mn2[lson], mn[rson]);
        }
        else{
 
            mn[rt] = mn[rson], num[rt] = num[rson];
            mn2[rt] = min(mn[lson], mn2[rson]);
        }
    }
    void build(int l, int r, int rt){
 
        cov[rt] = -1;
        if(l == r){
 
            for(int i = 0; i < 30; ++i) sum[rt][i] = (a[l] >> i) & 1;
            mn[rt] = a[l], mn2[rt] = inf, num[rt] = 1;
            return;
        }
        int mid = gmid;
        build(l, mid, lson);
        build(mid + 1, r, rson);
        pushUp(rt);
    }
    void pushDown2(int rt, int son){
 
        if(cov[rt] <= mn[son]) return;
        for(int i = 0; i < 30; ++i){
 
            sum[son][i] -= num[son] * ((mn[son] >> i) & 1);
            sum[son][i] += num[son] * ((cov[rt] >> i) & 1);
        }
        mn[son] = cov[son] = cov[rt];
    }
    void pushDown(int l, int r, int rt){
 
        if(cov[rt] != -1){
 
            int mid = gmid;
            pushDown2(rt, lson);
            pushDown2(rt, rson);
            cov[rt] = -1;
        }
    }
    void update(int l, int r, int rt, int L, int R, int val){
 
        if(val <= mn[rt]) return;
        if(l >= L && r <= R && val < mn2[rt]){
 
            cov[0] = val, pushDown2(0, rt);
            return;
        }
        int mid = gmid; pushDown(l, r, rt);
        if(L <= mid) update(l, mid, lson, L, R, val);
        if(R > mid) update(mid + 1, r, rson, L, R, val);
        pushUp(rt);
    }
    void query(int l, int r, int rt, int L, int R){
 
        if(l >= L && r <= R){
 
            for(int i = 0; i < 30; ++i){
 
                ans[i] += sum[rt][i];
            }
            return;
        }
        int mid = gmid; pushDown(l, r, rt);
        if(L <= mid) query(l, mid, lson, L, R);
        if(R > mid) query(mid + 1, r, rson, L, R);
    }
} tr;

int main(){
 
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n >> q;
    for(int i = 1; i <= n; ++i){
 
        cin >> a[i];
    }
    tr.build(1, n, 1);
    while(q--){
 
        int opt, l, r, x; cin >> opt >> l >> r >> x;
        if(opt == 1){
 
            tr.update(1, n, 1, l, r, x);
        }
        else{
 
            memset(ans, 0, sizeof ans);
            tr.query(1, n, 1, l, r);
            int ret = 0;
            for(int i = 29; i >= 0; --i){
 
                ans[i] += (x >> i) & 1;
                if(ans[i] & 1) { ret = ans[i]; break; }
            }
            cout << ret << "\n";
        }
    }
    return 0;
}

K - K Co-prime Permutation

简要题意:

给定 n n n k k k ,构造一个排列 p i p_i pi 使得 gcd ⁡ ( p i , i ) = 1 \gcd(p_i, i) = 1 gcd(pi,i)=1 i i i 的个数恰有 k k k 个,无解输出 − 1 -1 1

1 ≤ n ≤ 1 0 6 , 0 ≤ k ≤ n 1 \le n \le 10^6, 0 \le k \le n 1n106,0kn

解题思路:

i > 1 i \gt 1 i>1 gcd ⁡ ( i , i − 1 ) = 1 \gcd(i, i - 1) = 1 gcd(i,i1)=1 。特判 k = 0 k = 0 k=0 无解。

参考代码:
#include
using namespace std;
const int maxn = 1e6 + 5;

int ans[maxn];
int n, k;

int main(){
    
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n >> k;
    if(k == 0) cout << "-1" << endl;
    else{
        for(int i = 2; i <= k; ++i) ans[i - 1] = i;
        ans[k] = 1;
        for(int i = k + 1; i <= n; ++i) ans[i] = i;
        for(int i = 1; i < n; ++i) cout << ans[i] << " ";
        cout << ans[n] << endl;
    }
    return 0;
}

L - Let’s Play Curling

简要题意:

给定长度为 n n n 的数组 a i a_i ai 和长度为 m m m 的数组 b i b_i bi ,对于一个数 c c c,若 a i a_i ai 满足对于所有 j j j ∣ c − a i ∣ < ∣ c − b j ∣ \vert c - a_i \vert \lt \vert c - b_j \vert cai<cbj 则得分加 1 1 1 ,最终得分 f ( c ) f(c) f(c) 为满足的 i i i 的个数,求最大的 f ( c ) f(c) f(c)

多组数据, 1 ≤ n , m ≤ 1 0 5 , 1 ≤ a i , b i ≤ 1 0 9 , ∑ n i , ∑ m i ≤ 5 × 1 0 5 1 \le n, m \le 10^5, 1 \le a_i, b_i \le 10^9, \sum n_i, \sum m_i \le 5 \times 10^5 1n,m105,1ai,bi109,ni,mi5×105

解题思路:

选择若干 a i a_i ai ,需要满足 max ⁡ {   ∣ c − a i ∣   } < min ⁡ {   ∣ c − b j ∣   } \max\{~\vert c - a_i\vert~\} \lt \min\{~\vert c - b_j\vert~\} max{ cai }<min{ cbj } ,若确定 a i a_i ai ,则 c c c 取值为 max ⁡ { a i } + min ⁡ { a i } 2 \cfrac{\max\{a_i\} + \min\{a_i\}}{2} 2max{ai}+min{ai} 最优,显然不能有 b j b_j bj 落在 [ min ⁡ { a i } , max ⁡ { a i } ] [\min\{a_i\}, \max\{a_i\}] [min{ai},max{ai}] 。故答案就是 ( b j , b j + 1 ) (b_j, b_{j + 1}) (bj,bj+1) 最多有多少个 a i a_i ai

参考代码:
#include
using namespace std;
const int maxn = 2e5 + 5;

int a[maxn], b[maxn];
int n, m;

int main(){
    
    ios::sync_with_stdio(0); cin.tie(0);
    int T; cin >> T;
    while(T--){
        
        cin >> n >> m;
        for(int i = 1; i <= n; ++i) cin >> a[i];
        for(int i = 1; i <= m; ++i) cin >> b[i];
        b[++m] = 0, b[++m] = 1e9 + 1;
        sort(a + 1, a + 1 + n);
        sort(b + 1, b + 1 + m);
        int ret = 0;
        for(int i = 1; i < m; ++i){
            int p1 = lower_bound(a + 1, a + 1 + n, b[i] + 1) - a;
            int p2 = lower_bound(a + 1, a + 1 + n, b[i + 1]) - a;
            ret = max(ret, p2 - p1);
        }
        if(ret > 0) cout << ret << endl;
        else cout << "Impossible" << endl;
    }
    return 0;
}

M - Monster Hunter

简要题意:

给一棵 n n n 个结点的有根树, 1 1 1 号结点为根结点,每个结点上有一个血量为 h p i hp_i hpi 的怪物。必须从根到叶逐一消灭怪物,消灭第 u u u 号怪物的代价为 h p u + ∑ h p v hp_u + \sum hp_v hpu+hpv ,其中 v v v u u u 的直接子结点。使用一次魔法可以让一只怪物血量归零,求使用魔法次数为 0 , 1 , 2 , ⋯   , n 0, 1, 2, \cdots, n 0,1,2,,n 时所需的最小代价分别是多少。

多组数据, 2 ≤ n ≤ 2 × 1 0 3 , 1 ≤ h p i ≤ 1 0 9 , ∑ n i ≤ 2 × 1 0 3 2 \le n \le 2 \times 10^3, 1 \le hp_i \le 10^9, \sum n_i \le 2 \times 10^3 2n2×103,1hpi109,ni2×103

解题思路:

考虑一次魔法操作能减少的代价,如果对 u u u 结点使用,首先对父结点 f f f,消灭父结点 f f f 的代价减少 h p u hp_u hpu ,然后是减少 h p u + ∑ h p v hp_u + \sum hp_v hpu+hpv ,可以想象一次魔法操作对应将边 ( u , f ) (u, f) (u,f) 和所有 ( u , v ) (u, v) (u,v) 覆盖,将结点 u u u 覆盖。多次魔法操作后,一条边、一个结点被覆盖多次,也只能减少一次代价,然后就是树上背包枚举子树合并转移了。

参考代码:
#include
using namespace std;
#define sz(a) ((int)a.size())
#define pb push_back
const int maxn = 2e3 + 5;
typedef long long ll;
const ll oo = 1ll << 60;

vector<int> G[maxn];
int siz[maxn];
ll a[maxn], sum[maxn], dp[maxn][maxn][2], ans[maxn];
int n;

void dfs(int u, int f){
    dp[u][0][0] = 0, dp[u][1][1] = sum[u], siz[u] = 1;
    for(auto &v : G[u]){
        if(v == f) continue;
        dfs(v, u);
        for(int i = siz[u]; i >= 0; --i){
            for(int j = siz[v]; j >= 0; --j){
                dp[u][i + j][0] = max(dp[u][i + j][0], dp[u][i][0] + max(dp[v][j][0], dp[v][j][1]));
                dp[u][i + j][1] = max(dp[u][i + j][1], dp[u][i][1] + max(dp[v][j][0], dp[v][j][1] - a[v]));
            }
        }
        siz[u] += siz[v];
    }
}

int main(){
    
    ios::sync_with_stdio(0); cin.tie(0);
    int T; cin >> T;
    while(T--){
        cin >> n;
        for(int i = 1; i <= n; ++i){
            G[i].clear();
            for(int j = 0; j <= n; ++j){
                dp[i][j][0] = dp[i][j][1] = -oo;
            }
        }
        for(int i = 2; i <= n; ++i){
            int u; cin >> u;
            G[u].pb(i);
        }
        for(int i = 1; i <= n; ++i){
            cin >> a[i];
        }
        for(int i = 0; i <= n; ++i){
            ans[i] = 0;
        }
        for(int i = 1; i <= n; ++i){
            sum[i] = a[i];
            for(auto &v : G[i]) sum[i] += a[v];
            ans[0] += sum[i];
            if(i > 1) sum[i] += a[i];
        }
        dfs(1, 0);
        for(int i = 1; i <= n; ++i){
            ans[i] = ans[0] - max(dp[1][i][0], dp[1][i][1]);
        }
        for(int i = 0; i < n; ++i) cout << ans[i] << " ";
        cout << ans[n] << endl;
    }
    return 0;
}

你可能感兴趣的:(赛后补题专栏)