Loading

2024杭电多校第7场

7

1004 战争游戏 (hdu7508

游戏进行 \(10^{100}\) 局,几乎已经不可计算,而且囊括了极其大量的局面,于是猜测最终胜负与行动步数无关。考虑防守方一定失败的情况,当进攻方的轰炸半径 \(r_1\) 覆盖整棵树、或者 \(r_1\geq r_2\) 时,防守方必败;当 \(2r_1\geq r_2\) 时,进攻方可将防守方逼到叶子节点、使其必须掉头逃跑,此时防守方无法跑出轰炸范围而失败,该情况下的胜负与防守方初始位置 \(s\) 无关。对于其他情况,防守方都不会失败。分类讨论即可。

1007 创作乐曲 (hdu7511

妙妙dp题,赛时wyq一眼丁真、发现可以线段树优化dp,可惜我们没推出关键性质,最后TLE遗憾离场。

在每个最优子乐曲中,音符 \(i\) 的后继音符只有两种可能:\(a[p] - a[i] \leq k\) 中距离 \(i\) 最近的 \(p\),或者 \(a[i] - a[q] \leq k\) 中距离 \(i\) 最近的 \(q\). 采用反证法,若 \(i\) 的后继是其他音符 \(j\),由 \(\mid a[j] - a[i]\mid\space\leq k\)\(a[p]\)\(a[q]\) 中至少其一可放在 \(i\)\(j\) 间,即其他情况不是最优解。

查询距 \(i\) 最近的合法后继结点 \(p,q,\) 可使用线段树优化时间复杂度。建立 \(1\)\(m\) 的权值线段树,维护每个音符的下标,加入新的音符 \(i\)\(a[i]\) 位置赋值为 \(i\),查询 \(p,q\) 即查询 \(l = a[i] - k,\space r = a[i]\)\(l = a[i],\space r = a[i] + k\) 的区间最小值;注意由于权值范围过大,线段树需要动态开点。预处理所有 \(p,q,\) 询问时即可 \(O(n)\) 计算最优子乐曲。

线段树代码:

int num[M], ls[M] = {0}, rs[M] = {0}, cnt;
void update(int i, ll l, ll r, ll q, int x) {
    num[i] = x;
    if(l == r) return;
    ll mid = (l + r) / 2;
    if(q <= mid) {
        if(!ls[i]) ls[i] = ++cnt;
        update(ls[i], l, mid, q, x);
    } else {
        if(!rs[i]) rs[i] = ++cnt;
        update(rs[i], mid + 1, r, q, x);
    }
}
int query(int i, ll l, ll r, ll ql, ll qr) {
    if(ql <= l && qr >= r) return num[i];
    ll mid = (l + r) / 2;
    int ans = n + 1;
    if(ql <= mid) ans = min(ans, query(ls[i], l, mid, ql, qr));
    if(qr > mid) ans = min(ans, query(rs[i], mid + 1, r, ql, qr));
    return ans;
}

main函数内主要代码:

    cnt = 1;
    num[0] = num[1] = n + 1; // num[0] = n + 1,防止查询时最小值取为0
    for(int i = n; i; i--) {
        nex1[i] = query(1, 1, m, max(1ll, a[i] - k), a[i]);
        nex2[i] = query(1, 1, m, a[i], min(a[i] + k, m));
        update(1, 1, m, a[i], i); // 先查询再加点
    }
    int q;
    scanf("%d", &q);
    while(q--) {
        int l, r;
        scanf("%d%d", &l, &r);
        for(int i = l; i <= r; i++) {
            dp[i] = i - l;
        }
        for(int i = l; i <= r; i++) {
            dp[nex1[i]] = min(dp[nex1[i]], dp[i] + nex1[i] - i - 1);
            dp[nex2[i]] = min(dp[nex2[i]], dp[i] + nex2[i] - i - 1);
        }
        int ans = n + 1;
        for(int i = l; i <= r; i++) {
            ans = min(ans, dp[i] + r - i);
        }
        printf("%d\n", ans);
    }

1008 循环图 (hdu7512

计数题,考虑dp递推计算,发现dp方程已确定的情况下需要最多 \(10^{18}\) 次转移,且 \(n\leq 100\),可使用矩阵快速幂优化dp,分别对区间左右端点计数,最终答案即 \(ans[r] - ans[l - 1]\). 设计转移矩阵,对于 \(x\) 指向 \(y\) 的一条边,\(y\leq n\) 时在本周期内转移、可以与其他路径累计,\(y > n\) 时则只能计算一次,按节点编号依次处理可避免遗漏;\(ans[x]\) 为节点 \(1\)\(x\) 方案数之和,因此在转移矩阵中添加第 \(n + 1\) 维,对前 \(n\) 维的结果求和,再令 \(a[n + 1][n + 1] = 1\),即可在快速幂过程中维护答案。初值行向量即节点 \(1\)\(n\) 的方案数,与转移矩阵同理计算;对于最后不足 \(n\) 个节点的答案可暴力统计。

主要代码:

    // 转移矩阵
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            if(y[j] == i + n) {
                b[x[j]][i] += w[j];
                if(b[x[j]][i] >= mo) b[x[j]][i] -= mo;
            } else if(y[j] == i) {
                for(int k = 1; k <= n; k++) {
                    b[k][i] += b[k][x[j]] * w[j] % mo;
                    if(b[k][i] >= mo) b[k][i] -= mo;
                }
            }
        }
    }
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= n; j++) {
            b[i][n + 1] += b[i][j];
            if(b[i][n + 1] >= mo) b[i][n + 1] -= mo;
        }
    }
    b[n + 1][n + 1] = 1;
    equal(qwq, b); // equal用于赋值,似乎库函数里有赋值函数但我忘了)
    fpm(b, (l - 1) / n); // 快速幂
    fpm(qwq, (r - 1) / n);
    // 初始向量
    for(int i = 1; i <= n + 1; i++) sl[i] = 0;
    sl[1] = sr[1] = 1;
    for(int i = 2; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            if(y[j] == i) {
                sl[i] += sl[x[j]] * w[j] % mo;
                if(sl[i] >= mo) sl[i] -= mo;
            }
        }
        sr[i] = sl[i];
    }
    for(int i = 1; i <= n; i++) {
        sl[n + 1] += sl[i];
        if(sl[n + 1] >= mo) sl[n + 1] -= mo;
    }
    sr[n + 1] = sl[n + 1];
    mul(sl, b); // 矩阵乘
    mul(sr, qwq);
    // 统计答案,暴力处理边界
    ll ans = (sr[n + 1] - sl[n + 1] + mo) % mo;
    int cur = (l - 1) % n + 1;
    for(int i = cur; i <= n; i++) {
        ans += sl[i];
        if(ans >= mo) ans -= mo;
    }
    cur = (r - 1) % n + 1;
    for(int i = cur + 1; i <= n; i++) {
        ans += mo - sr[i];
        if(ans >= mo) ans -= mo;
    }
    printf("%lld\n", ans);

另有:一开始转移矩阵似乎写错了,想参考std却发现它自己都过不了样例,不好评价()最后找xht要了代码才AC,感谢大佬队友orz

posted @ 2024-08-10 22:37  Aderose_yr  阅读(143)  评论(0编辑  收藏  举报