SCOI2016 Day1 簡要題解
目錄
「SCOI2016」背單詞
題意
這出題人語文水平真低。。搬了 skylee 大佬的題意。
給你 \(n\) 個字串,不同的排列有不同的代價,代價按照如下方式計算(字串 \(s\) 的位置為 \(x\) ):
- 排在 \(s\) 後面的字串有 \(s\) 的字尾,則代價為 \(n^2\)
- 排在 \(s\) 前面的字串有 \(s\) 的字尾,且沒有排在 \(s\) 後面的 \(s\) 的字尾,則代價為 \(x-y\)( \(y\) 為最後一個與 \(s\) 不相等的字尾的位置);
- \(s\) 沒有後綴,則代價為 \(x\) 。
\(n \le 10^5, 1 \le \sum_s |s| \le 5.1 \times 10^5\)
題解
首先 \(n^2\) 的代價明顯比其他兩種操作的總和還多,顯然不夠優秀。
我們需要儘量避免 \(1\) 情況的出現,這顯然是可以達到的。
因為所有後綴的出現會存在一個偏序結構,構成了一個 \(DAG\) ,我們找個 \(DFS\)
其實這個 \(DAG\) 就是它反串構成的 \(Trie\) 。
我們相當於要在 \(Trie\) 上找一個 \(DFS\) 序把每個 關鍵點 編號,最小化 每個點與它祖先第一個 關鍵點 的編號差值的和。
關鍵點: 就是插入串最後的結束節點。
由於是個 \(DFS\) 序, 對於一個點的每個兒子需要遍歷的話,肯定要遍歷這個兒子的子樹。
所以對於當前這層貪心的話,不難發現這是一個類似於接水問題,我們肯定是讓需要時間較短 (\(Size\) 較小)的兒子排前面。
然後發現直接實現只會有 \(40\) 分。
為什麼錯了呢?
假設對於 \(\overline{ba}, \overline{bc}\)
這顯然是不行的。其中一種解決辦法就是,前面我們需要把 \(n\) 個串的字首關係,重新建個新樹。然後再按前面那樣做就可以了。
所以複雜度就是 \(O(n \log n + |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\) 個點的樹,每個點有個權值 \(G_i\) 。
有 \(q\) 次詢問,每次詢問樹上一條路徑上所有數 \(G_i\) 中選擇一些數異或的最大值。
\(n \le 20000, q \le 200000, G_i \le 2^{60}\)
題解
從一個集合中選擇一些數異或出最大值顯然是線性基的模板。
那麼我們只需要得到樹上一條路徑的線性基。
但線性基是不可減的,我們可以考慮用倍增把線性基合併。
複雜度就是 \(O((n + q) \log n \times 60^2)\) 可以卡過去。
其實有更好的解決方式,由於 \(q\) 比較大,我們儘量使它不要帶 \(\log\) 。
可以點分治,把每個詢問掛在被第一次被分開的中心,然後走到一個詢問的點後把它線性基保留起來。
最後複雜度是 \(O((n \log n + q) \times 60^2)\) 的。
其實也可以樹剖實現,一開始預處理到鏈頂的線性基值,複雜度是 \(O((n + q \log n) \times 60^2)\) 的。
總結
樹上查鏈不可減但可以合併的資訊,有三種方式。不支援修改可以用 倍增(可線上 or 點分治(離線) or 樹剖(可線上) 。
支援修改就用 樹剖 + 線段樹 。
注意線性基插入一個線性基的複雜度是 \(\displaystyle O(\frac{L^2}{\omega})\) 的,合併的複雜度是 \(\displaystyle O(\frac{L^3}{\omega})\) 。
程式碼
這個做法還是比較好寫的。
#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 $ 的大數,用 $ S_1S_2S_3 \ldots S_n $表示,其中 $ S_i $ 表示數的第 $ i $ 位,$ S_1 $ 是數的最高位,告訴你一些限制條件,每個條件表示為四個數 $ (l_1, r_1, l_2, r_2) $,即兩個長度相同的區間,表示子串 $ S_{l_1}S_{l_1 + 1}S_{l_1 + 2} \ldots S_{r_1} $ 與 $ S_{l_2}S_{l_2 + 1}S_{l_2 + 2} \ldots S_{r_2} $ 完全相同。
比如 $ n = 6 $ 時,某限制條件 $ (l_1 = 1, r_1 = 3, l_2 = 4, r_2 = 6) $,那麼 $ 123123 \(、\) 351351 $ 均滿足條件,但是 $ 12012 \(、\) 131141 $ 不滿足條件,前者數的長度不為 $ 6 $,後者第二位與第五位不同。問滿足以上所有條件的數有多少個。
$ 1 \leq n \leq 10 ^ 5, 1 \leq m \leq 10 ^ 5, 1 \leq {l_i}_1, {r_i}_1, {l_i}_2, {r_i}_2 \leq n $ 並且保證 $ {r_i}_1 - {l_i}_1 = {r_i}_2 - {l_i}_2 $。
題解
不難發現其實每次就是限制對應的位置數相同。
然後最後就會限制成 \(tot\) 種可以填的不同的數,最後的答案就是 \(9 \times 10^{tot - 1}\) 。
我們把強制要求相等的數放入並查集中就行了。直接實現這個過程是 \(O(n^2 \alpha(n))\) 的複雜度。
考慮如何優化,區間對應連邊容易想到線段樹優化建邊。但此處沒有那麼好用。
考慮把一個區間拆成 \(\log\) 個長為 \(2^i\) 的區間。
具體來說令 \(fa[i][j]\) 為 \(i\) 向右長為 \(2^j\) 的區間在並查集上的 \(root\) 。
初始化的時候 \(fa[i][j]\) 的 \(root\) 為 \(i\) 。
然後一個詢問我們就在對應區間上連邊就行了。
但是最後我們區間的連通性並不能代表點的連通性,所以我們需要把連通性下放。
具體來說把 \((i, j)\) 這個點和 \((i, j - 1), (i + 2^{j - 1}, j - 1)\) 對應相連就行了。
最後看 \((i, 0)\) 有幾個不同的聯通塊就可以完成了。
其實前面那個那個不需要拆成 \(O(\log n)\) 個區間。由於是要保證聯通,多連是沒有關係的,我們可以類似 \(ST\) 表查詢那樣,把最大的兩個可以代表區間相連。
複雜度就是 \(O((n \log n + m) \alpha (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;
}