做题整理 4.25

发布时间 2023-04-25 13:49:18作者: f2021ljh

字符串

P3538 [POI2012]OKR-A Horrible Poem

给定字符串,多次询问其子串的最小循环节长度。

由于循环节长度 \(len\) 一定是子串长度的约数,我们可以不断试除 \(len\) 的最小质因子,并判断是否合法,更新 \(ans\) 的最小值。线性筛 预处理所有数(\(\le5\times10^5\))的最小质因子;判断是否是循环节可以用 哈希,若 \(hash(l,r-len+1)=hash(l+len-1,r)\)\(len\) 为循环节。

最短路

P2901 [USACO08MAR]Cow Jogging G

有很多下坡道路 \((x,y,d)\),若 \(x>y\) 则是 \(x\) 通向 \(y\)。求 \(n\)\(1\) 的前 \(k\) 短的路径长度。\(1\le n\le10^3\)\(1\le m\le10^4\)\(1\le k\le100\)

题意告诉我们,这张图是一个 DAG,并且对于有向边 \((u,v)\) 一定满足 \(u>v\)。为了方便,我们建出反图,答案不变。

考虑在图上 DP,设 \(f(u,i)\) 表示从 \(1\) 出发到 \(u\)\(i\) 短的路径长度,对于一条边 \((u,v,w)\),将 \(f(u)\) 的每一项加上 \(w\) 并添加到 \(f(v)\) 中。由于 \(f(u),f(v)\) 从小到大有序,我们采用归并排序。由于 \(u<v\),我们只要从小到大枚举节点编号即可满足无后效性。时间复杂度 \(O(mk)\)

关于归并排序,C++ 中有一个函数 std :: merge(firstIt1, lastIt1, firstIt2, lastIt2, resIt, cmp) 可以方便地实现。cmp 参数可以省略。效果是把两个已经排好序的数组归并到 resIt 中。

P1522 [USACO2.4] 牛的旅行 Cow Tours

平面上若干节点,节点间有连边,形成若干连通块,边的长度为欧几里得距离。一个连通块的 直径 是指其中两两节点之间的最短路长度最大值。现在要在两个 属于不同连通块 的节点间添加一条边,使得 新形成的大连通块的 直径尽可能小。求出这个最小值。\(1\le n\le150\)

首先用 DFS 求出连通块。然后用 Floyd 处理出节点两两之间的最短路。

枚举不连通的点对 \((x,y)\),加边。设 \(maxd(x)\) 表示在连通块内其他点与 \(x\) 的最短路长度最大值。显然如果这条新加的边 \((x,y)\) 在新连通块的直径上,那么直径长度为 \(maxd(x)+dis(x,y)+maxd(y)\)

这道题的一个坑点在于,新连通块的直径 不一定经过新加入的边,也有可能是原来两个连通块的直径的 max 值。

另一个坑点在于,所求的 不是全局最大直径,而只是新连通块的直径。

针对两个坑点的 Hack 数据

P3489 [POI2009]WIE-Hexer

\(n\) 个村庄,\(m\) 条双向道路,\(p\) 种怪物,\(k\) 个铁匠,每个铁匠会居住在一个村庄里,你到了那个村庄后可以让他给你打造剑,每个铁匠打造的剑都可以对付一些特定种类的怪物。

每条道路上都可能出现一些特定种类的怪物,每条道路都有一个通过所需要的时间,现在要从 \(1\)​ 走到 \(n\)​,初始的时候你没有剑,要求在经过一条道路的时候,对于任意一种可能出现在这条道路上的的怪物,你都有已经有至少一把剑可以对付他,求从 \(1\)​ 走到 \(n\)​ 的最短时间(打造剑不需要时间)。

\(1\le n\le 200\)\(0\le m\le 3000\)\(1\le p\le 13\)\(0\le k\le n\)

注意:剑 可以多次使用。注意到 \(p\) 的范围很小,考虑状态压缩,记录当前都有哪些剑。

在 Dijkstra 的过程中,想要用一条边 \((u,v)\) 进行松弛,还需要满足当前在 \(u\) 拥有剑的状态足以通过这条边。

P1948 [USACO08JAN]Telephone Lines S

\(n\) 个互不连通的点,其中有 \(p\) 对点 \((a_i,b_i)\) 可以花费 \(l_i\) 连一条无向边。有 \(k\) 次机会可以 免费连边。连边的费用为所有边花费的 最大值。问要使 \(1\)\(n\) 连通,最小费用为多少。\(1\le n\le1000\)\(1\le p\le10000\)\(1\le l_i\le10^6\)

二分答案 \(x\),将题意转化一下:除了免费的边,只使用费用不超过 \(x\) 的边,能否使 \(1\)\(n\) 连通。

再转化一下:从 \(1\)\(n\) 的所有路径中,费用超过 \(x\) 的边数最少是否超过 \(k\)

于是将边权设为 \([l_i>x]\),Dijkstra 求最短路即可。时间复杂度 \(O(p\log n\log l_i)\)

P3008 [USACO11JAN]Roads and Planes G

\(T\) 个城镇,它们之间通过 \(R\) 条道路和航线连接,道路或航线 \(i\) 的花费为 \(C_i\)

道路是双向的,而航线是单向的;道路的花费非负,而航线的花费可能为负。

保证如果有一条航线可以从 \(A_i\)\(B_i\),那么不可能通过一些道路或航线从 \(B_i\) 回到 \(A_i\)

输出从起点城镇 \(S\) 到每个城镇的最少花费,无法到达输出 \(\texttt{NO PATH}\)

\(1\le T\le25000\)\(1\le R\le50000\)\(|C_i|\le10000\)

有向边不能用 Dijkstra,但有向边之间无环,所以可以把无向边缩成 SCC(即连通块)形成一个 DAG,然后拓扑排序找最短路。每个连通块内就可以 Dijkstra 了。

#include <queue>
#include <vector>
#include <cstring>
#include <iostream>
#define f(x, y, z) for (int x = (y); x <= (z); ++x)
using namespace std;
typedef pair<int, int> pii;
int constexpr N = 2.5e4 + 10, M = 1.5e5 + 10;
int constexpr INF = 0x7f7f7f7f;
int n, r, p, s;

int head[N], cnt;
struct Edge {
    int to, nxt, val;
} e[M];
inline void add(int from, int to, int val) {
    e[++cnt].to = to, e[cnt].nxt = head[from], e[cnt].val = val, head[from] = cnt;
    return;
}

int c[N], tot;
vector<int> v[N];
void dfs(int u) {
    c[u] = tot;
    v[tot].push_back(u);
    for (int i = head[u]; i; i = e[i].nxt) {
        int v = e[i].to;
        if (!c[v]) dfs(v);
    }
    return;
}

int deg[N];
queue<int> q;
priority_queue<pii> pq;
int d[N];
bool vis[N];

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    
    cin >> n >> r >> p >> s;
    f(i, 1, r) {
        int u, v, w;
        cin >> u >> v >> w;
        add(u, v, w), add(v, u, w);
    }
    f(i, 1, n) if (!c[i]) ++tot, dfs(i);
    f(i, 1, p) {
        int u, v, w;
        cin >> u >> v >> w;
        add(u, v, w);
        ++deg[c[v]];
    }
    f(i, 1, tot) if (!deg[i]) q.push(i);
    memset(d, 0x7f, sizeof d);
    d[s] = 0;
    while (!q.empty()) {
        int t = q.front(); q.pop();
        for (int i: v[t]) pq.emplace(-d[i], i);
        while (!pq.empty()) {
            int u = pq.top().second; pq.pop();
            if (vis[u]) continue;
            vis[u] = true;
            for (int i = head[u]; i; i = e[i].nxt) {
                int v = e[i].to, w = e[i].val;
                if (d[v] > d[u] + w) {
                    d[v] = d[u] + w;
                    if (c[u] == c[v]) pq.emplace(-d[v], v);
                }
                if (c[u] ^ c[v])
                    if (!--deg[c[v]]) q.push(c[v]);
            }
        }
    }
    f(i, 1, n)
        if (d[i] > n * 10000) cout << "NO PATH\n";
        else cout << d[i] << '\n';
    
    return 0;
}

差分约束

P4878 [USACO05DEC]Layout G

FJ 有编号为 \(1,\dots,n\)\(n\) 头奶牛。开始时,奶牛们 按照编号顺序 来排队。

\(m_1\) 对奶牛希望彼此之间的距离小于等于 \(d_{1i}\)\(1\le i\le m_1\));有 \(m_2\) 对奶牛希望彼此之间的距离大于等于 \(d_{2i}\)\(1\le i\le m_2\))。

计算 \(1\) 号奶牛和 \(n\) 号奶牛之间的距离最大为多少。可能有多头奶牛在同一位置上。无解输出 \(\texttt{-1}\),可以无穷远输出 \(\texttt{-2}\)

\(2\le n\le1000\)\(1\le m_1,m_2\le10^4\)\(1\le d_{1i},d_{2i}\le10^6\)

建立差分约束系统。首先判无解,从超级源点 \(0\) 向其他所有点连边,跑 SPFA 看有没有负环即可。然后再从 \(1\) 出发跑最短路。

注意:奶牛们按照编号顺序来排队,可能有多头奶牛在同一位置上。所以有一个 隐含条件 就是 \(a_i\ge a_{i-1}\),于是从 \(i\)\(i-1\) 连边。

P3275 [SCOI2011]糖果

幼儿园里有 \(N\) 个小朋友,\(\text{lxhgww}\) 老师现在想要给这些小朋友们分配糖果,并且满足小朋友们的 \(K\) 个要求。求至少需要准备多少个糖果。某个要求 \((X,A,B)\) 的意义如下:

  • 如果 \(X=1\), 表示第 \(A\) 个小朋友分到的糖果必须和第 \(B\) 个小朋友分到的糖果一样多;
  • 如果 \(X=2\), 表示第 \(A\) 个小朋友分到的糖果必须少于第 \(B\) 个小朋友分到的糖果;
  • 如果 \(X=3\), 表示第 \(A\) 个小朋友分到的糖果必须不少于第 \(B\) 个小朋友分到的糖果;
  • 如果 \(X=4\), 表示第 \(A\) 个小朋友分到的糖果必须多于第 \(B\) 个小朋友分到的糖果;
  • 如果 \(X=5\), 表示第 \(A\) 个小朋友分到的糖果必须不多于第 \(B\) 个小朋友分到的糖果。

无解输出 \(\texttt{-1}\)\(N\leq100000\)\(K\leq100000\)

直接差分约束跑 SPFA 会被 Hack 数据卡掉。这道题的一个关键性质是:边权全部为 \(0\)\(1\)

首先考虑边权为 \(0\) 的边。用 Tarjan 算法进行缩点,那么一个 SCC 内的点分到的糖果一定相同,并且具体的糖果数相互独立。

然后重构图,并且加入边权为 \(1\) 的边。此时直接在形成的 DAG 上拓扑排序 DP 即可。无解情况是加入边权为 \(1\) 的边后出现环,可以记录拓扑排序过程中是否每个点都入过队,如果存在没有入过队的点说明有环。

0/1 分数规划

P3199 [HNOI2009]最小圈

带权的有向图 \(G=(V,E)\) 以及 \(w:E\rightarrow\mathbb R\),每条边 \(e=(i,j)(i\neq j,i\in V,j\in V)\) 的权值定义为 \(w_{i,j}\)。令 \(n=|V|\)\(c=(c_1,c_2,\cdots,c_k)\)\(c_i\in V\))是 \(G\) 中的一个 当且仅当 \((c_i,c_{i+1})\)\(1\le i<k\))和 \((c_k,c_1)\) 都在 \(E\) 中,这时称 \(k\) 为圈 \(c\) 的长度。令 \(c_{k+1}=c_1\),定义圈 \(c=(c_1,c_2,\cdots,c_k)\)平均值\(\mu(c)=\dfrac1k\sum\limits_{i=1}^{k} w_{c_i,c_{i+1}}\),即 \(c\) 上所有边的权值的平均值。给定图 \(G\),求出 \(\min\mu(c)\)\(n\le 3000\)\(m\le 10000\)

考虑分数规划,设 \(\mu(c)>X\),二分 \(X\)。对于固定的 \(X\),有

\[\sum_{i=1}^k\left(w_{c_i,c_{i+1}}-X\right)<0. \]

于是把所有边权减 \(X\),判断是否有负环即可。采用 DFS-SPFA 可以更快地解决。(题目没有卡 SPFA,所以可以水过)