首页 > 其他分享 >USACO23JAN P【杂题】

USACO23JAN P【杂题】

时间:2023-03-01 13:33:05浏览次数:46  
标签:limits int sum back -- USACO23JAN 杂题 LL

A. [USACO23JAN] Tractor Paths P

有 \(n\) 个区间,第 \(i\) 个区间为 \([l_i,r_i]\)。保证 \(l_1<l_2<\cdots<l_n\) 且 \(r_1<r_2<\cdots<r_n\)。其中一部分区间是特殊的。

如果第 \(i\) 个区间和第 \(j\) 个区间相交,那么 \(i,j\) 之间有一条边。保证 \(1,n\) 联通。

\(q\) 次询问,每次给定 \(a,b\),求 \(a\) 到 \(b\) 至少要经过多少条边,以及有多少个特殊区间对应的点,使得这个点可能在 \(a\) 到 \(b\) 的最短路径上。

\(n,q \le 2\times 10^5\)。


显然,每个区间能够到达的也是一个区间 \([L_i,R_i]\),并且也满足 \(L_i,R_i\) 递增。

第一问倍增即可。第二问相当于要求有多少个点 \(d(a,x) + d(x,b) = d(a,b)\),我们可以求出 \(f_{i,k}\) 表示从 \(i\) 往右 \(k\) 步能到达的最远点,\(g_{i,k}\) 表示从 \(i\) 往左 \(k\) 步能到达的最远点,我们枚举 \(d(a,x)\),设 \(c(l,r)\) 表示在 \([l,r]\) 之间有多少个关键点,则答案即为 \(c(a,a) + c(b,b) + \sum \limits_{i = 1}^{d(a,b) - 1} c(g_{b,d(a,b) - i}, f_{a,i})\),显然这些区间不交。拆成前缀和用倍增维护即可,时间复杂度 \(\mathcal{O}(n \log n)\)。

code
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int n, k; char s[N], t[N]; 
int a[N], le[N], ri[N], f[N][18], g[N][18], p[N][18], q[N][18];
int qry(int x, int y) {
    if (x == y) {
        return 0;
    }
    int d = 0;
    for (int i = 17; i >= 0; i--) {
        if (f[x][i] >= y || f[x][i] == -1)
            continue;
        x = f[x][i], d += 1 << i;
    }
    return d + 1;
}
int main() {
    // ios :: sync_with_stdio(false);
    // cin.tie(nullptr);
    cin >> n >> k;
    scanf("%s %s", s + 1, t + 1);
    int c = 0, u = 1;
    for (int i = 1; i <= 2 * n; i++) {
        if (s[i] == 'L') {
            c++;
        } else {
            ri[u] = c, u++;
        }
    }
    c = n + 1, u = n;
    for (int i = 2 * n; i >= 1; i--) {
        if (s[i] == 'R') {
            c--;
        } else {
            le[u] = c, u--;
        }
    }
    for (int i = 1; i <= n; i++) {
        a[i] = a[i - 1] + t[i] - '0';
    }
    memset(f, -1, sizeof(f));
    memset(g, -1, sizeof(g));
    for (int i = 1; i < n; i++) {
        f[i][0] = ri[i], p[i][0] = a[ri[i]];
    }
    for (int i = n; i > 1; i--) {
        g[i][0] = le[i], q[i][0] = a[le[i] - 1];
    }
    for (int j = 1; j <= 17; j++) {
        for (int i = 1; i < n; i++) {
            if (f[i][j - 1] == -1)
                continue;
            f[i][j] = f[f[i][j - 1]][j - 1];
            p[i][j] = p[i][j - 1] + p[f[i][j - 1]][j - 1];
        }
    }
    for (int j = 1; j <= 17; j++) {
        for (int i = n; i > 1; i--) {
            if (g[i][j - 1] == -1)
                continue;
            g[i][j] = g[g[i][j - 1]][j - 1];
            q[i][j] = q[i][j - 1] + q[g[i][j - 1]][j - 1];
        }
    }
    while (k--) {
        int x, y;
        cin >> x >> y;
        int c = qry(x, y), d = 0;
        d += t[x] - '0';
        d += t[y] - '0';
        c--;
        for (int i = 17; i >= 0; i--) {
            if (c & (1 << i)) {
                d += p[x][i], x = f[x][i];
            }
        }
        for (int i = 17; i >= 0; i--) {
            if (c & (1 << i)) {
                d -= q[y][i], y = g[y][i];
            }
        }
        c++;
        cout << c << " " << d << "\n";
    }
    return 0;
}

B. [USACO23JAN] Mana Collection P

有 \(N\) 个法力池,第 \(i\) 个法力池每秒可积累 \(m_i\) 法力。有 \(M\) 条有向边 \((a_i,b_i,t_i)\) 连接,你可以在 \(t_i\) 秒内从 \(a_i\) 移动到 \(b_i\) 。每当你出现在一个池子里,你就可以收集储存在那个地方的所有法力,把它清空。在 \(0\) 的时候,所有的法力池都是空的,你可以选择任何一个池子来开始收集。

\(q\) 次询问,每个次给定两个整数 \(s\) 和 \(e\),求如果你在第 \(s\) 秒结束时必须在法力池 \(e\) 处,在 \(s\) 秒内能收集的最大法力值。

\(N \leq 18\),\(q \leq 2 \times 10^5\),时限 \(\text{5.0s}\)。


先用 Floyd 预处理出 \(d(x,y)\) 表示 \(x \to y\) 的最短距离,这步时间复杂度 \(\mathcal{O}(n^3)\)。

容易发现,对于一条路径,收集到的法力仅与路径上所有点的最晚遍历时刻有关,也就是说,我们仅需知道这些点的最晚遍历时刻即可算出收集到的法力大小。形式化地,假设路径经过了 \(c_1,c_2,\cdots,c_k = e\),且 \(c_i\) 最晚遍历的时间是 \(d_i(0 \leq d_1 < d_2 < \cdots < d_k = s)\),那么最终收集到的法力大小为 \(\sum \limits_{i=1}^k f_{c_i} \cdot d_i\)。

一个显然的贪心是,如果确定了一条路径,那么我们的策略一定是,先等一段时间,直到不走就来不及走到 \(e\) 了再开始走,即取 \(d_i = s - \sum \limits_{j = i}^{k-1} d(c_i,c_{i+1})\) 是最优的。那么法力大小即为 \(\sum \limits_{i = 1}^k f_{c_i} \cdot \left( s - \sum \limits_{j = i}^{k-1} d(c_i,c_{i+1}) \right) = s \cdot \sum \limits_{i=1}^k f_{c_i} - \sum \limits_{i=1}^{k-1} d(c_i,c_{i+1}) \sum \limits_{j=1}^{i} f_{c_j}\)。

将 \(\sum \limits_{i = 1}^k f_{c_i}\) 看做斜率,\(- \sum \limits_{i=1}^{k-1} d(c_i,c_{i+1}) \sum \limits_{j=1}^{i} f_{c_j}\) 看做截距,那么它对应了一条直线。进一步地,对于一个确定的点集,它所对应的直线的斜率是一个定值,故我们保留截距最大的那条即可,这可以通过状压 DP 在 \(\mathcal{O}(2^nn^2)\) 的时间复杂度内预处理。

对于每个询问,我们考虑满足 \(c_k = e\) 的点集对应的直线,并求出当前横坐标 \(s\) 对应的最大纵坐标即可,这可以通过单调栈维护凸包来预处理,询问时二分 \(s\) 对应的是哪一段即可。

现在唯一的问题是如何保证 \(d_1 \geq 0\)。但我们发现如果 \(d_1 < 0\) 一定不优,所以可以直接不管了。时间复杂度 \(\mathcal{O}(n^3 + 2^n n^2 + q \log n)\)。

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 18;
const LL inf = 1e18;
int n, m, q, a[N];
LL d[N][N], v[1 << N], f[1 << N][N];
struct dat {
    LL x, y;
};
vector <dat> e[N], t[N];
vector <double> b[N];
double calc(dat a, dat b) {
    if (a.x == b.x) {
        if (a.y < b.y) {
            return inf;
        } else {
            return -inf;
        }
    }
    return 1.0 * (b.y - a.y) / (a.x - b.x);
}
int main() {
    ios :: sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    for (int i = 1; i < (1 << n); i++) {
        for (int j = 1; j <= n; j++) {
            f[i][j] = inf;
        }
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if (i == j) {
                continue;
            }
            d[i][j] = inf;
        }
    }
    for (int i = 1, x, y, z; i <= m; i++) {
        cin >> x >> y >> z;
        d[x][y] = z;
    }
    for (int k = 1; k <= n; k++) {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
            }
        }
    }
    for (int s = 1; s < (1 << n); s++) {
        for (int i = 1; i <= n; i++) {
            if ((s & (1 << i - 1)) == 0) {
                continue;
            }
            v[s] += a[i];
            int t = s ^ (1 << i - 1);
            if (t == 0) {
                f[s][i] = 0;
                break;
            }
            for (int j = 1; j <= n; j++) {
                if ((t & (1 << j - 1)) == 0 || d[j][i] == inf) {
                    continue;
                }
                f[s][i] = min(f[s][i], f[t][j] + v[t] * d[j][i]);
            }
        }   
    }
    for (int i = 1; i <= n; i++) {
        for (int s = 1; s < (1 << n); s++) {
            if ((s & (1 << i - 1)) == 0) {
                continue;
            }
            e[i].push_back((dat){v[s], -f[s][i]});
        }
        sort(e[i].begin(), e[i].end(), [&](dat &a, dat &b) {
            if (a.x == b.x) {
                return a.y > b.y;
            } else {
                return a.x < b.x;
            }
        });
        t[i].push_back((dat){0, 0}), b[i].push_back(-inf);
        for (auto x : e[i]) {
            while (t[i].size() > 1 && calc(x, t[i].back()) < b[i].back()) {
                t[i].pop_back();
                b[i].pop_back();
            }
            double r = calc(x, t[i].back());
            t[i].push_back(x);
            b[i].push_back(r);
        }
    }
    cin >> q;
    while (q--) {
        int c, d;
        cin >> c >> d;
        int i = upper_bound(b[d].begin(), b[d].end(), c) - b[d].begin() - 1;
        dat z = t[d][i];
        LL ans = c * z.x + z.y;
        cout << ans << "\n";
    }
    return 0;
}

C. [USACO23JAN] Subtree Activation P

有一棵根为 \(1\) 的树,每个顶点最初都是关闭的。在一次操作中,你可以将一个顶点的状态切换。输出操作序列的最小长度,满足对于每一个顶点的子树,都有一个时刻,开启状态顶点的集合恰好是该子树中的顶点,且在所有操作之后,每个顶点都是关闭的。

\(n \leq 2 \times 10^5\)。


第一眼:哎这不是 DSU on Tree 吗(x)

设 \(S_u\) 为 \(u\) 子树的节点组成的集合。如果最后要求所有顶点都开启就可以直接 DSU on Tree 了,但是现在要所有顶点都关闭,那么不妨先考虑一下我们会怎么关。不难发现,假设现在开启的集合是 \(S_u\),那么我们一定会每次选择一个儿子 \(v\),关掉 \(S_u/S_v\),这样就能得到 \(S_v\),重复这样的操作直到所有点关闭。开启的时候也同理。

也就是说,我们的操作大概长成这样:每次从一个叶子 \(u\) 开始点亮,依次覆盖 \(S_u,S_{f_u},\cdots,S_r\),然后从 \(S_r\) 开始关,依次覆盖 \(S_{v' \in \text{son}_r},\cdots,S_{v}\),其中 \(v\) 是另一个叶子。进一步地,每次可以选择两个叶子 \((u,v)\),然后选择一个 \(\text{LCA(u,v)}\) 到根的路径上的节点 \(r\),覆盖 \(u \to r\) 和 \(r \to v\) 上的所有集合。这样一次操作的代价是 \(2 \times s_r\),其中 \(s_r\) 为 \(r\) 的子树大小。

现在问题就变成了:选取若干这样的路径,覆盖树上所有节点,并使得代价最小。可以发现,对于每个 \(r\),我们至多选择一条这样的路径,否则可以拆成两条路径且答案更优。于是可以 DP,设 \(f_{u,0}\) 表示覆盖 \(u\) 子树所需的最小代价,\(f_{u,1}\) 表示剩下一条从 \(u\) 到其子树内某个叶结点的链,\(u\) 子树内其余结点均被覆盖所需的最小代价。转移比较简单,这里就不具体写了,时间复杂度 \(\mathcal{O}(n)\)。

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 5;
const LL inf = 1e18;
int n, sz[N]; LL f[N][2];
vector <int> e[N];
LL max(LL x, LL y) {
    return x > y ? x : y;
}
void dfs(int u, int ff) {
    sz[u] = 1;
    LL sum = 0, mx = 0, mi = inf, _mi = inf;
    for (auto v : e[u]) {
        if (v == ff) {
            continue;
        }
        dfs(v, u);
        sz[u] += sz[v], mx = max(mx, sz[v]), sum += f[v][0];
        LL d = f[v][1] - f[v][0];
        if (d <= mi) {
            _mi = mi, mi = d;
        } else {
            _mi = min(_mi, d);
        }
    }
    if (sz[u] == 1) {
        f[u][0] = 2;
        return;
    }
    f[u][0] = 2 * sz[u] + sum, f[u][1] = sum + mi;
    if (_mi < inf) {
        f[u][0] += mi + _mi;
    }
    f[u][0] = min(f[u][0], sum + 2 * (sz[u] - mx));
}
int main() {
    ios :: sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n;
    for (int i = 2, f; i <= n; i++) {
        cin >> f;
        e[i].push_back(f);
        e[f].push_back(i);
    }
    dfs(1, 0);
    cout << f[1][0] << "\n";
    return 0;
}

标签:limits,int,sum,back,--,USACO23JAN,杂题,LL
From: https://www.cnblogs.com/came11ia/p/17165978.html

相关文章

  • 多项式杂题
    多项式特训。开始大生产运动。不得不说黑题通过数一下就上来了。由于大多数比较工业所以一律不放代码。歌被咕了,打算报复性整点活。整活其实不一定需要整活用的曲。目前......
  • 「杂题乱写」Codeforces 上 DP 乱写
    作为衡中OIer,我们要紧随SoyTony步伐,建设新时代SoyTony特色博客.原博客:SoyTony.目录CF607BZumaCF178FRepresentativeSamplingCF1774ETwoChessPiecesCF1783D......
  • 【杂题乱写】CodeForces上dp乱写1
    难度是\(1900\sim2600\)。1132FCleartheString*2000区间\(\text{dp}\),设\(f_{l,r}\)为删去区间\([l,r]\)的最小代价。一个子问题的突破点是讨论\(l\)是怎......
  • CF、AT 杂题题解
    CF1455Fsolution1前\(i\)次操作只会影响到\([1,i+1]\),并且在第\(i\)次操作前,原本在位置\(i\)的数只可能在\(i\)或\(i-1\)。于是就可以考虑设\(f_{i,0/1}\)......
  • 题解 LGP9018【[USACO23JAN] Moo Route G】
    problem现在有一条数轴,\(t\)表示当前时刻。在\(t=0\)时Bessie恰好处在\(x=0\)的位置。接下来,每秒钟Bessie会向左或者向右移动一个单位距离,我们保证Bessie是在......
  • 1-2 月杂题选做
    Preface想记录一些有意思的题,结果发现有点难度的题全是ds。/gg一些过于板的题或者板子题就不写了。TextCF718CSashaandArray斐波那契\(\to\)矩阵乘法,所以线段......
  • 2月杂题
    叠甲:本博文中出现所谓难度评价大部分为作者自己根据自己的水平以及其评分所认定的难度,含有较大主观性,仅供参考,切勿当真。作者菜菜,可能会收录很傻逼/一眼的题目,只是一个类......
  • 2月杂题
    1.P3920[WC2014]紫荆花之恋离线怎么做?考虑把点分树建出来。在线怎么做?考虑定期重构,暴力查散点,跳点分树查整点。2.CF1063FStringJourney\(O(n\sqrt{n})\):观察到答......
  • 杂题选做:数论(一)
    早期shitpost重修。P1835素数密度求区间\([L,R]\)内的素数个数。\(1\leL\leR<2^{31},R-L\le10^6.\)如果\(x\)是合数,那么\([2,\sqrtx]\)范围内一定存在......
  • SAM杂题
    现在是SAM什么都不会了的状态。所以打算搞字符串。差不多就是从头开始学SAM,但是全是题。我觉得得一半多都是代码。P2408不同子串个数回忆一下SAM中每个节点的子串......