杂题1

更好的阅读体验
这篇写满二十题就开杂题 2

P9350 [JOI 2023 Final] 宣传 2

左边有绝对值右边没有显然不是很好看,考虑拆绝对值。

那么相当于要同时满足 XiXjEiEjXjXi<=EiEj

移项就是 EjXj<=EiXi  Ej+XjEi+Xi

xi=EiXiyi=Ei+Xi,则可以再坐标轴上画出点,它能取到的就是它左下角的点。

问题转化成了选择最小的点覆盖所有的点,那么我们显然是选择随着 x 增加 y 下降的点,否则就可以将内层的点删掉。

直接按 y 从大到小排序后循环查找一遍即可。

const int N = 500010;
int n;

struct Node {
    LL x, y;
} q[N];

int main() {
    n = read();
    for (int i = 1; i <= n; i ++) {
        LL x = read(), e = read();
        q[i].x = e - x, q[i].y = e + x;
    }
    sort(q + 1, q + 1 + n, [&](Node a, Node b) {
        return a.y > b.y || a.y == b.y && a.x > b.x;
    }); 
    LL ans = 0, maxv = -2e9;
    for (int i = 1; i <= n; i ++)
        if (q[i].x > maxv) ans ++, maxv = q[i].x;
    printf("%lld\n", ans);
    return 0;
}

P9525 [JOISC 2022] 团队竞技

题目意思是要选择三个三元组 (a,b,c) 使得每个三元组中都有一个元素严格大于其它两对的对应元素。

做法是将所有 a, b, c,分别加入三个大根堆中,并且记录下标。然后循环取出三个堆的堆顶,如果三个属于不同的三元组,那就是答案;否则如果有两个或三个数属于同一个三元组它就一定不可能是答案,直接将该三元组的所有元素从堆里踢出就可以了。

由于我们每次会删掉一个三元组,所以总的复杂度是 O(nlogn)

如果最后都删完了也没有答案就说明无解。

const int N = 150010;
int n, x[N], y[N], z[N];
priority_queue<PII> A, B, C;
bool vis[N];

int main() {
    n = read();
    for (int i = 1; i <= n; i ++) {
        x[i] = read(), y[i] = read(), z[i] = read();
        A.push({x[i], i}); B.push({y[i], i}); C.push({z[i], i});
    }
    for (int i = 1; i <= n; i ++) {
        auto a = A.top().second, b = B.top().second, c = C.top().second;
        if (y[a] == y[b] || z[a] == z[c]) vis[a] = true;
        if (x[b] == x[a] || z[b] == z[c]) vis[b] = true;
        if (x[c] == x[a] || y[c] == y[b]) vis[c] = true;
        if (!vis[a] && !vis[b] && !vis[c]) {
            printf("%lld\n", (LL)x[a] + y[b] + z[c]);
            return 0;
        } 
        while (A.size() && vis[A.top().second]) A.pop();
        while (B.size() && vis[B.top().second]) B.pop();
        while (C.size() && vis[C.top().second]) C.pop();
    }
    puts("-1");
    return 0;
}

P8162 [JOI 2022 Final] 让我们赢得选举

考虑很明显的贪心。

首先所有的人不可能分散,同一时刻必定在同一个州演讲拿下更多的人和票。其次在一个州的演讲时间只可能是 ai,bi,0,否则都是浪费时间。

所以一个州就有三种情况,一种是不支持,一种是得票,一种是得票加得人。

由于每个州的人质量都一样的好,所以我们找人肯定先找 bi 小的,所以我们按照 b 进行排序。

接着考虑 DP,令 Fi,j,k 表示在前 i 个州中有 j 个州得票,k 个州得票加得人,这样做是 O(n4) 的,可以得到 66 分。

如何优化呢?我们考虑继续贪心,可以发现一定存在一个分界点使得前半部分都是得票或者得票加得人,后半部分都是不支持的。

简单证明一下。假设最后一个票+人后倒数第二个票+人之间存在不支持,那么把最后一个票+人的州往前移一定更优。以此类推两个票+人之间一定全是票。

所以我们可以简化 DP 状态为 Fi,j 表示前 i 个州有 j 个州是票+人,然后总复杂度就变成 O(n3) 的就可以卡过了。

具体实现我们可以枚举票+人州的上界,然后直接 DP 即可。

注意由于所有拿人的州一定比只拿票的州先演讲,所以在算一个州只拿票的时候人数为上界加一。

const int N = 510;
int n, k, a[N];
PII q[N];
double f[N][N];

double work(int B) {
    for (int i = 1; i <= n; i ++) a[i] = q[i].first;
    for (int i = 0; i <= n; i ++)
        for (int j = 0; j <= n; j ++)
            f[i][j] = 1e18;
    f[0][0] = 0;

    for (int i = 1; i <= n; i ++) {
        for (int j = 0; j <= min(B, i); j ++) {
            f[i][j] = f[i - 1][j] + q[i].first * 1.0 / (B + 1);
            if (j && q[i].second != INF) f[i][j] = min(f[i][j], f[i - 1][j - 1] + q[i].second * 1.0 / j); 
       }
    }

    double res = 1e18;
    for (int i = k; i <= n; i ++) res = min(res, f[i][B]);
    for (int i = k; i >= B; i--) {
        sort(a + 1 + i, a + 1 + n);
        double sum = 0;
        for (int j = i + 1; j <= k; j ++) sum += a[j];
        res = min(res, f[i][B] + sum / (B + 1)); 
    }
    return res;
}

int main() {
    n = read(), k = read();
    for (int i = 1; i <= n; i ++) {
        q[i] = {read(), read()};
        if (q[i].second == -1) q[i].second = INF;
    }
    sort(q + 1, q + 1 + n, [&](PII a, PII b) {
        return a.second < b.second;
    });

    double ans = 1e18;
    for (int i = 0; i <= k; i ++) ans = min(ans, work(i));
    printf("%.10lf\n", ans);
    return 0;
}

P9352 [JOI 2023 Final] 训猫

首先我们发现如果按照题目中所给方式建一棵树,则如果猫在 u,那么当它跳向某一个子树中的结点时(即猫 u 被砸跑了),u 的其它子树的结点就不能跳了。

所以我们考虑树形 DP,令 Fu 表示猫在以 u 为根的子树中最多跳多少下。令 vu 子树中满足 Pu>PvP 最大的结点。

Fu=max(Fu,Fv+dist(u,v)),其中 dist(u,v)u,v 在树上的距离,可以通过 lca 解决。

写出 DP 后稍加思索会发现,这个 DP 顺序是乱套的,可能从 ijk,但是 ki,j 之间,如果按照原方式建图就炸了。

但我们其实只要在建图的时候不要 (u,v) 而是 (Pu,Pv) 即可,然后按照 u1n 的顺序 DP,并用并查集维护一下 v 即可。

代码太长就不放了,可以去云剪贴板查看。

P11674 [USACO25JAN] Reachable Pairs G

考虑正着做比较困难,所以正难则反。我们考虑按照 t 从大到小执行 S 中的操作。

对于操作 0,相当于删掉所有与 u 相连的边,反着来就是用 u 连接当前所有的边;对于操作 1,我们发现它并不会改变图上的连通性,相当于让点 u 变为了一个虚点,反着做相当于给联通块加一个点。

具体来说我们用并查集维护,最开始现将所有虚点之间的连边添加进并查集中。

const int N = 200010, M = 400010;
int n, m;
int fa[N];
LL res, ans[N], sz[N]; 
char s[N];
vector<int> g[N];

int find(int x) {
    if (fa[x] == x) return x;
    return fa[x] = find(fa[x]);
}
void merge(int a, int b) {
    a = find(a), b = find(b);
    if (a == b) return;
    fa[b] = a;
    res -= sz[a] * (sz[a] - 1) / 2, res -= sz[b] * (sz[b] - 1) / 2;
    sz[a] += sz[b], res += sz[a] * (sz[a] - 1) / 2;
}

int main() {
    n = read(), m = read();
    rep(i, 1, n) fa[i] = i;
    scanf("%s", s + 1);
    while (m --) {
        int a = read(), b = read();
        g[a].push_back(b); g[b].push_back(a);
        if (s[a] == '1' && s[b] == '1') merge(a, b);
    }
    for (int u = n; u; u --) {
        if (s[u] == '0') {
            for (auto v : g[u]) 
                if (v > u || s[v] == '1') merge(v, u);
        }
        int v = find(u); res += sz[v] ++;
        ans[u] = res;
    }
    for (int i = 1; i <= n; i ++) printf("%lld\n", ans[i]);
    return 0;
}

P11676 [USACO25JAN] DFS Order P

考虑 DFS 序的性质,一棵子树的 DFS 序一定是连续的一段,并且可以发现 N750,所以考虑区间 DP。

先考虑初始没有边怎么做,显然我们应该搞一棵树。

fi,j 表示以 i 为根的子树在 DFS 序中结尾为 j 的最小代价。转移就是 fi,j=fi,k+fk+1,j+ai,k+1

对于初始有边的情况,我们可以考虑直接把原来的边全部删除。由于最后的图里一定不包含横叉边,但是会有返祖边。而如果原图中有返祖边那我们就不应该删去增大代价,所以我们只要在 DP 的时候把原先删除过的返祖边加回来即可。

具体来说考虑子树 [i,k][k+1,j],如果要把 [k+1,j] 塞进 [i,k] 那么增加的返祖边一定是 (l,k+1),(l,k+2),...,(l,j)。而我们只需要其中代价为负数的边,所以前缀和处理一下即可。

const int N = 810;
int n, a[N][N], s[N][N];
int f[N][N], cost; 

int main() {
    n = read();
    rep(j, 2, n) rep(i, 1, j - 1) a[i][j] = read(), cost += (a[i][j] < 0 ? -a[i][j] : 0);
    rep(i, 1, n) rep(j, 1, n) s[i][j] = s[i][j - 1] + (a[i][j] < 0 ? a[i][j] : 0);
    rep(i, 1, n) f[i][i] = 0;
    rep(len, 2, n) {
        for (int i = 1; i + len - 1 <= n; i ++) {
            int j = i + len - 1; f[i][j] = INF;
            rep(k, i, j - 1) f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + s[i][j] - s[i][k + 1] + a[i][k + 1]);
        } 
    }
    printf("%d\n", f[1][n] + cost);
    return 0;
}

AT_agc018_f [AGC018F] Two Trees

一个显然的性质是树上的点一定存在构造方式使得点权只取到 [1,1],这是显然的,因为最后每个子树的权值和只能为 11

然后我们先考虑无解的情况,因为对于一个结点它在两棵树上的子树和都要是 11,而 11(mod2),所以对于一个结点它的子树大小在两棵树上儿子数量奇偶性一定要相同,否则无解。

既然已经出现了奇偶性,所以我们考虑用一种方式将它们的奇偶性转化一下。对于本题,我们可以考虑构造一个图然后跑欧拉回路。

具体的构造方式是,对于度数为偶数的点,我们令它的权值为 0,度数为奇数的点,我们把它两棵树上的对应结点连一条横叉边。对于一条横叉边,如果它在欧拉回路上的方向是从第一棵树到第二棵树,则连接它的两个点的权值为 1,否则为 1

证明我们可以考虑对于欧拉回路拆成若干个环,对于每个环的贡献分类讨论:

  1. 从结点 u 向它的儿子走最后通过其它儿子或是横叉边回到 u
  2. 从结点 u 向它的儿子走最后通过 u 的父亲回到 u
  3. 从结点 u 向它的父亲走最后通过横叉边回到 u,或是从横叉边走从父亲回到 u

对于第一类,我们画图可以发现,由于该环一定经历了相反方向的横叉边(因为要从树一到树二再回到树一)和偶数点,所以它的贡献会全部抵消。对于第二类和第三类,继续画图会发现它们的贡献一定是 1 或是 1。因为从 u 的父亲走到 u 的这条边只会经过一次,所以第二类和第三类的环总共只会出现一次,这样就可以保证贡献和为 11

最后再考虑对于两棵树的根节点,我们要创建一个超级根连接两个根,可以直接再开辟一个节点,你也可以直接把两棵树的根节点连边,这是等价的。

const int N = 800010;
int n;
vector<int> g[N];
int rt1, rt2, cnt;
int ans[N], degs1[N], degs2[N], nx[N], ny[N], types[N]; 
bool st[N];

void add(int a, int b, int type) {
    g[a].push_back(++ cnt); g[b].push_back(cnt);
    nx[cnt] = a, ny[cnt] = b; 
    types[cnt] = type;
}

void dfs(int u) {
    while (g[u].size()) {
        int i = g[u].back(); g[u].pop_back();
        if (st[i]) continue;
        st[i] = true;
        int v = nx[i] ^ ny[i] ^ u;
        if (types[i] == 1) ans[min(u, v)] = u < v ? 1 : -1;
        dfs(v); 
    }
}

int main() {
    n = read();
    for (int i = 1; i <= n; i ++) {
        int x = read();
        if (x == -1) rt1 = i;
        else {
            add(x, i, 0);
            degs1[i] ++, degs1[x] ++; 
        }
    }
    for (int i = 1; i <= n; i ++) {
        int x = read();
        if (x == -1) rt2 = i;
        else {
            add(x + n, i + n, 0);
            degs2[i] ++, degs2[x] ++; 
        }
    }
    
    degs1[rt1] ++, degs2[rt2] ++; add(rt1, rt2 + n, 0);
    for (int i = 1; i <= n; i ++)
        if (degs1[i] % 2 != degs2[i] % 2) {
            puts("IMPOSSIBLE");
            return 0;
        }
    for (int i = 1; i <= n; i ++)
        if (degs1[i] & 1)
            add(i, i + n, 1);
    dfs(1);
    puts("POSSIBLE");
    printf("%d", ans[1]);
    for (int i = 2; i <= n; i ++) printf(" %d", ans[i]);
    puts("");
    return 0;
}

P11727 [JOIG 2025] 神経衰弱 2 / Pair Matching 2

根据题目的左右手机制,可以发现只有两种策略可以拿到 x 的贡献,即 xxxyxy,并且一张牌如果拿到了手上就一定要的到贡献,不然你拿这张牌肯定不优。

考虑线性 DP,令 prei 表示 i 第一次出现是在什么位置,fi,0 表示前 i 张牌中两张 Ai 连续取的最大得分,fi,1 则是不连续取。

那么显然有 fi,0=maxj[1,prei){fj,0/1}+Vai,即枚举 xx 前的部分的最大值加上 xx 的贡献。

对于 xyxy 的情况我们也可以同样地得出方程 fi,1=maxprej<prei{fj,0}+Vai。注意这里由于 DP 的定义我们只考虑了 xyx 的情况,所以我们不能转移 fj,1,否则就会出现 xyxaba 之类的情况。

对于第一个式子因为 prei<i,所以我们维护一下前缀最大值,对于第二个式子我们直接用树状数组维护一下即可。

const int N = 800010;
int n, a[N], pre[N], p[N];
LL v[N], f[N][2], pm[N], tr[N];

void add(int x, LL k) {
    for (; x <= n * 2; x += lowbit(x)) tr[x] = max(tr[x], k);
}
LL query(int x) {
    LL res = 0;
    for (; x; x -= lowbit(x)) res = max(res, tr[x]);
    return res;
}

int main() {
    n = read();
    rep(i, 1, n * 2) a[i] = read();
    rep(i, 1, n) v[i] = read();
    rep(i, 1, n * 2) 
        if (p[a[i]]) pre[i] = p[a[i]];
        else p[a[i]] = i;
    rep(i, 1, n * 2) 
        if (!pre[i]) pm[i] = pm[i - 1];
        else {
            f[i][0] = pm[pre[i] - 1] + v[a[i]];
            f[i][1] = query(pre[i] - 1) + v[a[i]];
            add(pre[i], f[i][0]); 
            pm[i] = max(pm[i - 1], max(f[i][0], f[i][1]));
        }
    printf("%lld\n", pm[n * 2]);
    return 0;
}

P1948 [USACO08JAN] Telephone Lines S

DP,图论,分层图,最短路,二分,BFS

考虑一个 DP,令 fi,j 表示从 1ij 条边免费,其中不免费的最大边的最小值。

那么 max(fi,j,wi,k)fk,j 还有 fi,jfk,j+1,其中 ki 的邻点,前一种转移对应 (i,k) 不免费,后一种对应免费。

但这实际上有后效性,因为图上可能有环。但我们可以运用迭代的思想,借助 SPFA 进行动态规划,直至不能再更新为止。从最短路问题出发理解,其实可以看成二维结点 (i,j) 作为图上的结点的分层图上跑最短路,一共是 N×K 个结点和 M×K 个边。

最短路算法可以使用 SPFA 也可以堆优化 dijkstra,但是为了防止被卡还是后者好,毕竟这是一张正权图。

本题还有另一种方法,由于本题答案显然具有单调性,所以可以二分答案,问题就转化为判定是否存在合法的方案。假设当前二分到了 mid,我们考虑将所有边权大于 mid 的边权置为 1,小于等于的置为 0,由于大于的一定要免费,所以我们可以求出最短路看看是否小于 K。对于这种 0,1 类型的最短路可以通过双端队列 BFS 解决。代码我没有写过。

const int N = 1010, M = 20010;
int n, m, k;
int h[N], e[M], ne[M], w[M], idx;
int d[N][N];
bool vis[N][N];
priority_queue<pair<int, PII> > q;

void add(int a, int b, int c) {
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

void dijkstra(int st) {
    memset(d, 0x7f, sizeof d); d[st][0] = 0;
    q.push({0, {st, 0}});  
    while (q.size()) {
        int i = q.top().second.first, j = q.top().second.second;
        q.pop();
        if (vis[i][j]) continue;
        vis[i][j] = true;
        for (int _ = h[i]; ~_; _ = ne[_]) {
            int y = e[_], z = w[_];
            if (d[y][j] > max(d[i][j], z)) {
                d[y][j] = max(d[i][j], z);
                q.push(make_pair(-d[y][j], make_pair(y, j)));
            }
            if (j < k && d[y][j + 1] > d[i][j]) {
                d[y][j + 1] = d[i][j];
                q.push(make_pair(-d[y][j+1], make_pair(y, j+1)));
            }
        }
    }
}

int main() {
    n = read(), m = read(), k = read();
    memset(h, -1, sizeof h);
    while (m --) {
        int a = read(), b = read(), c = read();
        add(a, b, c); add(b, a, c);
    }
    dijkstra(1);
    int ans = 0x7f7f7f7f;
    for (int i = 0; i <= k; i ++) ans = min(ans, d[n][i]);
    printf("%d\n", ans == 0x7f7f7f7f ? -1 : ans);
    return 0;
}

[NOIP 2009 提高组] 最优贸易

DP,图论,最短路,缩点

考虑题意要求一条从 1n 的路径使得路径上有两个点 pq,满足 p<q 且使得 cqcp 最大。求这个最大值。

考虑枚举分界点 i,我们可以求出 1i 的最小值和 in 的最大值,两者相减。然后对于所有分界点枚举一遍即可。

那么如何求这些最小值和最大值呢?一种方法是直接 DP,但可能有环,所以考虑缩点。另一种方式是建一张反图,然后分别在原图从 1 开始,反图从 n 开始做就可以了。计算过程中只要把 SPFA 中 d[x] + w(x, y) 更新 d[y] 改成 min(d[x], price[y]) 即可。注意本题不能使用 dijkstra,这和负权图其实是一个道理,即 dijkstra 中第一次访问某个点时不能的得到最短路,那么正确性就会出现问题,因为 dijkstra 的复杂度是通过只访问每个点一次来保证的。

代码写复杂了,其实可以通过对每个边设置类型少写一遍 spfa。

const int N = 100010;
int n, m, c[N];
vector<int> G1[N], G2[N]; 
int d1[N], d2[N]; 
bool vis[N];

void spfa() {
    memset(d1, 0x3f, sizeof d1); memset(d2, -0x3f, sizeof d2);
    queue<int> q; q.push(1); vis[1] = true, d1[1] = c[1];
    while (q.size()) {
        int u = q.front(); q.pop();
        vis[u] = false;
        for (auto v : G1[u]) 
            if (d1[v] > min(d1[u], c[v])) {
                d1[v] = min(d1[u], c[v]);
                if (!vis[v]) vis[v] = true, q.push(v);
            }
    }
    while (!q.empty()) q.pop();
    memset(vis, false, sizeof vis);
    q.push(n); vis[n] = true, d2[n] = c[n];
    while (q.size()) {
        int u = q.front(); q.pop();
        vis[u] = false;
        for (auto v : G2[u]) 
            if (d2[v] < max(d2[u], c[v])) {
                d2[v] = max(d2[u], c[v]);
                if (!vis[v]) vis[v] = true, q.push(v);
            }
    } 
}

int main() {
    n = read(), m = read();
    for (int i = 1; i <= n; i ++) c[i] = read();
    while (m --) {
        int a = read(), b = read(), op = read();
        G1[a].push_back(b); G2[b].push_back(a);
        if (op == 2) G1[b].push_back(a), G2[a].push_back(b);
    } 
    spfa();
    int ans = 0;
    for (int i = 1; i <= n; i ++) ans = max(ans, d2[i] - d1[i]);
    printf("%d\n", ans);
    return 0;
}

P3008 [USACO11JAN] Roads and Planes G

图论,最短路问题,DP

考虑题目给出的是一张带有负权边的图,如果正常跑最短路只能 SPFA,但是会被卡掉。

然而我们发现题目给了一些很好的性质,概括一下就是有一堆边权为正的双向边,和一堆边权为负的单向边,而且保证一条单向边如果可以从 AB 就一定不存在路径可以从 BA。我们考虑把所有连通的双向边组成的连通块看作一个结点,整张图就变成了一张 DAG,就可以直接拓扑排序做。然后连通块内的点可以直接跑 dijkstra,复杂度就对了。

同时尽量不要使用所谓的 SLF 之类的优化 SPFA,它的复杂度没法保证,看 lyd 的课学到了通过一堆三角形来把 SLF 优化卡成指数级别。

const int N = 25010, M = 150010;
int n, m1, m2, s;
int h[N], e[M], ne[M], w[M], tp[M], idx;
int d[N], degs[N], c[N], cnt;  
vector<int> a[N];
bool vis[N];
priority_queue<PII, vector<PII>, greater<PII> > heap;
queue<int> q;

void add(int a, int b, int c, int op) {
    e[idx] = b, w[idx] = c, tp[idx] = op, ne[idx] = h[a], h[a] = idx ++;
}

void dfs(int u) {
    c[u] = cnt;
    a[cnt].push_back(u);
    for (int i = h[u]; ~i; i = ne[i]) {
        int v = e[i];
        if (tp[i] == 1) continue;
        if (!c[v]) dfs(v);
    }
}

void dijkstra(int B) {
    for (auto u : a[B]) heap.push({d[u], u}); 
    while (heap.size()) {
        int u = heap.top().second; heap.pop();
        if (vis[u]) continue;
        vis[u] = true;
        for (int i = h[u]; ~i; i = ne[i]) {
            int v = e[i];
            if (tp[i] == 0) {
                if (d[v] > d[u] + w[i]) {
                    d[v] = d[u] + w[i];
                    heap.push({d[v], v});
                }
            } else {
                d[v] = min(d[v], d[u] + w[i]);
                if (-- degs[c[v]] == 0) q.push(c[v]);
            }
        }
    } 
}

int main() {
    n = read(), m1 = read(), m2 = read(), s = read();
    memset(h, -1, sizeof h);
    while (m1 --) {
        int a = read(), b = read(), c = read();
        add(a, b, c, 0);
        add(b, a, c, 0);
    }
    while (m2 --) {
        int a = read(), b = read(), c = read();
        add(a, b, c, 1);
    }
    for (int i = 1; i <= n; i ++)
        if (!c[i]) { cnt ++; dfs(i); }
    for (int u = 1; u <= n; u ++)
        for (int i = h[u]; ~i; i = ne[i]) 
            if (tp[i] == 1) degs[c[e[i]]] ++;
    for (int i = 1; i <= cnt; i ++)
        if (!degs[i]) q.push(i);
    memset(d, 0x3f, sizeof d);
    d[s] = 0;
    while (q.size()) {
        int u = q.front(); q.pop();
        dijkstra(u);    
    }
    for (int i = 1; i <= n; i ++) 
        if (d[i] > n * 10000) puts("NO PATH");
        else printf("%d\n", d[i]);
    return 0;
}

P1347 排序

图论,最短路问题。

思路很板的一道题,考虑小于关系可以传递,所以相当于一个传递闭包。n 的范围也很小,所以直接 Floyd 维护传递闭包即可。

const int N = 35;
int n, m;
int d[N][N];
char s[N];

int floyd() {
    rep(k, 0, n - 1) rep(i, 0, n - 1) rep(j, 0, n - 1) {
        d[i][j] |= d[i][k] & d[k][j];
        if (d[i][j] == d[j][i] && d[i][j] == 1 && i != j) return -1;
    }
    rep(i, 0, n - 1) rep(j, 0, n - 1)
        if (d[i][j] == d[j][i] && d[i][j] == 0 && i != j) return 0;
    return 1;
}

void solve() {
    memset(d, 0, sizeof d);
    bool flag = false;
    rep(i, 1, m) {
        scanf("%s", s);
        if (s[0] == s[2]) {
            printf("Inconsistency found after %d relations.\n", i);
            flag = true;
        }
        if (flag) continue;
        d[s[0] - 'A'][s[2] - 'A'] = 1;
        int res = floyd();
        if (res == -1) printf("Inconsistency found after %d relations.\n", i), flag = true;
        else if (res == 1) {
            printf("Sorted sequence determined after %d relations: ", i);
            pair<int, char> ans[N];
            rep(j, 0, n - 1) ans[j] = {0, j + 'A'};
            rep(i, 0, n - 1) rep(j, 0, n - 1)
                if (d[i][j]) ans[j].first ++;
            sort(ans, ans + n);
            rep(i, 0, n - 1) printf("%c", ans[i].second);
            puts(".");
            flag = true; 
        } 
    }
    if (!flag) puts("Sorted sequence cannot be determined.");
}

int main() {
    while (scanf("%d%d", &n, &m) == 2, n || m) solve();
    return 0;
}

P10927 Sightseeing trip

图论,最短路问题。

这个题很巧妙,我们先来重申一下 Floyd 中 Di,j 的定义,这也是很多人没有在意的。

即经过若干个编号不超过 k 的节点,从 ij 的最短路,其中 k 就是阶段,所以要放在最外层循环。所以状态表示中本来是有三维的,只是省掉了一维。

对于本题,考虑在每次外层循环 k 新开始的时候 D 中是存了经过编号不超过 k1 的最短路的。显然,min1i<j<k{di,j+aj,k+ak,i} 就是满足编号不超过 k 的结点组成且经过 k 的最小环长度,理由是 ij 可以看做枚举了环上两个离 k 最近的点。

const int N = 110, M = 20010;
int n, m;
int d[N][N], a[N][N], p[N][N];
vector<int> path;

void get_path(int i, int j) {
    if (!p[i][j]) return;
    get_path(i, p[i][j]);
    path.push_back(p[i][j]);
    get_path(p[i][j], j);
}

int main() {
    n = read(), m = read();
    memset(a, 0x3f, sizeof a); 
    rep(i, 1, n) a[i][i] = 0;
    rep(i, 1, m) {
        int u = read(), v = read(), w = read();
        a[u][v] = a[v][u] = min(a[u][v], w);        
    }
    memcpy(d, a, sizeof a);
    LL ans = INF;
    rep(k, 1, n) {
        rep(i, 1, k - 1) rep(j, i + 1, k - 1) {
            if ((LL)d[i][j] + a[j][k] + a[k][i] < ans) {
                ans = (LL)d[i][j] + a[j][k] + a[k][i];
                path.clear();
                path.push_back(i); 
                get_path(i, j);
                path.push_back(j);
                path.push_back(k);
            }
        }
        rep(i, 1, n) rep(j, 1, n) 
            if (d[i][j] > d[i][k] + d[k][j]) 
                d[i][j] = d[i][k] + d[k][j], p[i][j] = k;
    }
    if (ans == INF) puts("No solution.");
    else {
        for (auto u : path) printf("%d ", u);
        puts("");
    }
    return 0;
}

最近更新:2024/3/4

posted @ 2025-02-25 16:14  比翼の鼠  阅读(3)  评论(0编辑  收藏  举报
//雪花飘落效果
评论
收藏
关注
推荐
深色
回顶
收起
点击右上角即可分享
微信分享提示