T1.语言
签到题。可以直接\(O(n)\)预处理出来前缀和,但我用了线段树,所以多了一个\(log\)的复杂度。
题意转化:找到一个位置为动词,上一个位置为名词,句子末尾是名词,其他地方是名词或形容词,bool数组随便弄一下就出来了。
代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <cstdio>
#include <iostream>
#include <cstring>
#define rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define _rep(i, a, b) for (int i = (a); i < (b); ++i)
using namespace std; const int Z = 1e5 + 10;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f |= c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
int n, w[30];
char s[Z];
inline bool adj(int x) { return w[s[x] - 'a'] & 1; }
inline bool non(int x) { return w[s[x] - 'a'] & 2; }
inline bool ver(int x) { return w[s[x] - 'a'] & 4; }
#define lk (rt << 1)
#define rk (rt << 1 | 1)
#define mid (L + R >> 1)
bool cov[Z << 2];
inline void pushup(int rt) { cov[rt] = cov[lk] & cov[rk]; }
void build(int rt, int L, int R)
{
if (L == R) { cov[rt] = non(L) | adj(L); return; }//名词或者形容词
build(lk, L, mid), build(rk, mid + 1, R);
pushup(rt);
}
bool query(int rt, int L, int R, int l, int r)
{
if (l > r) return 1;
if (l <= L && r >= R) return cov[rt];
int res = 1;
if (l <= mid) res &= query(lk, L, mid, l, r);
if (r > mid) res &= query(rk, mid + 1, R, l, r);
return res;
}
bool judge()
{
if (!non(n)) return false;//the last one is not a noun
_rep(i, 2, n)
if (ver(i) && non(i - 1))//noun + verb
if (query(1, 1, n, 1, i - 1) && query(1, 1, n, i + 1, n)) return true;//the before and after is all noun or adjective
return false;
}
sandom main()
{
fre(language, language);
int T = read();
while (T--)
{
rep(i, 0, 25) w[i] = read();
scanf("%s", s + 1); n = strlen(s + 1);
build(1, 1, n);
if (judge()) puts("Yes");
else puts("No");
}
return 0;
}
T2.色球
双向链表大模拟。赛时没有考虑到\(2\)操作均摊复杂度是\(O(n)\)的(因为一个点只可能被插入一次、删除一次)。链表注意边界判断。
代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <cstdio>
#include <iostream>
#define rep(i, a, b) for (int i = (a); i <= (b); ++i)
using namespace std;
namespace IO
{
int wrt[20], Tp = 0;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f |= c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
inline void write(int x) { if (x < 0) putchar('-'), x = -x; do { wrt[++Tp] = x % 10, x /= 10; } while (x); while (Tp) putchar(wrt[Tp--] | 48); putchar('\n'); }
}
using namespace IO; const int Z = 2e5 + 10;
int n, m, k, ans;
int tot, head[Z], tail[Z];
struct lst { int num, col, to[2]; } e[Z];
inline void un(int x, int y)
{
if (!e[x].to[0]) e[x].to[0] = y;
else e[x].to[1] = y;
}
inline int del(int x)
{
int nxt = e[x].to[0] ? e[x].to[0] : e[x].to[1];
if (e[nxt].to[0] == x) e[nxt].to[0] = 0;
else e[nxt].to[1] = 0;
return nxt;
}
inline void Push(int x, int y, int z)
{
e[++tot] = lst{x, y, head[z], 0};
if (head[z]) un(head[z], tot);
else tail[z] = tot;
head[z] = tot;
}
inline void Pop(int x, int z)
{
while (head[z] && x > 0)
{
x -= e[head[z]].num;
ans = e[head[z]].col;
head[z] = del(head[z]);
}
if (!head[z]) tail[z] = 0;
if (x < 0) Push(-x, ans, z);
}
inline void Put(int u, int v)
{
un(head[u], head[v]), un(head[v], head[u]);
if (!tail[v]) tail[v] = head[u];
if (tail[u]) head[v] = tail[u];
head[u] = tail[u] = 0;
}
char op[5];
sandom main()
{
fre(color, color);
n = read(), m = read();
while (m--)
{
scanf("%s", op); int x = read(), y = read();
switch (op[2])
{
case 's': Push(x, y, read()); break;
case 'p': Pop(x, y); write(ans); break;
case 't': Put(x, y); break;
}
}
return 0;
}
T3.斐波
首先先把\(g_n=fib^2_n\)的递推式整出来,有\(g_n=2g_{n-1}+2g_{n-2}-g_{n-3}\)。
推导:
自然可以写成矩阵加速计算
\[\begin{aligned} \left( \begin{array}{ccc} 2 & 2 & -1 \\ 1 & 0 & 0 \\ 0 & 1 & 0 \\ \end{array} \right) \left( \begin{array}{ccc} g_{n-1} \\ g_{n-2} \\ g_{n-3} \\ \end{array} \right) =\left( \begin{array}{ccc} g_{n} \\ g_{n-1} \\ g_{n-2} \\ \end{array} \right) \end{aligned} \]我们定义此处的系数矩阵为\(A\),单位矩阵为\(I\),初始矩阵为\(U\)(从\(g_0\)开始)。
\(f(S)=\sum\limits_{T\subseteq S} g(\sum T)\),在此基础上考虑加入一个数,那么分成原来的贡献以及新数与之前所有集合拼成的贡献,有\(f(S\bigcup a_i)=f(S)+\sum\limits_{T\subseteq S} g(\sum T+a_i)\)
由于矩阵乘法的性质,\(g(n+x)=A^xg(n)\),所以有\(f(S\bigcup a_i)=f(S)+\sum\limits_{T\subseteq S} A^{a_i}g(\sum T)=f(S)+A^{a_i}f(S)=(I+A^{a_i})*f(S)\)
以此类推,对于任意集合\(S\),\(f(S)=\prod (I+A^{a_i})*U\),那么题目也就是要求\(\sum\limits_{i=l}^{r}\sum\limits_{j=i}^{r}\prod\limits_{k=i}^{j}(I+A^{a_i})\ *U\),\(U\)可以提出来最后再乘。
考虑用线段树维护前面的东西, 一段区间的贡献显然有左右两边各自的贡献,再考虑中间部分的合并,因为我们只关心最后的加和,而不关心具体矩阵和过程怎么样,而中间部分的合并就是左区间的所有后缀积矩阵与右区间的所有前缀积矩阵自由搭配,因为矩阵满足乘法分配律,所以不妨先把两侧所有的矩阵加起来,最后统一乘起来自由搭配。
代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <cstdio>
#include <iostream>
#include <cstring>
#define rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define int long long
using namespace std; typedef long long ll;
namespace IO
{
const int bif = 1 << 18; char buf[bif], *p1, *p2; int wrt[20], Tp = 0;
inline char getc() { if (p1 == p2) { p2 = (p1 = buf) + fread(buf, 1, bif, stdin); if (p1 == p2) return EOF; } return *p1++; }
inline int read() { int x = 0, f = 0; char c = getc(); while (!isdigit(c)) f |= c == '-', c = getc(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getc(); return f ? -x : x; }
inline void write(int x) { if (x < 0) putchar('-'), x = -x; do { wrt[++Tp] = x % 10, x /= 10; } while (x); while (Tp) putchar(wrt[Tp--] | 48); putchar('\n'); }
}
using namespace IO; const int Z = 1e5 + 10; const int mod = 998244353;
int n, m, k, a[Z];
struct Matrix
{
int a[3][3];
Matrix () { memset(a, 0, sizeof(a)); }
friend Matrix operator +(Matrix x, Matrix y)
{
Matrix z;
rep(i, 0, k) rep(j, 0, k) z.a[i][j] = (x.a[i][j] + y.a[i][j]) % mod;
return z;
}
friend Matrix operator *(Matrix x, Matrix y)
{
Matrix z;
rep(i, 0, k) rep(j, 0, k) rep(t, 0, k) (z.a[i][j] += x.a[i][t] * y.a[t][j]) %= mod;
return z;
}
}; Matrix base, unit, init, b[Z];
struct tree
{
Matrix sum, lsum, rsum, tot;
#define lk (rt << 1)
#define rk (rt << 1 | 1)
#define mid (L + R >> 1)
}; tree tr[Z << 2], ans;
inline void change(int rt, Matrix v)
{
tr[rt].sum = tr[rt].lsum = tr[rt].rsum = tr[rt].tot = v;
}
inline tree pushup(tree lc, tree rc)//矩阵乘法满足乘法分配律(利用加法和直接分配)
{
tree rt;
rt.sum = lc.sum * rc.sum;
rt.lsum = lc.lsum + lc.sum * rc.lsum;
rt.rsum = rc.rsum + rc.sum * lc.rsum;
rt.tot = lc.tot + rc.tot + lc.rsum * rc.lsum;
return rt;
}
void build(int rt, int L, int R)
{
if (L == R) { change(rt, b[a[L]]); return; }
build(lk, L, mid), build(rk, mid + 1, R);
tr[rt] = pushup(tr[lk], tr[rk]);
}
void update(int rt, int L, int R, int pos, int v)
{
if (L == R) { change(rt, b[v]); return; }
if (pos <= mid) update(lk, L, mid, pos, v);
else update(rk, mid + 1, R, pos, v);
tr[rt] = pushup(tr[lk], tr[rk]);
}
tree query(int rt, int L, int R, int l, int r)
{
if (l <= L && r >= R) return tr[rt];
if (l > mid) return query(rk, mid + 1, R, l, r);
else if (r <= mid) return query(lk, L, mid, l, r);
else return pushup(query(lk, L, mid, l, r), query(rk, mid + 1, R, l, r));
}
sandom main()
{
fre(fib, fib);
base.a[0][0] = base.a[0][1] = 2, base.a[0][2] = -1, base.a[1][0] = base.a[2][1] = 1;
unit.a[0][0] = unit.a[1][1] = unit.a[2][2] = 1;
init.a[0][0] = 0, init.a[1][0] = 1, init.a[2][0] = 1;
n = read(), m = read(); k = 2;
rep(i, 1, n) a[i] = read();
b[0] = unit; rep(i, 1, 1e5) b[i] = b[i - 1] * base; rep(i, 1, 1e5) b[i] = b[i] + unit;
build(1, 1, n);
while (m--)
{
int op = read(), l = read(), r = read();
if (op == 1) update(1, 1, n, l, r);
else ans = query(1, 1, n, l, r), write(((ans.tot * init).a[0][0] + mod) % mod);
}
return 0;
}
T4.偶数
赛时想到了\(40\)分的性质,但真没想到满分也是性质结论啊。
左右共存时,\(kmp\)求最长的\(border\)是显然的。直接把中间除去\(border\)的部分接在后面即可,但是这样极其没有规律,发现左半部分的变化是在后面添加了一个除去后\(border\)的前缀,我们把它称为一个周期(定义——字符串除去后缀\(border\)的前缀)。具体化地:设\(u=vv\),\(w\)是\(v\)的最短周期(\(v-最长border\)),则变化后为\(vwvw\)。
不妨只考虑左半部分,正确性:我们称一次字符串操作为一次扩展。对于一个查询区间\([l, r]\)如果它既包含左侧,也包含右侧,那不妨再将字符串扩展一次,这时\(l、r\)都到了新字符串的左侧,也就是可以通过策略使得查询区间始终在左部分。
考虑快速求解:
首先一个结论:若\(w\)是\(v\)的最短周期,那么\(v\)是\(vw\)的最短周期。因为\(w\)是\(v\)的一个前缀,那么\(w\)一定是\(vw\)的\(border\),可以证明它是最长的\(border\)(反证会产生矛盾),那么\(v\)就是最短周期了。
以此类推,字符串变化形如:\(v->vw->vwv->vwvvw\)。
观察不难发现,这个东西类似于斐波那契数列,即\(s_i=s_{i-1}s_{i-2}\),因为斐波那契成倍增长,所以只需要\(log\)次。又因为任意一个自然数都可以被斐波那契数列中的一些数拼出来,对于字符串前面的串一定是后面的串的前缀。所以类似于倍增,从大到小从前往后拼接字符串。
代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <cstdio>
#include <iostream>
#include <cstring>
#define rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define dwn(i, a, b) for (int i = (a); i >= (b); --i)
#define int long long
using namespace std; typedef long long ll;
namespace IO
{
int wrt[20], Tp = 0;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f |= c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
inline void write(int x) { if (x < 0) putchar('-'), x = -x; do { wrt[++Tp] = x % 10, x /= 10; } while (x); while (Tp) putchar(wrt[Tp--] | 48); putchar('\n'); }
}
using namespace IO; const int Z = 1e5 + 10; const int mod = 998244353;
inline int qpow(int a, int b, int c) { int res = 1; while (b) { if (b & 1) res = res * a % c; a = a * a % c; b >>= 1; } return res; }
int n, m, k, q, ans;
char s[Z];
int nxt[Z];
void KMP()
{
nxt[1] = 0;
for (int i = 2, j = 0; i <= m; ++i)
{
while (j && s[i] != s[j + 1]) j = nxt[j];
if (s[i] == s[j + 1]) j++;
nxt[i] = j;
}
}
int ba[Z], has[Z];
int d[Z], f[Z];
inline int getnum(int x)//fibnocci能拼出所有自然数
{
ans = 0;
dwn(i, k, 0) if (x >= d[i])
{
x -= d[i];
ans = (ans * ba[i] + f[i]) % mod;
}
ans = (ans * qpow(10, x, mod) + has[x]) % mod;
return ans;
}
sandom main()
{
fre(even, even);
int T = read();
while (T--)
{
scanf("%s", s + 1); m = strlen(s + 1) / 2;//后一半不用管
n = read(), q = read();
rep(i, 1, m) has[i] = (has[i - 1] * 10 + s[i] - '0') % mod;
KMP();//最长border--最短周期
d[1] = m, d[0] = m - nxt[m];
f[1] = has[d[1]], f[0] = has[d[0]];
ba[1] = qpow(10, d[1], mod), ba[0] = qpow(10, d[0], mod);
rep(i, 2, 90)
{
d[i] = d[i - 1] + d[i - 2];//递推fibnocci长度
ba[i] = ba[i - 1] * ba[i - 2] % mod;//预处理10的幂次
f[i] = (f[i - 1] * ba[i - 2] + f[i - 2]) % mod;
if (d[i] >= n) { k = i; break; }
}
while (q--)
{
int l = read(), r = read();
write(((getnum(r) - getnum(l - 1) * qpow(10, r - l + 1, mod)) % mod + mod) % mod);
}
}
return 0;
}