SCOI2016 Day1 简要题解

「SCOI2016」背单词

题意

这出题人语文水平真低。。 搬了 skylee 大佬的题意。

给你 n 个字符串,不同的排列有不同的代价,代价按照如下方式计算(字符串 s 的位置为 x ):

  1. 排在 s 后面的字符串有 s 的后缀,则代价为 n2
  2. 排在 s 前面的字符串有 s 的后缀,且没有排在 s 后面的 s 的后缀,则代价为 xyy 为最后一个与 s 不相等的后缀的位置);
  3. s 没有后缀,则代价为 x

现在需要最小化排列的代价。

n105,1s|s|5.1×105

题解

首先 n2 的代价明显比其他两种操作的总和还多,显然不够优秀。

我们需要尽量避免 1 情况的出现,这显然是可以达到的。

因为所有后缀的出现会存在一个偏序结构,构成了一个 DAG ,我们找个 DFS 序遍历就行了。

其实这个 DAG 就是它反串构成的 Trie

我们相当于要在 Trie 上找一个 DFS 序把每个 关键点 编号,最小化 每个点与它祖先第一个 关键点 的编号差值的和。

关键点: 就是插入串最后的结束节点。

由于是个 DFS 序, 对于一个点的每个儿子需要遍历的话,肯定要遍历这个儿子的子树。

所以对于当前这层贪心的话,不难发现这是一个类似于接水问题,我们肯定是让需要时间较短 (Size 较小)的儿子排前面。

然后发现直接实现只会有 40 分。

为什么错了呢?

假设对于 ba¯,bc¯ 这两个串,但是不存 b¯ 串。但由于 Trie 上会存在 b¯ 这个节点,所以我们会在空串的时候考虑的时候会假设有 b¯ 这个串。

这显然是不行的。其中一种解决办法就是,前面我们需要把 n 个串的前缀关系,重新建个新树。然后再按前面那样做就可以了。

所以复杂度就是 O(nlogn+|S|) 的。

代码

#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 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 DEBUG(...) fprintf(stderr, __VA_ARGS__) #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 ("2012.in", "r", stdin); freopen ("2012.out", "w", stdout); #endif } typedef long long ll; ll ans = 0; namespace Trie { const int Maxn = 510010, Alpha = 26; int from[Maxn]; int Size = 0, sz[Maxn], trans[Maxn][Alpha], key[Maxn]; inline void Insert(char *str) { int u = 0; For (i, 1, strlen(str + 1)) { int id = str[i] - 'a'; if (!trans[u][id]) trans[u][id] = ++ Size; ++ sz[u = trans[u][id]]; from[u] = id; } key[u] = true; } vector<int> G[Maxn]; void Get_Tree(int u = 0, int Last = 0) { if (key[u]) G[Last].pb(u), Last = u; For (i, 0, Alpha - 1) if (trans[u][i]) Get_Tree(trans[u][i], Last); } int id = 0; void Dfs(int u = 0, int Last = 0) { if (u) ans += (++ id) - Last, Last = id; sort(G[u].begin(), G[u].end(), [&](const int &lhs, const int &rhs) { return sz[lhs] < sz[rhs]; } ); for (int v : G[u]) Dfs(v, Last); } }; char str[510010]; int main () { File(); using namespace Trie; int n = read(); For (i, 1, n) { scanf ("%s", str + 1); reverse(str + 1, str + strlen(str + 1) + 1); Insert(str); } Get_Tree(); Dfs(); printf ("%lld\n", ans); return 0; }

「SCOI2016」幸运数字

题意

有一颗 n 个点的树,每个点有个权值 Gi

q 次询问,每次询问树上一条路径上所有数 Gi 中选择一些数异或的最大值。

n20000,q200000,Gi260

题解

从一个集合中选择一些数异或出最大值显然是线性基的模板。

那么我们只需要得到树上一条路径的线性基。

但线性基是不可减的,我们可以考虑用倍增把线性基合并。

复杂度就是 O((n+q)logn×602) 可以卡过去。

其实有更好的解决方式,由于 q 比较大,我们尽量使它不要带 log

可以点分治,把每个询问挂在被第一次被分开的中心,然后走到一个询问的点后把它线性基保留起来。

最后复杂度是 O((nlogn+q)×602) 的。

总结

树上查链不可减但可以合并的信息,有三种方式。不支持修改可以用 倍增(可在线 or 点分治(离线) or 树剖(可在线)

支持修改就用 树剖 + 线段树

注意线性基插入一个线性基的复杂度是 O(L2ω) 的,合并的复杂度是 O(L3ω)

代码

最后那个做法还是比较好写的。

#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 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 DEBUG(...) fprintf(stderr, __VA_ARGS__) #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;} template<typename T> inline T read() { T 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 ("2013.in", "r", stdin); freopen ("2013.out", "w", stdout); #endif } typedef long long ll; template<int Maxn> struct Linear_Base { ll Base[Maxn + 1]; Linear_Base() { Set(Base, 0); } inline void Insert(ll val) { Fordown (i, Maxn, 0) if (val >> i & 1) { if (!Base[i]) { Base[i] = val; break ; } else val ^= Base[i]; } } inline ll Max() { ll res = 0; Fordown (i, Maxn, 0) chkmax(res, res ^ Base[i]); return res; } }; typedef Linear_Base<60> Info; Info Merge(Info x, Info y) { For (i, 0, 60) if (y.Base[i]) x.Insert(y.Base[i]); return x; } void Add(Info &x, Info y) { For (i, 0, 60) if (y.Base[i]) x.Insert(y.Base[i]); } const int N = 20100; vector<int> G[N]; int n, q, dep[N], anc[N][17], Log2[N]; Info data[N][17]; inline ll Calc(int x, int y) { Info res = Merge(data[x][0], data[y][0]); if (dep[x] < dep[y]) swap(x, y); int gap = dep[x] - dep[y]; Fordown (i, Log2[gap], 0) if (gap >> i & 1) Add(res, data[x][i]), x = anc[x][i]; Add(res, data[x][0]); if (x == y) return Merge(res, data[y][0]).Max(); Fordown (i, Log2[dep[x]], 0) if (anc[x][i] != anc[y][i]) { Add(res, data[x][i]); Add(res, data[y][i]); x = anc[x][i]; y = anc[y][i]; } Add(res, data[x][0]); Add(res, data[y][0]); return Merge(res, data[anc[x][0]][0]).Max(); } void Dfs_Init(int u, int fa = 0) { dep[u] = dep[anc[u][0] = fa] + 1; for (int v : G[u]) if (v != fa) Dfs_Init(v, u); } int main() { File(); n = read<int>(); q = read<int>(); For (i, 1, n) data[i][0].Insert(read<ll>()); For (i, 1, n - 1) { int u = read<int>(), v = read<int>(); G[u].pb(v); G[v].pb(u); } Dfs_Init(1); For (i, 2, n) Log2[i] = Log2[i >> 1] + 1; For (j, 1, Log2[n]) For (i, 1, n) { anc[i][j] = anc[anc[i][j - 1]][j - 1]; data[i][j] = Merge(data[i][j - 1], data[anc[i][j - 1]][j - 1]); } while (q --) printf ("%lld\n", Calc(read<int>(), read<int>())); return 0; }

「SCOI2016」萌萌哒

题意

一个长度为 n 的大数,用 S1S2S3Sn表示,其中 Si 表示数的第 i 位,S1 是数的最高位,告诉你一些限制条件,每个条件表示为四个数 (l1,r1,l2,r2),即两个长度相同的区间,表示子串 Sl1Sl1+1Sl1+2Sr1Sl2Sl2+1Sl2+2Sr2 完全相同。

比如 n=6 时,某限制条件 (l1=1,r1=3,l2=4,r2=6),那么 123123351351 均满足条件,但是 12012131141 不满足条件,前者数的长度不为 6,后者第二位与第五位不同。问满足以上所有条件的数有多少个。

1n105,1m105,1li1,ri1,li2,ri2n 并且保证 ri1li1=ri2li2

题解

不难发现其实每次就是限制对应的位置数相同。

然后最后就会限制成 tot 种可以填的不同的数,最后的答案就是 9×10tot1

我们把强制要求相等的数放入并查集中就行了。直接实现这个过程是 O(n2α(n)) 的复杂度。

考虑如何优化,区间对应连边容易想到线段树优化建边。但此处没有那么好用。

考虑把一个区间拆成 log 个长为 2i 的区间。

具体来说令 fa[i][j]i 向右长为 2j 的区间在并查集上的 root

初始化的时候 fa[i][j]rooti

然后一个询问我们就在对应区间上连边就行了。

但是最后我们区间的连通性并不能代表点的连通性,所以我们需要把连通性下放。

具体来说把 (i,j) 这个点和 (i,j1),(i+2j1,j1) 对应相连就行了。

最后看 (i,0) 有几个不同的联通块就可以完成了。

其实前面那个那个不需要拆成 O(logn) 个区间。由于是要保证联通,多连是没有关系的,我们可以类似 ST 表查询那样,把最大的两个可以代表区间相连。

复杂度就是 O((nlogn+m)α(n))

总结

这题还是比较巧妙的,类似于线段树优化建边的思想,我们可以拆成子区间去解决这个问题。

类似于这种题,只要构建出模型保证和原图一样的连通性即可。

代码

#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 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 DEBUG(...) fprintf(stderr, __VA_ARGS__) 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 ("2014.in", "r", stdin); freopen ("2014.out", "w", stdout); #endif } const int N = 2e5 + 1e3; int n, m, fa[N][22], Log2[N]; int find(int x, int y) { return fa[x][y] == x ? x : fa[x][y] = find(fa[x][y], y); } const int Mod = 1e9 + 7; 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; } int main () { File(); n = read(); m = read(); For (i, 2, n) Log2[i] = Log2[i >> 1] + 1; For (i, 1, n) For (j, 0, Log2[n]) fa[i][j] = i; For (i, 1, m) { int p1 = read(), r1 = read(), p2 = read(), r2 = read(); int len = Log2[r1 - p1 + 1]; fa[find(p1, len)][len] = find(p2, len); fa[find(r1 - (1 << len) + 1, len)][len] = find(r2 - (1 << len) + 1, len); } Fordown (j, Log2[n], 1) For (i, 1, n) { int u = find(i, j), v = find(i, j - 1); fa[v][j - 1] = find(u, j - 1); u = find(i, j), v = find(i + (1 << j - 1), j - 1); fa[v][j - 1] = find(u + (1 << j - 1), j - 1); } int tot = 0; For (i, 1, n) if(find(i, 0) == i) ++ tot; printf ("%lld\n", fpm(10, tot - 1) * 9ll % Mod); return 0; }

__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/10086623.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(483)  评论(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】
点击右上角即可分享
微信分享提示