JLOI2015 DAY2 简要题解

「JLOI2015」骗我呢

题意

问有多少个 \(n \times m\) 的矩阵 \(\{x_{i, j}\}\) 满足

  • 对于 \(\forall i \in [1, n], j \in [1, m]\) 有 \(x_{i, j} \in[0, m]\) ;
  • 对于 \(\forall i \in [1, n], j \in [1, m)\) 有 \(x_{i, j} < x_{i, j + 1}\) ;
  • 对于 \(\forall i \in (1, n], j \in [1, m)\) 有 \(x_{i, j} < x_{i - 1, j + 1}\) 。

答案对于 \(10^9+7\) 取模。 \(1 \le m, n \le 10^6\)

题解

可以先写个暴力,观察下最后矩阵的性质。

不难发现对于每一行,会从小到大出现 \(m\) 个不同的数,那么只会有 \([0, m]\) 中的一个数不会在当行出现。

那么我们可以设一个很显然的 \(dp\) 了,令 \(f_{i, j}\) 为到第 \(i\) 行缺的是 \(j\) 的方案数。画画图,观察观察。每次可以转移的上一行的范围就是 \([j + 1, m]\) 。

然后利用前缀和优化就可以做到 \(O(n^2)\) ,优化后的 \(dp\) 本质上其实就是

有一个 长为 \(n + m + 1\) 宽为 \(n\) 的网格,从左下走到右上,每次只能向右或者向上走,不能经过 \(y = x + 1\) 和 \(y = x - (m + 2)\) 的方案数。

这个其实是个经典计数,就是卡特兰数多了上下界的限制。

我们记通过 \(y = x + 1\) 的事件为 \(A\) 通过 \(y = x - (m + 2)\) 的事件为 \(B\) ,我们其实就是求不出现 \(A\) 或 \(B\) 的

这个怎么做呢,通常可以考虑容斥。如果没有限制,走到 \((x, y)\) 的方案其实就是 \(\displaystyle {x + y \choose y}\) 。

那么我们考虑枚举它的一个交错子序列 \(ABABAB\dots\) 。

答案其实就是 \(All - A - B + AB + BA - BAB - ABA + \dots\) 。

那么现在问题就在与如何计算 \(ABABAB\dots\) 出现的方案数,我们考虑最后的终点 \((n + m + 1, n)\) 沿着 \(A\) 翻折 再沿着 \(B\) 翻折,这样不断重复下去。然后计算一开始起点到终点的方案数。

这样就可以在 \(\mathcal O(n)\) 的时间内解决啦。

总结

多记经典计数 \(dp\) ,关键时刻可以救命。

代码

#include <bits/stdc++.h>

#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl

using namespace std;

template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }

inline int read() {
    int x(0), sgn(1); char ch(getchar());
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * sgn;
}

void File() {
#ifdef zjp_shadow
    freopen ("2109.in", "r", stdin);
    freopen ("2109.out", "w", stdout);
#endif
}

const int N = 5e6 + 1e3, Mod = 1e9 + 7;

int fac[N], ifac[N];

inline int fpm(int x, int power) {
    int res = 1;
    for (; power; power >>= 1, x = 1ll * x * x % Mod)
        if (power & 1) res = 1ll * res * x % Mod;
    return res;
}

void Math_Init(int maxn) {
    fac[0] = ifac[0] = 1;
    For (i, 1, maxn) fac[i] = 1ll * fac[i - 1] * i % Mod;
    ifac[maxn] = fpm(fac[maxn], Mod - 2);
    Fordown (i, maxn - 1, 1) ifac[i] = ifac[i + 1] * (i + 1ll) % Mod;
}

inline int Comb(int n, int m) {
    if (n < 0 || m < 0 || n < m) return 0;
    return 1ll * fac[n] * ifac[m] % Mod * ifac[n - m] % Mod;
}

inline int Calc(int n, int m) {
    return (n < 0 || m < 0) ? 0 : Comb(n + m, m);
}

int n, m;

inline void Flip1(int &x, int &y) { swap(x, y); -- x; ++ y; }

inline void Flip2(int &x, int &y) { swap(x, y); x += m + 2; y -= m + 2; }

int main () {

    File();

    n = read(); m = read();

    Math_Init(n * 2 + m + 1);

    int ans = Calc(n + m + 1, n);
    {
        int x = n + m + 1, y = n;
        while (x >= 0 && y >= 0) {
            Flip1(x, y); (ans -= Calc(x, y)) %= Mod;
            Flip2(x, y); (ans += Calc(x, y)) %= Mod;
        }
    }

    {
        int x = n + m + 1, y = n;
        while (x >= 0 && y >= 0) {
            Flip2(x, y); (ans -= Calc(x, y)) %= Mod;
            Flip1(x, y); (ans += Calc(x, y)) %= Mod;
        }
    }

    ans = (ans + Mod) % Mod;
    printf ("%d\n", ans);

    return 0;

}

「JLOI2015」管道连接

题意

该部门有 \(n\) 个情报站。给出 \(m\) 对情报站 \(u_i, v_i\) 和费用 \(w_i\),表示情报站 \(u_i\) 和 \(v_i\) 之间可以花费 \(w_i\) 建立通道。

如果两个情报站联通那么意味着它们就建立了通道连接。有 \(p\) 个重要情报站,其中每个情报站有一个特定的频道。

现要花费最少的资源,使得任意相同频道的情报站之间都建立通道连接。

\(0 < c_i \leq p \leq 10, \ 0 < u_i, v_i, d_i \leq n \leq 1000, \ 0 \leq m \leq 3000, \ 0 \leq w_i \leq 20000\) 。

题解

如果学过斯坦纳树不难发现就是裸题了。。。 可是我没学过QAQ

那么就简单讲一下原理与做法。

有一张很大的图,其中有很少的几个关键点。然后要选权值和尽量小的边构成一颗生成树使得这些点都存在于这颗树上,这个就称作 斯坦纳树

如何做呢?考虑 \(dp\) 解决这个问题,令 \(f[i][S]\) 为以 \(i\) 为根节点的树子树中包括了 \(S\) 这个关键点集合的最小代价。

我们一共有两种转移:

  • 对于一个点 \(i\) 它所包含的一个集合 \(S\) ,有转移 \(f_{i, S} = \max_{T \subseteq S} \{f_{i, T} + f_{i, S- T}\}\) 。
  • 对于每个集合 \(S\) ,把 \(f_{i, S} < \infty\) 的点 \(i\) 放入队列中,跑关于 \(S\) 这个状态的 \(\text{Spfa}\) 。因为此处的 \(dp\) 转移其实就是松弛操作。

然后假设整张图有 \(n\) 个点 \(m\) 条边,有 \(p\) 个关键点。

复杂度是 \(\mathcal O(n \times 3^p + km \times 2^p )\) 。其中 \(k\) 是 \(\text{Spfa}\) 那个系数,可以被卡到 \(\mathcal O(n)\) 。。。



那么对于这道题,我们考虑直接对于每种频道的做一个斯坦纳树。然后令 \(g[S]\) 为 \(S\) 这个集合的最优答案,初值为 \(\min f_{i, S}\) 然后直接做一遍子集 \(dp\) 就好啦。

总结

多学冷门数据结构、算法哇。。。裸题就十分地好做QAQ

代码

#include <bits/stdc++.h>

#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl

using namespace std;

template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }

inline int read() {
    int x(0), sgn(1); char ch(getchar());
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * sgn;
}

void File() {
#ifdef zjp_shadow
    freopen ("2110.in", "r", stdin);
    freopen ("2110.out", "w", stdout);
#endif
}

const int N = 1025, M = 3010 * 2, inf = 0x3f3f3f3f;

int n, m, p;

int Head[N], Next[M], to[M], val[M], e = 0;

inline void add_edge(int u, int v, int w) {
    to[++ e] = v; Next[e] = Head[u]; val[e] = w; Head[u] = e;
}

int maxsta;

namespace Steiner_Tree {

    int f[N][N]; bitset<N> inq;

    queue<int> Q;

    void Spfa(int sta) {
        while (!Q.empty()) {
            int u = Q.front(); Q.pop(); inq[u] = false;
            for (int i = Head[u], v = to[i]; i; v = to[i = Next[i]]) {
                if (chkmin(f[v][sta], f[u][sta] + val[i]))
                    if(!inq[v]) Q.push(v), inq[v] = true;
            }
        }
    }

    void Build() {
        For (S, 1, maxsta) {
            For (i, 1, n) {
                for (int T = S - 1; T; T = (T - 1) & S)
                    chkmin(f[i][S], f[i][T] + f[i][S ^ T]);
                if (f[i][S] < inf) Q.push(i), inq[i] = true;
            }
            Spfa(S);
        }
    }

};

int c[N], d[N], g[N], con[N];

inline bool Check(int sta) {
    For (i, 1, p) {
        int cur = sta & con[i];
        if (cur && cur != con[i]) return false;
    }
    return true;
}

int main () {

    File();

    n = read(); m = read(); p = read();

    while (m --) {
        int u = read(), v = read(), w = read();
        add_edge(u, v, w); add_edge(v, u, w);
    }

    For (i, 1, p) c[i] = read(), d[i] = read();

    For (i, 1, p) For (j, 1, p)
        if (c[i] == c[j]) con[i] |= (1 << (j - 1));
    maxsta = (1 << p) - 1;

    using Steiner_Tree :: f;

    Set(f, 0x3f);
    For (i, 1, p)
        f[d[i]][1 << (i - 1)] = 0;
    Steiner_Tree :: Build();

    Set(g, 0x3f);
    For (S, 1, maxsta) if (Check(S)) {
        For (i, 1, n) chkmin(g[S], f[i][S]);
        for (int T = S - 1; T; T = (T - 1) & S)
            chkmin(g[S], g[T] + g[S ^ T]);
    }
    printf ("%d\n", g[maxsta]);

    return 0;

}

「JLOI2015」战争调度

题意

王国的公民关系刚好组成一个 \(n\) 层的完全二叉树。公民 \(i\) 的下属是 \(2i\) 和 \(2i +1\)。最下层的公民即叶子节点的公民是平民,平民没有下属,最上层的是国王,中间是各级贵族。

现在这个王国爆发了战争,国王需要决定每一个平民是去种地以供应粮食还是参加战争,每一个贵族(包括国王自己)是去管理后勤还是领兵打仗。一个平民会对他的所有直系上司有贡献度,若一个平民 \(i\) 参加战争,他的某个直系上司 \(j\) 领兵打仗,那么这个平民对上司的作战贡献度为 \(w_{ij}\)。若一个平民 \(i\) 种地,他的某个直系上司 \(j\) 管理后勤,那么这个平民对上司的后勤贡献度为 \(f_{ij}\),若 \(i\) 和 \(j\) 所参加的事务不同,则没有贡献度。为了战争需要保障后勤,国王还要求不多于 \(m\) 个平民参加战争。

国王想要使整个王国所有贵族得到的贡献度最大,请求出这个值。

\(2 \leq n \leq 10, \ m \leq 2^{n-1}, \ 0 \leq w_{ij}, f_{ij} \leq 2000\)

题解

看到完全二叉树,自然要想到关于深度的 \(dp\) 。

我想到了 \(\text{HNOI2018 D2 T3}\) 那个简单 \(dp\) ,你考虑预留祖先就好了。

具体来说,就是令 \(f_{i, j, S}\) 为到第 \(i\) 个点,选了 \(j\) 个去打仗, \(i\) 的祖先的状态为 \(S\) 的最大贡献度。

然后从前往后依次枚举每个点,考虑相邻两个点的 \(lca\) 深度就行了。

这样的话复杂度是 \(\mathcal O(2^{3(n - 1)})\) ,空间好像开不下,我们滚动第一维就行了。

这样实现了一下,直接就跑过去了??!!好像还挺快的,最慢的要 \(0.1s\) 。。

然后看了看网上题解,发现就是把这个 \(dp\) 在树上记忆化转移就好了,证一证时空复杂度就是 \(O(n 2^{2(n - 1)})\) 的。

QAQ 好像看的那个人的速度还没有我快。。。

总结

树上 \(dp\) 还是要在树上转移卡状态。。别下树 \(dp\) 。。。

代码

#include <bits/stdc++.h>

#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl

using namespace std;

template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }

inline int read() {
    int x(0), sgn(1); char ch(getchar());
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * sgn;
}

void File() {
#ifdef zjp_shadow
    freopen ("2111.in", "r", stdin);
    freopen ("2111.out", "w", stdout);
#endif
}

const int N = 1025, B = 513;

int n, m, w[2][N][12], Log2[N], val[2][N][B];

int f[B][B], g[B][B], v[B][B];

int main () {

    File();

    n = read(); m = read();

    int k = 1 << (n - 1);

    Fordown (id, 1, 0) For (i, k, (k << 1) - 1) {
        For (j, 0, n - 2) w[id][i][j] = read();
        Rep (sta, k) Rep (j, n - 1)
            if ((sta >> j & 1) ^ id) val[id][i][sta] += w[id][i][j];
    }

    Rep (id, 2) Rep (sta, k) f[id][sta] = val[id][k][sta];

    For (i, k + 1, (k << 1) - 1) {
        int x = i - 1, y = i, len = - 1;
        for (; x != y; x >>= 1, y >>= 1) ++ len;

        For (j, 0, min(m, i - k + 1)) Rep (sta, k)
            chkmax(v[j][sta >> len], f[j][sta]);

        Rep (id, 2) For (j, id, min(m, i - k + 1)) Rep (sta, k)
            chkmax(g[j][sta], v[j - id][sta >> len] + val[id][i][sta]);

        For (j, 0, min(m, i - k + 1)) Rep(sta, k)
            f[j][sta] = g[j][sta], g[j][sta] = v[j][sta] = 0;
    }

    int ans = 0;
    For (j, 0, m) Rep(sta, k) chkmax(ans, f[j][sta]);
    printf ("%d\n", ans);

    return 0;

}

原文地址:https://www.cnblogs.com/zjp-shadow/p/10360330.html

时间: 2024-10-10 05:41:06

JLOI2015 DAY2 简要题解的相关文章

JLOI2015 DAY1 简要题解

「JLOI2015」有意义的字符串 题意 给你 \(b, d, n\) 求 \[ [(\frac{b + \sqrt d}2)^n] \mod 7528443412579576937 \] \(0 < b^2 \le d < (b + 1)^2 \le 10^{18}, n \le 10^{18}\) 且 \(b \bmod 2 = 1, d \bmod 4 = 1\) 题解 我们把形式如果凑成 \[ f(n) = (\frac{b + \sqrt d}2)^n + (\frac{b - \s

AGC025简要题解

AGC025简要题解 B RGB Coloring 一道简单题,枚举即可. C Interval Game 考虑可以进行的操作只有两种,即左拉和右拉,连续进行两次相同的操作是没有用的. 左拉时肯定会选择右端点尽量小的,右拉选择左端点尽量大的,所以排序之后贪心即可. D Choosing Points 首先证明对于所有\(d\),假设让两个不能同时选的点之间连一条边,那么结果是一张二分图. \(d\)是奇数可以黑白染色,\(d\)是偶数的时候,显然连边的两点在同一个颜色内.那么我们可以只考虑这个颜

月考简要题解

模拟赛简要题解 一下题目均可在loj上找到 10178. 「一本通 5.5 例 4」旅行问题 简单题,将n扩大到2 * n,单调队列即可,注意正反向. #include<iostream> #include<cstring> #include<cmath> #include<cstdio> #include<algorithm> using namespace std; typedef long long ll; const int N=2000

JXOI2018简要题解

JXOI2018简要题解 T1 排序问题 题意 九条可怜是一个热爱思考的女孩子. 九条可怜最近正在研究各种排序的性质,她发现了一种很有趣的排序方法: Gobo sort ! Gobo sort 的算法描述大致如下: 假设我们要对一个大小为 \(n\) 的数列 \(a\) 排序. 等概率随机生成一个大小为 \(n\) 的排列 \(p\) . 构造一个大小为 \(n\) 的数列 \(b\) 满足 \(b_i=a_{p_i}\) ,检查 \(b\) 是否有序,如果 \(b\) 已经有序了就结束算法,并

BJOI2018简要题解

BJOI2018简要题解 D1T1 二进制 题意 pupil 发现对于一个十进制数,无论怎么将其的数字重新排列,均不影响其是不是 \(3\) 的倍数.他想研究对于二进制,是否也有类似的性质. 于是他生成了一个长为 \(n\) 的二进制串,希望你对于这个二进制串的一个子区间,能求出其有多少位置不同的连续子串,满足在重新排列后(可包含前导 \(0\))是一个 \(3\) 的倍数.两个位置不同的子区间指开始位置不同或结束位置不同. 由于他想尝试尽量多的情况,他有时会修改串中的一个位置,并且会进行多次询

杂题记录及简要题解(三)

以下是大概 5 月初开始做的一些题.以前的简要题解都是骗人的.这次真的是简要题解了(大雾 相对之前改良了一下题目名称的格式. 2017 计蒜之道 初赛 - 腾讯狼人杀 二分答案 \(x\) 后原问题变为检验是否存在选取方案 \((V, E)(|V| = k)\) 使得 \(\sum_\limits{e \in E} w_e - xk \cdot (2n- k)\).式子可以写成 \(\sum_\limits{e \in E} w_e + \frac{k(k - 1)}{2} \cdot 2x -

【简要题解】Hihocoder 重复旋律1-8简要题解

[简要题解]Hihocoder 重复旋律1-8简要题解 编号 名称标签 难度 1403 后缀数组一·重复旋律 Lv.4 1407 后缀数组二·重复旋律2 Lv.4 1415 后缀数组三·重复旋律3 Lv.4 1419 后缀数组四·重复旋律4 Lv.4 1445 后缀自动机二·重复旋律5 Lv.4 1449 后缀自动机三·重复旋律6 Lv.4 1457 后缀自动机四·重复旋律7 Lv.1 1465 后缀自动机五·重复旋律8 Lv.1 1466 后缀自动机六·重复旋律9 Lv.1 后缀数组 思路简单

《信奥一本通》提高版—简要题解

<信奥一本通>提高版-简要题解 贪心 活动安排: 按右端点排序,因为越早结束越好. 然后从1扫到n,每次比较当前位置线段的左端点是否大于上一个选的线段的右端点.如果大于,那么ans++,将上一个选的线段的右端点更新为当前线段的右端点:如果小于,那什么都不用做.因为选上一条一定比选当前这一条更优(结束时间更早). 种树 按右端点排序,对于每个区间的需求,从右端往左端扫,只要没种到树的就种,ans++. 因为要使前面的需求尽量与后面的需求重叠,从而使树的数量最少 喷水装置 观察+画图发现对于一个圆

【题解】CF616(Div 2)简要题解

[题解]CF616(Div 2)简要题解 A 分类讨论 若数码和是奇数 若最后一个数是奇数:找到从前往后第一个奇数数位删掉 若最后一个数是偶数:不断删除最后一个数直到这个剩下的数是奇数,由于之前删掉的数都是偶数所以对数码和\(\mod 2\)不会有影响.再做一遍第一个算法即可. 若数码和是偶数 若最后一个数是奇数:符合条件 若最后一个数是偶数:不断删除最后一个数直到奇数.由于之前删掉的数都是偶数所以对数码和\(\mod 2\)不会有影响,直接输出即可. 最后要判断一下前导零. B 可以发现若有合