大概就找几个题瞎写了一下。
1.[GXOI/GZOI2019]旅行者
题意
给你一张有向图,有 (k) 个关键点,让你求这 (k) 个点之间的最短距离是多少。
数据范围: (nleq 10^5,mleq 5 imes 10^5)
题解
把这 (k) 个点二进制分组,然后跑最短路即可。
由于是有向图,所以要跑两遍。
复杂度:(O(nlog^2n)) 。据说还有带一只 (log) 的做法,但我不太会。
2.[TJOI2019]甲苯先生和大中锋的字符串
题意
给你一个字符串,让你求在这个字串中出现了 (k) 次的子串,在这些子串中子串长度出现次数最多的长度数。
数据范围: (1leq nleq 10^5,sum nleq 3 imes 10^6)
题解
(TJ) 省选为什么会出后缀自动机的裸题啊。
先对这个字符串建立后缀自动机,然后合并出 ( ext{endpos}) 的大小,对于 ( ext{endpos}) 大小为 (k) 的节点,会对其 (minlen(i)sim maxlen(i)) 的出现次数有贡献。差分一下即可。
3.CF360C Levko and Strings
题意
给一个为 (n) 的只有小写字母组成的串 (s),定义它与一个长度为 (n) 的串 (t) 的的美丽度为:(c) 存在多少个二元组 ((i,j) 1leq i leq j leq n) 满足 (s[i...j] < t[i...j]),这里的 ('<') 是字典序比较。求有多少个 (t) ,使得 (s) 与 (t) 的美丽度为 (k)。
数据范围: (n,k leq 2000) 。
题解
设 (f[i][k]) 表示 (t) 串填到第 (i) 个字符,美丽度为 (k) 的方案数。
转移的话考虑枚举上一个 (s_j < t_j) 的地方,中间的 (t_{j+1}sim t_{i-1}) 和 (s_{j+1}sim s_{i-1}) 字符串完全相同。
若 (s_{k} > t_k) 则有:(f[i][k] = displaystylesum_{j=0}^{i-1} f[j][k] imes (s[i]- ext{'a'})) 。
若 (s_k < t_k) 则有:(f[i][k] = displaystylesum_{j=0}^{i-1} f[j][k-(n-i+1)(i-j)] imes ( ext{'z'}-s[i])) 。
第一个转移的话前缀和优化一下就好了。
第二个转移显然是不会超过 (sqrt{n}) 次的。
所以复杂度为 (O(nksqrt{n})) 。
4.[八省联考2018]劈配
题目太长了,懒得简化了。
题解
对于第一问的话,很容易想到一个二分图的模型,有源点向每个选手连一条容量为 (1) 的边,由导师向汇点连一条容量为 (b_i) 的边,如果第 (i) 名选手填了第 (j) 名导师,则有 (i) 向 (j+n) 连一条容量为 (1) 的边。每次枚举每个选手的每一档志愿,同时向这一档志愿中的导师连边,如果当前网络的最大流增加了 (1), 则说明这个选手会被当前枚举到的这一档志愿录取。
第二问的话,不难想到二分答案,设其为 ( ext{mid}) ,然后把前 (i-mid-1) 的选手对应的最优录取方案加进去。同时把第 (i) 位选手的前 (s_i) 档志愿所对应的边连上。求一遍最大流, 如果最大流等于 (i-mid) (这里要排除被淘汰的学生)则说明这个答案是合法的。
一个小优化:每次可以把一些没有用的边删掉,这样会快很多。
加上这个优化每次暴力重构都可以过去,就不用了写麻烦的删边操作了。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
#include<queue>
using namespace std;
const int inf = 1e7+10;
const int N = 410;
int T,n,m,s,t,x,c,tot = 1;
int head[N],b[N],a[N],p[N],dep[N],Q[N][N][N],cnt[N][N],cur[N];
struct node
{
int to,net,w;
}e[300010];
queue<int> q;
inline int read()
{
int s = 0,w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
return s * w;
}
void add(int x,int y,int w)
{
e[++tot].to = y;
e[tot].w = w;
e[tot].net = head[x];
head[x] = tot;
}
bool bfs()
{
for(int i = 0; i <= t; i++) cur[i] = head[i], dep[i] = 0;
while(!q.empty()) q.pop();
q.push(s); dep[s] = 1;
while(!q.empty())
{
int x = q.front(); q.pop();
for(int i = head[x]; i; i = e[i].net)
{
int to = e[i].to;
if(e[i].w && !dep[to])
{
dep[to] = dep[x] + 1;
q.push(to);
if(to == t) return 1;
}
}
}
return 0;
}
int dinic(int x,int flow)
{
if(x == t || !flow) return flow;
int rest = flow, val;
for(int i = cur[x]; i && rest; i = e[i].net)
{
cur[x] = i;
int to = e[i].to;
if(!e[i].w || dep[to] != dep[x]+1) continue;
val = dinic(to,min(e[i].w,rest));
if(val == 0) dep[to] = 0;
e[i].w -= val, e[i^1].w += val, rest -= val;
}
return flow - rest;
}
int Maxflow()
{
int res = 0, flow = 0;
while(bfs())
{
while(flow = dinic(s,inf)) res += flow;
}
return res;
}
bool judge(int x,int mid)
{
tot = 1; int num = 0;
memset(head,0,sizeof(head));
for(int i = 1; i <= m; i++) add(n+i,t,b[i]), add(t,n+i,0);
for(int i = 1; i <= x-mid-1; i++)
{
add(s,i,1); add(i,s,0);
if(p[i] != m+1)
{
num++;
for(int j = 1; j <= cnt[i][p[i]]; j++) add(i,n+Q[i][p[i]][j],1), add(n+Q[i][p[i]][j],i,0);
}
}
add(s,x,1); add(x,s,0);
for(int i = 1; i <= a[x]; i++)
{
for(int j = 1; j <= cnt[x][i]; j++) add(x,n+Q[x][i][j],1), add(n+Q[x][i][j],x,0);
}
if(Maxflow() == num+1) return 1;
return 0;
}
void Qk()
{
tot = 1;
memset(head,0,sizeof(head));
memset(p,0,sizeof(p));
memset(cnt,0,sizeof(cnt));
}
int main()
{
// freopen("a.in","r",stdin);
// freopen("a.out","w",stdout);
T = read(); c = read();
while(T--)
{
n = read(); m = read();
s = 0, t = n+m+1;
for(int i = 1; i <= m; i++) b[i] = read();
for(int i = 1; i <= m; i++) add(n+i,t,b[i]), add(t,n+i,0);
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
x = read();
Q[i][x][++cnt[i][x]] = j;
}
}
for(int i = 1; i <= n; i++)
{
add(s,i,1); add(i,s,0);
for(int j = 1; j <= m; j++)
{
for(int k = 1; k <= cnt[i][j]; k++) add(i,n+Q[i][j][k],1), add(n+Q[i][j][k],i,0);
if(Maxflow() != 0){p[i] = j; break;}
else for(int k = 1, u = tot; k <= cnt[i][j]; k++, u -= 2) e[u].w = e[u^1].w = 0;//每次把没用的边都删掉。
}
if(!p[i]) p[i] = m+1;
printf("%d ",p[i]);
}
printf("
");
for(int i = 1; i <= n; i++) a[i] = read();
for(int i = 1; i <= n; i++)
{
if(p[i] <= a[i])printf("%d ",0);
else
{
int L = 1, R = i-1, ans = i;
while(L <= R)
{
int mid = (L + R)>>1;
if(judge(i,mid))
{
ans = mid;
R = mid - 1;
}
else L = mid + 1;
}
printf("%d ",ans);
}
}
printf("
"); Qk();
}
return 0;
}
5.AGC038C LCMs
题意
给你一个序列 (a_i), 让你求 (displaystylesum_{i=1}^{n}sum_{j=i+1}^{n} ext{lcm}(a_i,a_j)) 。答案对 (998244353) 取模。
数据范围:(1leq nleq 2 imes 10^5,1leq A_ileq 10^6) 。
题解
莫比乌斯反演。
我们考虑先把 (displaystylesum_{i=1}^{n}sum_{j=1}^{n} ext{lcm}(a_i,a_j)) 求出来,然后减去 (sum a_i) ,在除以 (2), 就是题目中要我们求的东西的答案。
考虑怎么求 (displaystylesum_{i=1}^{n}sum_{j=1}^{n} ext{lcm}(a_i,a_j)) 。莫反一波。
(displaystylesum_{i=1}^{n}sum_{j=1}^{n} ext{lcm}(a_i,a_j))
(=displaystylesum_{i=1}^{n}sum_{j=1}^{n} {a_ia_jover gcd (a_ia_j)})
(=displaystylesum_{d=1}^{n} {1over d}sum_{i=1}^{n}sum_{j=1}^{n} (a_ia_j) imes [gcd (a_i,a_j = d])
(=displaystylesum_{d=1}^{n}{1over d}sum_{p=1}^{nover d}mu(p)sum_{i=1}^{n}sum_{j=1}^{n} (a_i,a_j) imes [pmid gcd({a_iover d},{a_jover d})])
设 (Q = dp) 则有:
(原式=displaystylesum_{Q=1}^{n}sum_{i=1}^{n} [Qmid a_i]a_isum_{j=1}^{n}[Qmid a_j]a_jsum_{dmid Q}{1over d}mu({nover d}))
求 (displaystylesum_{i=1}^{n}[{Qmid a_i}]a_i) ,每个 (a_i) 枚举他的约数然后算一下就好了。
后面的 (displaystyle f(i) = sum_{dmid i}{1over d}mu({nover d})) 。这个也是可以线性筛筛出来的。
复杂度:(O(nsqrt{n}+n)) 。
ps:以后要少用 #define int long long
,这玩意常数贼大,用了这个 (20s+), 不用这个 (7s+) ,就 (nm) 离谱.
6.P4051 [JSOI2007]字符加密
题意
题面懒得简化了 ( ext{QAQ})。
题解
先把整个字符串复制一倍,在接到原串的后面。
然后求一下后缀排序就好了。
7.P4070 [SDOI2016]生成魔咒
题意
有 (n) 次操作,每次操作在 (s) 的结尾加入一个字符。每次操作后问你当前字符串本质不同的子串个数。
数据范围:(nleq 10^5,x_ileq 10^9)
题解
本质不同的子串个数可以用后缀自动机来求,即 (displaystyle sum_{u} maxlen(u)-maxlen(link(u))) 。
然后这道题就可以用 ( ext{LCT}) 动态来维护了
一个简单的做法,对整个串建立一个后缀自动机,然后每次添加新字符的时候维护一下答案的增量就好了。
由于这道题的字符集很大,所以要用 ( ext{map}) 来存状态。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
#include<queue>
#include<map>
using namespace std;
#define LL long long
const int N = 5e5+10;
int n,x,cnt,last,link[N],len[N];
LL ans;
inline int read()
{
int s = 0,w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
return s * w;
}
map<int,int> tr[N];
void Extend(int ch)
{
int cur = ++cnt, p;
len[cur] = len[last] + 1;
for(p = last; p && !tr[p].count(ch); p = link[p]) tr[p][ch] = cur;
if(!p) link[cur] = 1, ans += 1LL * len[cur]-len[link[cur]];
else
{
int x = tr[p][ch];
if(len[x] == len[p] + 1) link[cur] = x, ans += 1LL * len[cur]-len[link[cur]];
else
{
int y = ++cnt;
len[y] = len[p] + 1;
tr[y] = tr[x];
ans -= 1LL * len[x] - len[link[x]];
link[y] = link[x];
link[x] = link[cur] = y;
ans += 1LL * len[y]-len[link[y]];
ans += 1LL * len[x]-len[link[x]];
ans += 1LL * len[cur]-len[link[cur]];
while(p && tr[p][ch] == x)
{
tr[p][ch] = y;
p = link[p];
}
}
}
last = cur;
}
int main()
{
n = read();
last = cnt = 1;
for(int i = 1; i <= n; i++)
{
x = read();
Extend(x);
printf("%lld
",ans);
}
return 0;
}
8.CF802I Fake News (hard)
题意
给你一个字符串,让你求 (sum_{p}cnt(s,p)^2) 。
(cnt(s,p)) 表示子串 (p) 在 (s) 中的出现次数。
数据范围:(|s| leq 10^5, Tleq 10)
题解
后缀自动机裸题。
先对整个串建立后缀自动机,然后求出每个节点 ( ext{endpos}) 的大小。
然后答案为 (sum_{u} (max_len(u)-minlen(u)) imes |endpos(u)|^2) 。
9.CF852B Neural Network country
题意
有一幅有向图,除了源点和汇点有 (L) 层,每层 (n) 个点。 第 (i+1) 层的每个点到第 (i+2) 层的每个点都有一条边,边的权值为有向边终点的权值。求源点到汇点的路径长度能被 (m) 整除的个数。
数据范围: (2leq nleq 10^6,Lleq 10^5,mleq 100) 。
题解
设 (f[i][j]) 表示当前在第 (i) 层,从源点到这一层的点的路径长度 (\%m) 为 (j) 的路径个数。
转移的话则有:(f[i][j] =sum_{k} f[i-1][(j-b_k+m)\% m]) 。
这个直接矩阵快速幂优化一下就好了。
但这个做法有个缺陷,就是到最后两层我们还需要记录到当前路径以那个点结尾,如果把这个也设进状态的话,那复杂度就是 (O(n^3k^3)) 的了。
解决办法也很简单,只需要把后面那两层拿出来单独讨论一下就好了。
时间复杂度:(O(nm+m^3logk)) 。
10.P5231 [JSOI2012]玄武密码
题意描述
给你一个只包含 ( ext{E.S,W,N}) 的字符串 (s),和 (m) 个字符串 (t) 。让你对每一个 (t) 求其最长的前缀 (p) ,满足 (p) 是 (s) 的子串。
数据范围:(1leq nleq 10^7,mleq 10^5,|t|leq 100)
题解
好像有 ( ext{AC自动机}) 和 (SAM) 的两种做法。
做法1 SAM
(s) 的子串等价于 (s) 的一段后缀的前缀。
我们对 (s) 建立后缀自动机,然后对每个 (t) 串在上面跑匹配就好了。
由于 (s) 只包含 ( ext{E,S,W,N}) 这四个字符,所以我们 (trans) 数组第二维只需要开到 (4) 就可以了,这样就可以省下不少空间。
做法2:AC自动机
由于自己写的是上面的那个做法,所以这个口胡一下就好了(不保证正确性)。
我们对 (t) 串建立 (AC) 自动机,然后对 (s) 串在上面进行匹配,并把经过的点标记一下。、
最后在对 (t) 串跑一下 ( ext{tire}) 树((AC) 自动机的插入所构成的 ( ext{tire}) 树),如果当前节点有标记,就拿它来更新一下答案就好了。