明天有信息会考。
A.
严格弱于Numbers on a Circle。
先做个差分,发现每回就是选择一个数加 \(n\),最后使得每个数都相等,那么每个数的操作次数就是与最大值的差值除以 \(n\),注意判断无解。
B.Division into Two
感觉跟 \(CSP-S\) 的 \(C\) 差不多啊。
考虑到如果将集合 \(S\) 中的数从小到大写成一个序列,则可以将答案看作一段连续的元素进入 \(X\),之后的一段进入 \(Y\),再之后的一段进入 \(X\),以此类推。考虑对这个序列进行 DP。
那么此时一个自然的想法就是设 \(f_i\) 表示前 \(i\) 个元素中,最后一段元素进入 \(X\) 且最后一段恰好在 \(i\) 结尾的方案数。类似的,\(g_i\) 表示前 \(i\) 个元素中,最后一段元素进入 \(Y\) 且最后一段恰好在 \(i\) 结尾的方案数。但是在转移时会卡住:虽然我们可以枚举最后一段的开头,但是我们无法得知倒数第二段的开头,因此无法得知最后一段开头与倒数第三段结尾的差是否在题目要求范围内。
但是我们可以发现这个状态的定义中暗含一个条件:对于 \(f_i\) 要求 \(i+1\) 进入 \(Y\)。因此我们没有必要考虑倒数第三段和最后一段,我们考虑倒数第二段和下一段。下一段的开头已经得知是 \(i+1\),倒数第二段的结尾就是我们枚举的东西。因此这个是否合法是容易判定的。于是就可以转移了。
暴力转移的复杂度是 \(O(n^2)\),但是容易发现这个转移是区间求和的形式同时区间还有单调性,因此写个双指针就直接 \(O(n)\) 做完了。
C.bzoj2905. 背单词
看到子串第一时间想 \(\text{SAM}\),由于是 \(n\) 个串,所以建广义 \(SAM\),最后对于每个串都是求后缀链接树上的到根路径 \(\max\),给他变成子树 \(\text{chkmax}\) 单点查询就好。
这时候会发现这个后缀链接树的作用等同于 \(\text{Fail}\) 树,不过既然时间复杂度一样空间也正好够用就不用管了。
所以除了我都写的 AC 自动机是吧
#include <bits/stdc++.h>
#define fr first
#define se second
#define Un unsigned
#define LL long long
#define pb push_back
#define pii pair<int,int>
#define pLi pair<LL,int>
#define pLL pair<LL,LL>
#define __ __int128
#define ld long double
#define VE vector<LL>
#define db double
#define Ve vector<int>
using namespace std;
const int N = 20010,M = 3e5+5;
int tr[M][26],cnt,p[M],v[N],ed[N],f[N];
string s[N];
void ins(int i)
{
int p = 0;
for (char c : s[i])
{
int x = c-'a';
if (!tr[p][x]) tr[p][x] = ++cnt;
p = tr[p][x];
}
ed[i] = p;
}
struct SAM
{
int tot = 1,np = 1,len[M<<1],fa[M<<1],tr[M<<1][26];
void extend(int c)
{
int p = np;np = ++tot;len[np] = len[p]+1;
while(p && !tr[p][c]) tr[p][c] = np,p = fa[p];
if (!p) fa[np] = 1;
else
{
int q = tr[p][c];
if (len[q] == len[p]+1) fa[np] = q;
else
{
int nq = ++tot;len[nq] = len[p]+1;
memcpy(tr[nq],tr[q],sizeof(tr[q]));
fa[nq] = fa[q],fa[np] = fa[q] = nq;
while(p && tr[p][c] == q) tr[p][c] = nq,p = fa[p];
}
}
}
}sam;
void build()
{
queue < int > q;
q.push(0),p[0] = 1;
while(q.size())
{
int x = q.front();q.pop();
for (int i = 0;i < 26;i++)
if (tr[x][i]) sam.np = p[x],sam.extend(i),p[tr[x][i]] = sam.np,q.push(tr[x][i]);
}
}
struct edge{int to,pre;}e[M<<1];
int las[M<<1],ct,dfn[M<<1],siz[M<<1],d;
void add(int u,int v){e[++ct] = {v,las[u]},las[u] = ct;}
void D(int x)
{
siz[x] = 1,dfn[x] = ++d;
for (int i = las[x],y;i;i = e[i].pre) D(y = e[i].to),siz[x] += siz[y];
}
int mx[M<<3];
void M1(int l,int r,int L,int R,int k,int i)
{
if (l >= L && r <= R) return (void)(mx[i] = max(mx[i],k));
int mid = (l+r)>>1;
if (L <= mid) M1(l,mid,L,R,k,i<<1);
if (mid+1 <= R) M1(mid+1,r,L,R,k,i<<1|1);
}
int Q(int l,int r,int p,int ans,int i)
{
ans = max(ans,mx[i]);
if (l == r) return ans;
int mid = (l+r)>>1;
if (p <= mid) return Q(l,mid,p,ans,i<<1);
return Q(mid+1,r,p,ans,i<<1|1);
}
void B(int l,int r,int i)
{
mx[i] = 0;
if (l == r) return ;
int mid = (l+r)>>1;
B(l,mid,i<<1),B(mid+1,r,i<<1|1);
}
int main()
{
ios::sync_with_stdio(0);
int T;cin >> T;
while (T--)
{
int n,ans = 0;cin >> n;
for (int i = 1;i <= n;i++) cin >> s[i] >> v[i],ins(i);
build();
for (int i = 2;i <= sam.tot;i++) add(sam.fa[i],i);
D(1);
for (int i = 1;i <= n;i++)
{
int pos = 1;f[i] = 0;
for (int j = 0;j < s[i].size();j++)
pos = sam.tr[pos][s[i][j]-'a'],f[i] = max(f[i],Q(1,sam.tot,dfn[pos],0,1));
f[i] += v[i],ans = max(ans,f[i]);
M1(1,sam.tot,dfn[p[ed[i]]],dfn[p[ed[i]]]+siz[p[ed[i]]]-1,f[i],1);
}
cout << ans << '\n';
for (int i = 0;i <= cnt;i++) memset(tr[i],0,sizeof(tr[i]));
B(1,sam.tot,1);
for (int i = 1;i <= sam.tot;i++)
memset(sam.tr[i],0,sizeof(sam.tr[i])),las[i] = 0;
sam.tot = sam.np = 1;
cnt = d = ct = 0;
}
return 0;
}