striving & singing

2020-11-09

入阵曲

https://www.luogu.com.cn/problem/P3941

常用套路,把一个矩形拆为两个前缀和之差,然后用桶记录$\bmod k$得到的值对应的前缀和有几个即可。

#include<cstdio>

int n, m, d, a[410][410], cnt[1000100], sum[410][410];
long long ans;

int main() {
    scanf("%d%d%d", &n, &m, &d);
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j) scanf("%d", &sum[i][j]), sum[i][j] = (sum[i][j] + sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + d) % d;
    cnt[0] = 1;
    for (int i = 1; i <= m; ++i)
        for (int j = i; j <= m; ++j) {
            for (int k = 1; k <= n; ++k) {
                ans += cnt[(sum[k][j] - sum[k][i - 1] + d) % d];
                ++cnt[(sum[k][j] - sum[k][i - 1] + d) % d];
            }
            for (int k = 1; k <= n; ++k) --cnt[(sum[k][j] - sum[k][i - 1] + d) % d];
        }
    printf("%lld\n", ans);
    return 0;
}

将军令

https://www.luogu.com.cn/problem/P3942

经典题,可以树形 DP,不过好像不太好写,贪心要更好写一些。每次取出没被覆盖的点中深度最大的一个,然后以它的 k 级父亲为中心覆盖即可。判断一个点有没有被覆盖可以开一个数组$dis[x]$表示 x 的子树中所有覆盖中心与 x 的距离最短是多少即可。两种做法总复杂度都是$O(nk)$。

#include<cstdio>
#include<cstring>
#include<algorithm>

int n, k, t, depth[100100], p[100100], dis[100100], f[100100], ans;
struct edge { int to, next; };
struct graph {
    int ecnt = 1, head[100100];
    edge edges[200100];
    inline void addedge(int u, int v) {
        edges[++ecnt].to = v;
        edges[ecnt].next = head[u];
        head[u] = ecnt;
    }
}g;

void dfs(int x, int lst) {
    f[x] = g.edges[lst ^ 1].to;
    depth[x] = depth[f[x]] + 1;
    for (int i = g.head[x]; i; i = g.edges[i].next) {
        if ((i ^ lst) == 1) continue;
        dfs(g.edges[i].to, i);
    }
}
inline bool cmp(int i, int j) { return depth[i] > depth[j]; }
inline int min(int a, int b) { return a < b ? a : b; }

int main() {
    scanf("%d%d%d", &n, &k, &t);
    for (int i = 1; i < n; ++i) {
        int u, v; scanf("%d%d", &u, &v);
        g.addedge(u, v); g.addedge(v, u);
    }
    dfs(1, 0);
    for (int i = 1; i <= n; ++i) p[i] = i;
    std::sort(p + 1, p + n + 1, cmp);
    memset(dis, 0x3f, sizeof(int) * (n + 1));
    for (int i = 1; i <= n; ++i) {
        int x = p[i];
        bool ok = false;
        int d = 0, ca = x;
        for (; d <= k && !ok; ++d, ca = f[ca]) if (dis[ca] + d <= k) ok = true;
        if (ok) continue;
        for (d = k, ca = x; d >= 0; --d, ca = f[ca]) dis[ca] = min(dis[ca], d);
        for (d = 1; d <= k; ++d, ca = f[ca]) dis[ca] = min(dis[ca], d);
        ++ans;
    }
    printf("%d\n", ans);
    return 0;
}

星空

https://www.luogu.com.cn/problem/P3943

首先可以把序列差分一下,这样每次操作就转化为把一个长度为$b_i+1$的区间的两个端点取反。目标是把所有的 0 变为 1。把两个 1 取反是没有意义的,而把一个 0 和一个 1 取反相当于把 0 移动到了 1 的位置,把两个 0 取反相当于两个 0 移动到一起时会抵消为 1。

于是可以状压 DP,设$dp[s]$表示状态为 s 时需要的最少操作次数,其中 s 表示所有的 0 的位置上的状态,即是 0 还是 1。枚举把哪两个 0 变为 1 即可。根据愤怒的小鸟那题的 trick 可以固定把第一个 0 变为 1,然后枚举另一个 0 ,这样就把 DP 的复杂度从$O(2^kk^2)$降到了$O(2^kk)$。

另外还要预处理把某两个 0 变为 1 需要的最少操作次数,直接 bfs 即可。看起来好像还能用完全背包,但实际上完全背包是错的,因为在操作时要求操作的区间不能越界,而完全背包只考虑距离之差没有考虑越界的情况,会导致答案错误。

总复杂度$O(nmk+2^kk)$。

#include<cstdio>
#include<cstring>
#include<queue>

int n, k, m, a[20], acnt, b[65], cost[20][40010], dp[66000];
bool flag[40010], vis[40010];
std::queue<int> q;

inline int min(int a, int b) { return a < b ? a : b; }
void bfs(int s) {
    memset(vis, 0, sizeof(bool) * (n + 1));
    cost[s][a[s]] = 0;
    q.push(a[s]);
    while (!q.empty()) {
        int x = q.front();
        q.pop();
        if (vis[x]) continue;
        vis[x] = true;
        for (int i = 1; i <= m; ++i) {
            if (x + b[i] - 1 <= n && cost[s][x + b[i] - 1] > cost[s][x] + 1) {
                cost[s][x + b[i] - 1] = cost[s][x] + 1;
                q.push(x + b[i] - 1);
            }
            if (x - b[i] + 1 >= 1 && cost[s][x - b[i] + 1] > cost[s][x] + 1) {
                cost[s][x - b[i] + 1] = cost[s][x] + 1;
                q.push(x - b[i] + 1);
            }
        }
    }
}

int main() {
    scanf("%d%d%d", &n, &k, &m);
    for (int i = 1; i <= k; ++i) scanf("%d", &a[i]), flag[a[i]] = !flag[a[i]], flag[a[i] + 1] = !flag[a[i] + 1];
    ++n;
    for (int i = 1; i <= n; ++i) if (flag[i]) a[++acnt] = i;
    k = acnt;
    for (int i = 1; i <= m; ++i) scanf("%d", &b[i]), ++b[i];
    memset(cost, 0x3f, sizeof cost);
    for (int i = 1; i <= k; ++i) bfs(i);
    memset(dp, 0x3f, sizeof dp);
    dp[0] = 0;
    for (int s = 1; s < (1 << k); ++s) {
        int x = 0;
        for (int i = 1; i <= k; ++i)
            if (s & (1 << i - 1)) {
                if (!x) x = i;
                else dp[s] = min(dp[s], dp[s ^ (1 << x - 1) ^ (1 << i - 1)] + cost[x][a[i]]);
            }
    }
    printf("%d\n", dp[(1 << k) - 1]);
    return 0;
}

[HNOI2009]最小圈

https://www.luogu.com.cn/problem/P3199

前几天模拟赛的原题。需要注意的是 bfs 式 spfa 会被卡飞,需要写复杂度错误的 dfs 式 spfa 才能通过。

#include<cstdio>
#include<cstring>
#include<queue>

const double eps = 1e-10;
int n, m;
double minw = 0x3fffffff, maxw = -0x3fffffff, dis[3010];
bool vis[3010];
struct edge { int to, next; double w; };
struct graph {
    int ecnt = 1, head[3010];
    edge edges[10010];
    inline void addedge(int u, int v, double w) {
        edges[++ecnt].to = v;
        edges[ecnt].w = w;
        edges[ecnt].next = head[u];
        head[u] = ecnt;
    }
}g;
std::queue<int> q;

inline double min(double a, double b) { return a < b ? a : b; }
inline double max(double a, double b) { return a > b ? a : b; }
bool dfs(int x, const double mid) {
    if (vis[x]) return true;
    vis[x] = true;
    for (int i = g.head[x]; i; i = g.edges[i].next) {
        int &v = g.edges[i].to; double w = g.edges[i].w - mid;
        if (dis[v] > dis[x] + w) {
            dis[v] = dis[x] + w;
            if (dfs(v, mid)) { vis[x] = false; return true; }
        }
    }
    vis[x] = false;
    return false;
}
inline bool check(double mid) {
    memset(dis, 0, sizeof(double) * (n + 1));
    for (int i = 1; i <= n; ++i) if (dfs(i, mid)) return true;
    return false;
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; ++i) {
        int u, v; double w; scanf("%d%d%lf", &u, &v, &w);
        g.addedge(u, v, w);
        minw = min(minw, w); maxw = max(maxw, w);
    }
    double l = minw, r = maxw + eps;
    while (l + eps < r) {
        double mid = l + (r - l) / 2;
        if (check(mid)) r = mid;
        else l = mid + eps;
    }
    printf("%.8lf\n", l);
    return 0;
}
return