「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) & S; 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; }

JLOI2015 DAY2 简要题解的更多相关文章

  1. JLOI2015 DAY1 简要题解

    「JLOI2015」有意义的字符串 题意 给你 \(b, d, n\) 求 \[ [(\frac{b + \sqrt d}2)^n] \mod 7528443412579576937 \] \(0 & ...

  2. SCOI2016 Day2 简要题解

    「SCOI2016」妖怪 题意 有 \(n\) 只妖怪,每只妖怪有攻击力 \(\text{atk}\) 和防御力 \(\text{dnf}\) ,在环境 \((a, b)\) 下,它可以把攻击力和防御 ...

  3. SCOI 2015 Day2 简要题解

    「SCOI2015」小凸玩密室 题意 小凸和小方相约玩密室逃脱,这个密室是一棵有 $ n $ 个节点的完全二叉树,每个节点有一个灯泡.点亮所有灯泡即可逃出密室.每个灯泡有个权值 $ A_i $,每条边 ...

  4. AHOI2013 Round2 Day2 简要题解

    第一题: 第一问可以用划分树或主席树在O(nlog2n)内做出来. 第二问可以用树状数组套主席树在O(nlog2n)内做出来. 我的代码太挫了,空间刚刚卡过...(在bzoj上) 第二题: 分治,将询 ...

  5. Noip 2014酱油记+简要题解

    好吧,day2T1把d默认为1也是醉了,现在只能期待数据弱然后怒卡一等线吧QAQ Day0 第一次下午出发啊真是不错,才2小时左右就到了233,在车上把sao和fate补掉就到了= = 然后到宾馆之后 ...

  6. Tsinghua 2018 DSA PA2简要题解

    反正没时间写,先把简要题解(嘴巴A题)都给他写了记录一下. upd:任务倒是完成了,我也自闭了. CST2018 2-1 Meteorites: 乘法版的石子合并,堆 + 高精度. 写起来有点烦貌似. ...

  7. Codeforces 863 简要题解

    文章目录 A题 B题 C题 D题 E题 F题 G题 传送门 简要题解?因为最后一题太毒不想写了所以其实是部分题解... A题 传送门 题意简述:给你一个数,问你能不能通过加前导000使其成为一个回文数 ...

  8. HNOI2018简要题解

    HNOI2018简要题解 D1T1 寻宝游戏 题意 某大学每年都会有一次 Mystery Hunt 的活动,玩家需要根据设置的线索解谜,找到宝藏的位置,前一年获胜的队伍可以获得这一年出题的机会. 作为 ...

  9. JXOI2018简要题解

    JXOI2018简要题解 T1 排序问题 题意 九条可怜是一个热爱思考的女孩子. 九条可怜最近正在研究各种排序的性质,她发现了一种很有趣的排序方法: Gobo sort ! Gobo sort 的算法 ...

随机推荐

  1. c++入门之内置数组和array比较

    array是C++11中新提出来的容器类型,与内置数组相比,array是一种更容易使用,更加安全的数组类型,可以用来替代内置数组.作为数组的升级版,继承了数组最基本的特性,也融入了很多容器操作,下面介 ...

  2. 【评分】Beta 答辩总结

    [评分]Beta 答辩总结 总结 按时交 - 有分 晚交 - 0分 迟交一周以上 - 倒扣本次作业分数 抄袭 - 倒扣本次作业分数 由于前期不够重视,到beta评分才发现有5组的代码提交仅由一人&qu ...

  3. Minimal string CodeForces – 797C

    题目链接 题目难度: 1700rating 题目类型:string+贪心+STL 题目思路: 由于题目要求的最终结果是字典序最小的那个字符串,那么我们从贪心的从’a’开始查找字符串里是否存在,如果存在 ...

  4. pandas数据清洗策略1

    Pandas常用的数据清洗5大策略如下: 1.删除 DataFrame 中的不必要 columns 2.改变 DataFrame 的 index 3.使用 .str() 方法来清洗 columns 4 ...

  5. 文本文档中各字母出现次数汇总(java)

    package 字母频率统计; import java.io.*; public class Inputfile { public static void main(String args[]) { ...

  6. 哈尔滨工程大学ACM预热赛

    https://ac.nowcoder.com/acm/contest/554#question A #include <bits/stdc++.h> using namespace st ...

  7. HowTos/Virtualization/VirtualBox - CentOS Wiki

    https://wiki.centos.org/HowTos/Virtualization/VirtualBox

  8. Velocity之初印象

    Velocity 模板引擎介绍 在现今的软件开发过程中,软件开发人员将更多的精力投入在了重复的相似劳动中.特别是在如今特别流行的 MVC 架构模式中,软件各个层次的功能更加独立,同时代码的相似度也更加 ...

  9. php单元测试

    https://blog.csdn.net/gaisidewangzhan1/article/details/80347008

  10. Java Serializable的使用和transient关键字使用小记(转载)

    一:Serializable 1.持久化的简单介绍: “持久化”意味着对象的“生存时间”并不取决于程序是否正在执行——它存在或“生存”于程序的每一次调用之间.通过序列化一个对象,将其写入磁盘,以后在程 ...