HAOI2016 简要题解

「HAOI2016」食物链

题意

现在给你 n 个物种和 m 条能量流动关系,求其中的食物链条数。

1n100000,0m200000

题解

拓扑 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 ("2060.in", "r", stdin); freopen ("2060.out", "w", stdout); #endif } const int N = 1e5 + 1e3; int n, m, f[N]; vector<int> G[N]; int indeg[N]; int main () { File(); n = read(); m = read(); For (i, 1, m) { int u = read(), v = read(); G[u].push_back(v); ++ indeg[v]; } queue<int> Q; For (i, 1, n) if (!indeg[i] && G[i].size()) Q.push(i), f[i] = 1; int ans = 0; while (!Q.empty()) { int u = Q.front(); Q.pop(); for (int v : G[u]) { f[v] += f[u]; if (!(-- indeg[v])) Q.push(v); } if (!bool(G[u].size())) ans += f[u]; } printf ("%d\n", ans); return 0; }

「HAOI2016」放棋子

题意

给你一个 N×N 的矩阵,每行有一个障碍,数据保证任意两个障碍不在同一行,任意两个障碍不在同一列,要求你在这个矩阵上放 N 枚棋子(障碍的位置不能放棋子),要求你放 N 个棋子也满足每行只有一枚棋子,每列只有一枚棋子的限制,求有多少种方案。

N200

题解

题意其实就是给你一个障碍排列 {Ai} 求有多少个排列 {Pi} 满足对于 i 都有 AiPi

不难发现 Ai 的顺序是不影响答案的,那么就是错排数了。

利用递推公式 f[n]=(n1)(f[n1]+f[n2])python 的高精度就可以求解了。(偷懒啦)

代码

n = int(input()) f = [0] * (n + 3) f[2] = 1 for i in range(3, n + 1): f[i] = (i - 1) * (f[i - 1] + f[i - 2]) print(f[n])

「HAOI2016」地图

题意

出题人语文老师 die 了。

有一个 n 个点 m 条边的仙人掌,其中每个点有一种颜色种类 ai

q 次询问,每次给出三个参数 ty,x,y ,表示如果当前 1x 所有简单路径都封死的情况下,x 能到达点的颜色编号 aiy 且出现次数 mod2=ty 的颜色有多少种。。

n100000,m150000,Q100000,ai106

题解

前面那个仙人掌,然后封路。其实就是对应求出圆方树后 x 的子树。(至于这个圆方树不一定要对于点双建新点,用原来的点就行了)

问题就转化成为多次询问区间中颜色在一段区间内的颜色 y 出现次数为奇/偶的点。

如果做过 Gty的二逼妹子序列 那么就绝对会做这题啦。

考虑莫队,那么插入和删除都要 O(nm) 次,询问区间权值种类只有 O(m) 次。

如果用线段树维护,瓶颈在于前面插入删除,就变成 O(nmlogn) 应该跑不过。

利用平衡结合的思路,减少前者的复杂度,增加后者的复杂度。不难想到分块可以做到 O(1)O(n) 或者 O(n)O(1) 插入+询问。

那么就可以做到 O(nm+mn) 啦。

代码

#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 #define pb push_back 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 ("2062.in", "r", stdin); freopen ("2062.out", "w", stdout); #endif } const int N = 1.5e5 + 1e3; int n, m; vector<int> G[N], E[N]; int dfn[N], lowlink[N], stk[N], top; void Tarjan(int u, int fa = 0) { static int clk = 0; dfn[u] = lowlink[u] = ++ clk; stk[++ top] = u; for (int v : G[u]) if (!dfn[v]) { Tarjan(v, u); chkmin(lowlink[u], lowlink[v]); if (lowlink[v] >= dfn[u]) { int cur; do E[u].pb(cur = stk[top --]); while (cur != v); } } else if (v != fa) chkmin(lowlink[u], dfn[v]); } int efn[N], num[N]; void Dfs_Init(int u, int fa = 0) { static int clk = 0; num[dfn[u] = ++ clk] = u; for (int v : E[u]) if (v != fa) Dfs_Init(v, u); efn[u] = clk; } int a[N], Hash[N]; int blksz, blkid[N], Beg[N], End[N]; struct Query { int id, opt, l, r, lim; } Q[N]; struct Cmp { inline bool operator () (const Query &lhs, const Query &rhs) { if (blkid[lhs.l] != blkid[rhs.l]) return blkid[lhs.l] < blkid[rhs.l]; if (lhs.r != rhs.r) return lhs.r < rhs.r; return lhs.id < rhs.id; } }; int sum[N][2], times[N]; inline void Insert(int col) { if (times[col]) -- sum[blkid[col]][times[col] & 1]; ++ sum[blkid[col]][(++ times[col]) & 1]; } inline void Delete(int col) { -- sum[blkid[col]][times[col] & 1]; if ((-- times[col])) ++ sum[blkid[col]][times[col] & 1]; } int ans[N]; int main () { File(); n = read(); m = read(); For (i, 1, n) Hash[i] = a[i] = read(); sort(Hash + 1, Hash + n + 1); int cnt = unique(Hash + 1, Hash + n + 1) - Hash - 1; For (i, 1, n) a[i] = lower_bound(Hash + 1, Hash + cnt + 1, a[i]) - Hash; For (i, 1, m) { int u = read(), v = read(); G[u].pb(v); G[v].pb(u); } Tarjan(1); Dfs_Init(1); int q = read(); blksz = sqrt(max(n, q) + .5); For (i, 1, max(n, q)) { blkid[i] = i / blksz + 1; if (blkid[i] != blkid[i - 1]) End[blkid[i - 1]] = i - 1, Beg[blkid[i]] = i; } End[blkid[max(n, q)]] = max(n, q); For (i, 1, q) { int opt = read(), x = read(), y = read(); y = upper_bound(Hash + 1, Hash + cnt + 1, y) - Hash - 1; Q[i] = (Query) {i, opt, dfn[x], efn[x], y}; } sort(Q + 1, Q + q + 1, Cmp()); int l = 1, r = 0; For (i, 1, q) { while (r < Q[i].r) Insert(a[num[++ r]]); while (l > Q[i].l) Insert(a[num[-- l]]); while (r > Q[i].r) Delete(a[num[r --]]); while (l < Q[i].l) Delete(a[num[l ++]]); int lim = Q[i].lim, res = 0; For (j, 1, blkid[lim] - 1) res += sum[j][Q[i].opt]; For (j, Beg[blkid[lim]], lim) if (times[j]) res += (times[j] & 1) == Q[i].opt; ans[Q[i].id] = res; } For (i, 1, q) printf ("%d\n", ans[i]); return 0; }

「HAOI2016」字符合并

题意

有一个长度为 n01 串,你可以每次将相邻的 k 个字符合并,得到一个新的字符并获得一定分数。得到的新字符和分数由这 k 个字符确定。你需要求出你能获得的最大分数。

1n300, 0ci1, wi1, k8

题解

一开始想到 dp 了,又不会转移。。。最近为啥这么多这种情况啊 TAT 看来细节还是不会写。

看到数据范围不难想到一个 区间+状压 dp ,令 fl,r,S[l,r] 最后合并成 S 这个状态的最优答案。

注意到如果 |S|>k 一定不优,我们一定会在当前把它合并掉。

那么转移的时候如果 |S|=k 那么我们就合并就好啦。

其他的话,转移不需要枚举左右都选了很多个的情况,强制左边选 |S|1 个右边选 1 个就好啦,讨论的情况会少很多QAQ

这样的话,复杂度是 O(n32k) 的,可能被卡常。

我们再进行一点小小的优化,你会发现每次合并的时候,很多合并对应的方案是相同的。我们在枚举断点那里每次跳 k1 然后合并就好啦 qwq

复杂度此时就变成 O(n32kk) 啦,跑的还挺快的。

总结

最优化转移还是那句话,宜多不宜少。你把很多个更优秀的状态记到劣一点的状态上是不会影响答案的,此时转移会变得容易许多。

然后想一个看起来不对的 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; using ll = long long; 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 ("2063.in", "r", stdin); freopen ("2063.out", "w", stdout); #endif } const int N = 310; const ll inf = 0x3f3f3f3f3f3f3f3f; ll f[N][N][1 << 8], g[2]; char str[N]; int a[N], c[N], w[N]; int main () { File(); int n = read(), k = read(); scanf ("%s", str + 1); For (i, 1, n) a[i] = str[i] ^ 48; Rep (i, 1 << k) c[i] = read(), w[i] = read(); For (i, 1, n) For (j, i, n) Rep (S, 1 << k) f[i][j][S] = -inf; Fordown (i, n, 1) For (j, i, n) { if (i == j) { f[i][j][a[i]] = 0; continue; } int len = (j - i) % (k - 1); if (!len) len = k - 1; for (int mid = j; mid > i; mid -= k - 1) Rep (S, 1 << len) { chkmax(f[i][j][S << 1], f[i][mid - 1][S] + f[mid][j][0]); chkmax(f[i][j][S << 1 | 1], f[i][mid - 1][S] + f[mid][j][1]); } if (len == k - 1) { g[0] = g[1] = -inf; Rep (S, 1 << k) chkmax(g[c[S]], f[i][j][S] + w[S]); Rep (id, 2) f[i][j][id] = g[id]; } } ll ans = -inf; Rep (S, 1 << k) chkmax(ans, f[1][n][S]); printf ("%lld\n", ans); return 0; }

「HAOI2016」找相同字符

可以参考我原来写的 后缀数组小结 ,里面的最后一道例题就是啦。

这个也可以广义 SAM 解决,或者一个串在另外一个 SAM 跑匹配就行了。


__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/10368622.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(618)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
历史上的今天:
2018-02-13 BZOJ 2683: 简单题(CDQ分治 + 树状数组)
点击右上角即可分享
微信分享提示