3.17 总结
前言:
废掉了
T1.线性代数
考场时想到了线性基,但是思路仅从基底入手,没想到如何维护这个基的最大值小于 \(n\)。
题解:
容易想到线性代数,相当于就是求任意元素小于 \(n\) 的一维线性空间个数。
由于一个线性空间可能对应多个基,我们不妨以这个线性空间的最大值作为标识。
容易知道,每个基底在二进制表示下的最高位不同,所以按照二进制标识下最高位进行枚举。
\(dp[i][j][1/0]\) 表示考虑二进制表示下前 \(i\) 位,基大小为 \(j\),此时线性空间最大值是/否等于 \(n\) 在二进制表示下的前 \(i\) 位。
转移考虑是否要添加一个二进制标识下最高位为 \(i\) 的基底,或者前 \(j - 1\) 个第 \(i\) 位可以随便选,然后根据前 \(j - 1\) 个的结果选择第 \(j\) 个的第 \(i\) 为零或者 \(1\)(你不必知道前 \(j - 1\) 个异或出来的最大值第 \(i\) 位的数值,你只需要知道你的第 \(j\) 个数的第 \(i\) 位不自由即可,考试时我就卡在这里)。
参考代码
// ubsan: undefined
// accoders
#include <set>
#include <map>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <cstdio>
#include <vector>
#include <random>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define fi first
#define se second
#define db double
#define LL long long
//#define int long long
#define PII pair <int, int>
#define ULL unsigned long long
#define MP(x,y) make_pair (x, y)
#define rep(i,j,k) for (int i = (j); i <= (k); i++)
#define per(i,j,k) for (int i = (j); i >= (k); i--)
template <typename T>
void read (T &x) {
x = 0; T f = 1;
char ch = getchar ();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar ();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + ch - '0';
ch = getchar ();
}
x *= f;
}
template <typename T, typename... Args>
void read (T &x, Args&... Arg) {
read (x), read (Arg...);
}
const int MaxPrint = 1000;
int Poi_For_Print, Tmp_For_Print[MaxPrint + 5];
template <typename T>
void write (T x) {
if (x == 0) {
putchar ('0');
return;
}
bool flag = (x < 0 ? 1 : 0);
x = (x < 0 ? -x : x);
while (x) Tmp_For_Print[++Poi_For_Print] = x % 10, x /= 10;
if (flag) putchar ('-');
while (Poi_For_Print) putchar (Tmp_For_Print[Poi_For_Print--] + '0');
}
template <typename T, typename... Args>
void write (T x, Args... Arg) {
write (x); putchar (' '); write (Arg...);
}
template <typename T, typename... Args>
void print (T x, char ch) {
write (x); putchar (ch);
}
template <typename T> T Max (T x, T y) { return x > y ? x : y; }
template <typename T> T Min (T x, T y) { return x < y ? x : y; }
template <typename T> T Abs (T x) { return x > 0 ? x : -x; }
const int Maxk = 30;
const LL Mod = 1e9 + 7;
int n;
int tp, st[Maxk + 5];
LL f[Maxk + 5][Maxk + 5];
LL dfs (int step, int cnt, int Limit) {
if (step == -1) {
return 1;
}
if (!Limit && f[step][cnt]) return f[step][cnt];
int up = (Limit ? st[step] : 1);
//在填第 cnt 个主元的第 step 位
LL res = 0;
rep (i, 0, up) {
if (cnt == 0 && i == 1) continue;
//根据前 cnt - 1 个主元异或结果的第 step 位决定第 cnt 个主元的第 step 位填什么
res += dfs (step - 1, cnt, Limit & (i == up)) * (cnt == 0 ? 1 : (1 << (cnt - 1))) % Mod;
res %= Mod;
}
//准备新加入主元,最高位为 step
if (up)
res += dfs (step - 1, cnt + 1, Limit), res %= Mod;
if (!Limit) f[step][cnt] = res;
return res;
}
signed main () {
// freopen ("C:\\Users\\HP\\Desktop\\lihan\\1.in", "r", stdin);
// freopen ("C:\\Users\\HP\\Desktop\\lihan\\1.out", "w", stdout);
freopen ("algebra.in", "r", stdin);
freopen ("algebra.out", "w", stdout);
read (n);
int tmp = n;
while (tmp)
st[tp++] = tmp & 1, tmp >>= 1;
tp--;
write (dfs (tp, 0, 1));
return 0;
}
T2.
真秒,给我一辈子都想不出来
口胡
考虑一个 \(T_2\) 上一条路径在 \(T_1\) 上是否满足要求,条件是 \(T_2\) 上的点组成点集 \(S\),要求 \(T_2\) 上 \(S\) 的导出子图点数 - 边数 = 1。
首先,对于一条路径 \(s, t\) 的点数是确定的,直接在 \(T_2\) 上求 \(Lca\) 就行了。
考虑 \(T_1\) 上一条边会提供什么贡献。
会将路径 \(s \rightarrow t (s \in S_1, t \in S_2 \cup S_3\) 的边数加 \(1\),发现 \(S_1, S_2, S_3\) 成树状,此时将 \(s \rightarrow t\) 抽象成点 \((dfn_s, dfn_t)\) (\(dfn\) 表示 \(T_2\) 上的 \(dfs\) 序),然后就可以进行二维区间修改了。
T3.
应该是三道题中最简单的一道,当时没怎么看,被 T1 搞麻了。
转换一下题意,那么就是找到所有 \(S_{[l_i, r_i]} = S_{[l, r]}\) 的区间,然后在 \(1\) ~ \(n\) 中选最少的点,对于所有 \(i\),满足 \([l_i, r_i - 1]\) 里至少有一个选择的点。
容易想到贪心,从左往右扫描,选择未含选定点的区间的末尾。
预处理询问,要求询问 \(l, r\) 满足 \(r - l + 1 \leq \sqrt {n}\),做法就是枚举长度,然后扫一遍,用 \(map\) 将所有可能询问的字符串找到并求解。
\(r - l + 1 \leq \sqrt {n}\) 直接用预处理的回答
\(r - l + 1 \geq \sqrt {n}\) 容易发现需要加入的 \(*\) 不超过 \(\sqrt {n}\) 个,做法就是令 \(p = 1\),找到第一个左端点大于等于 \(p\) 的,然后令 \(p = r_i\)(相当于选择 \(r_i\))。寻找第一个左端点大于等于 \(p\) 的方法是利用 \(SA\) 数组,因为在按照后缀排序后,对于 \(S_{[i, n]}\),最长公共前缀一定是在 \(SA\) 中与 \(i\) 更近的点,在同侧的情况下,离的越远,公共前缀越短,那么可以按序将 \(SA\) 插入主席树中,找到 \(l, r\) 这个询问在 \(SA\) 上最长公共前缀 \(\geq R - L + 1\) 的区间 \([L, R]\),然后在版本 \([L, R]\) 中二分线段树找到第一个大于等于 \(p\) 的点。
参考代码
// ubsan: undefined
// accoders
#pragma GCC optimize(3)
#pragma GCC target("avx")
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
#pragma GCC optimize(2)
#include <set>
#include <map>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <cstdio>
#include <vector>
#include <random>
#include <cstdlib>
#include <cassert>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define fi first
#define se second
#define db double
#define LL long long
//#define int long long
#define PII pair <int, int>
#define ULL unsigned long long
#define MP(x,y) make_pair (x, y)
#define rep(i,j,k) for (int i = (j); i <= (k); i++)
#define per(i,j,k) for (int i = (j); i >= (k); i--)
template <typename T>
void read (T &x) {
x = 0; T f = 1;
char ch = getchar ();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar ();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + ch - '0';
ch = getchar ();
}
x *= f;
}
template <typename T, typename... Args>
void read (T &x, Args&... Arg) {
read (x), read (Arg...);
}
const int MaxPrint = 1000;
int Poi_For_Print, Tmp_For_Print[MaxPrint + 5];
template <typename T>
void write (T x) {
if (x == 0) {
putchar ('0');
return;
}
bool flag = (x < 0 ? 1 : 0);
x = (x < 0 ? -x : x);
while (x) Tmp_For_Print[++Poi_For_Print] = x % 10, x /= 10;
if (flag) putchar ('-');
while (Poi_For_Print) putchar (Tmp_For_Print[Poi_For_Print--] + '0');
}
template <typename T, typename... Args>
void write (T x, Args... Arg) {
write (x); putchar (' '); write (Arg...);
}
template <typename T, typename... Args>
void print (T x, char ch) {
write (x); putchar (ch);
}
template <typename T> T Max (T x, T y) { return x > y ? x : y; }
template <typename T> T Min (T x, T y) { return x < y ? x : y; }
template <typename T> T Abs (T x) { return x > 0 ? x : -x; }
const int Maxn = 1e5;
const int Maxk = 60;
const ULL P = 13331;
const int Inf = 0x3f3f3f3f;
int n, q, Size;
ULL pre[Maxn + 5];
char s[Maxn + 5];
map <ULL, int> ans;
LL w[Maxn + 5];
ULL Query (int l, int r) {
return pre[r] - pre[l - 1] * w[r - l + 1];
}
int sa[Maxn + 5], id_bak[Maxn + 5];
//sa[i] len + 1 ~ 2 * len 排名为 i 的串的编号
//id_bak[i] 编号为 i 的串该被放进哪个桶
int sort_id[Maxn + 5], poi_bak[Maxn + 5];
//sort_id[i] 按照 len + 1 ~ 2 * len 排序的排名为 i 的串的编号
//poi_bak[i] 当前队列的最后一个元素的排名
int tem[Maxn * 2 + 5];
void Init_Bak (int n, int m) {//基数排序,装桶
rep (i, 1, m)
poi_bak[i] = 0;
rep (i, 1, n)
poi_bak[id_bak[i]]++;
rep (i, 1, m)
poi_bak[i] += poi_bak[i - 1];
}
void Sort_L (int n) {//按照 len + 1 ~ 2 * len 排序
per (i, n, 1)
sa[poi_bak[id_bak[sort_id[i]]]--] = sort_id[i];
}
void Get_Sa (int n, int m, char *str) {
rep (i, 1, n) {
id_bak[i] = str[i] - 'a' + 1;
sort_id[i] = i;
}
Init_Bak (n, m);
Sort_L (n);
for (int len = 1; len < n; len <<= 1) {
Init_Bak (n, m);
int Now_Rank = 0;
rep (i, n - len + 1, n) {
sort_id[++Now_Rank] = i;
}
rep (i, 1, n)
if (sa[i] > len)
sort_id[++Now_Rank] = sa[i] - len;
Sort_L (n);
rep (i, 1, n) tem[i] = id_bak[i];
Now_Rank = 1; id_bak[sa[1]] = 1;
rep (i, 2, n) {
if (tem[sa[i - 1]] != tem[sa[i]] || tem[sa[i - 1] + len] != tem[sa[i] + len])
Now_Rank++;
id_bak[sa[i]] = Now_Rank;
}
m = Now_Rank;
}
}
bool check (int x, int y, int len) {
if (x + len - 1 > n) return 0;
if (y + len - 1 > n) return 0;
return Query (x, x + len - 1) == Query (y, y + len - 1);
}
#define ls(p) (Tr[p].ch[0])
#define rs(p) (Tr[p].ch[1])
#define tot(p) (Tr[p].tot)
#define mid ((l + r) >> 1)
struct Node {
int tot;
int ch[2];
};
struct Persistent_Tree {
int cnt;
int timestamp, rt[Maxn + 5];
Node Tr[Maxn * Maxk + 5];
void Push_Up (int p) {
tot (p) = tot (ls (p)) + tot (rs (p));
}
int New_Node (int Root) {
int p = ++cnt;
Tr[p] = Tr[Root];
return p;
}
int Update (int Root, int Index, int l, int r) {
int p = New_Node (Root);
if (l == r) {
tot (p) ++;
return p;
}
if (Index <= mid) ls (p) = Update (ls (p), Index, l, mid);
else rs (p) = Update (rs (p), Index, mid + 1, r);
Push_Up (p);
return p;
}
void Update (int Index) {
timestamp++;
rt[timestamp] = Update (rt[timestamp - 1], Index, 1, n);
}
int Query (int p, int q, int ql, int qr, int l, int r) {
if (ql > qr) return -1;
if (tot (q) - tot (p) == 0) return -1;
if (l == r) return l;
int res = -1;
if (ql <= mid) res = Query (ls (p), ls (q), ql, qr, l, mid);
if (res != -1) return res;
if (qr > mid) res = Query (rs (p), rs (q), ql, qr, mid + 1, r);
return res;
}
}Tree;
signed main () {
// freopen ("C:\\Users\\HP\\Desktop\\lihan\\1.in", "r", stdin);
// freopen ("C:\\Users\\HP\\Desktop\\lihan\\1.out", "w", stdout);
freopen ("string.in", "r", stdin);
freopen ("string.out", "w", stdout);
w[0] = 1; rep (i, 1, Maxn) w[i] = w[i - 1] * P;
read (n, q);
Size = 10;
scanf ("%s", s + 1);
rep (i, 1, n) {
pre[i] = pre[i - 1] * P + s[i] - 'a' + 1;
}
rep (len, 1, Size) {
map <ULL, PII> bak;
rep (l, 1, n - len + 1) {
int r = l + len - 1;
ULL tmp = Query (l, r);
if (bak.find (tmp) != bak.end ()) {
if (bak[tmp].se <= l) {
ans[tmp] = ans[tmp] + 1;
bak[tmp] = MP (l, r);
}
}
else {
ans[tmp] = 1;
bak[tmp] = MP (l, r);
}
}
}
Get_Sa (n, 26, s);
rep (i, 1, n)
sort_id[sa[i]] = i;
rep (i, 1, n) {
Tree.Update (sa[i]);
}
rep (i, 1, q) {
int L, R; read (L, R);
if (L == R) {
print (-1, '\n');
continue;
}
if (R - L + 1 <= Size) {
print (ans[Query (L, R)], '\n');
continue;
}
else {
int res = 0;
int ql, qr;
int l = 0, r = sort_id[L];
while (l + 1 < r) {
if (check (sa[mid], L, R - L + 1)) r = mid;
else l = mid;
}
ql = r;
l = sort_id[L], r = n + 1;
while (l + 1 < r) {
if (check (sa[mid], L, R - L + 1)) l = mid;
else r = mid;
}
qr = l;
int p = 1;
while (Tree.Query (Tree.rt[ql - 1], Tree.rt[qr], p, n, 1, n) != -1) {
res++;
p = Tree.Query (Tree.rt[ql - 1], Tree.rt[qr], p, n, 1, n) + (R - L);
}
print (res, '\n');
}
}
return 0;
}