杂题乱做
大概就找几个题瞎写了一下。
1.[GXOI/GZOI2019]旅行者
题意
给你一张有向图,有 \(k\) 个关键点,让你求这 \(k\) 个点之间的最短距离是多少。
数据范围: \(n\leq 10^5,m\leq 5\times 10^5\)
题解
把这 \(k\) 个点二进制分组,然后跑最短路即可。
由于是有向图,所以要跑两遍。
复杂度:\(O(n\log^2n)\) 。据说还有带一只 \(\log\) 的做法,但我不太会。
2.[TJOI2019]甲苯先生和大中锋的字符串
题意
给你一个字符串,让你求在这个字串中出现了 \(k\) 次的子串,在这些子串中子串长度出现次数最多的长度数。
数据范围: \(1\leq n\leq 10^5,\sum n\leq 3\times 10^6\)
题解
\(TJ\) 省选为什么会出后缀自动机的裸题啊。
先对这个字符串建立后缀自动机,然后合并出 \(\text{endpos}\) 的大小,对于 \(\text{endpos}\) 大小为 \(k\) 的节点,会对其 \(minlen(i)\sim maxlen(i)\) 的出现次数有贡献。差分一下即可。
3.CF360C Levko and Strings
题意
给一个为 \(n\) 的只有小写字母组成的串 \(s\),定义它与一个长度为 \(n\) 的串 \(t\) 的的美丽度为:\(c\) 存在多少个二元组 \((i,j) \ 1\leq 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] = \displaystyle\sum_{j=0}^{i-1} f[j][k] \times (s[i]- \text{'a'})\) 。
若 \(s_k < t_k\) 则有:\(f[i][k] = \displaystyle\sum_{j=0}^{i-1} f[j][k-(n-i+1)(i-j)] \times (\text{'z'}-s[i])\) 。
第一个转移的话前缀和优化一下就好了。
第二个转移显然是不会超过 \(\sqrt{n}\) 次的。
所以复杂度为 \(O(nk\sqrt{n})\) 。
4.[八省联考2018]劈配
题目太长了,懒得简化了。
题解
对于第一问的话,很容易想到一个二分图的模型,有源点向每个选手连一条容量为 \(1\) 的边,由导师向汇点连一条容量为 \(b_i\) 的边,如果第 \(i\) 名选手填了第 \(j\) 名导师,则有 \(i\) 向 \(j+n\) 连一条容量为 \(1\) 的边。每次枚举每个选手的每一档志愿,同时向这一档志愿中的导师连边,如果当前网络的最大流增加了 \(1\), 则说明这个选手会被当前枚举到的这一档志愿录取。
第二问的话,不难想到二分答案,设其为 \(\text{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("\n");
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("\n"); Qk();
}
return 0;
}
5.AGC038C LCMs
题意
给你一个序列 \(a_i\), 让你求 \(\displaystyle\sum_{i=1}^{n}\sum_{j=i+1}^{n} \text{lcm}(a_i,a_j)\) 。答案对 \(998244353\) 取模。
数据范围:\(1\leq n\leq 2\times 10^5,1\leq A_i\leq 10^6\) 。
题解
莫比乌斯反演。
我们考虑先把 \(\displaystyle\sum_{i=1}^{n}\sum_{j=1}^{n}\text{lcm}(a_i,a_j)\) 求出来,然后减去 \(\sum a_i\) ,在除以 \(2\), 就是题目中要我们求的东西的答案。
考虑怎么求 \(\displaystyle\sum_{i=1}^{n}\sum_{j=1}^{n}\text{lcm}(a_i,a_j)\) 。莫反一波。
\(\displaystyle\sum_{i=1}^{n}\sum_{j=1}^{n}\text{lcm}(a_i,a_j)\)
\(=\displaystyle\sum_{i=1}^{n}\sum_{j=1}^{n} {a_ia_j\over \gcd (a_ia_j)}\)
\(=\displaystyle\sum_{d=1}^{n} {1\over d}\sum_{i=1}^{n}\sum_{j=1}^{n} (a_ia_j)\times [\gcd (a_i,a_j = d]\)
\(=\displaystyle\sum_{d=1}^{n}{1\over d}\sum_{p=1}^{n\over d}\mu(p)\sum_{i=1}^{n}\sum_{j=1}^{n} (a_i,a_j)\times [p\mid \gcd({a_i\over d},{a_j\over d})]\)
设 \(Q = dp\) 则有:
\(原式=\displaystyle\sum_{Q=1}^{n}\sum_{i=1}^{n} [Q\mid a_i]a_i\sum_{j=1}^{n}[Q\mid a_j]a_j\sum_{d\mid Q}{1\over d}\mu({n\over d})\)
求 \(\displaystyle\sum_{i=1}^{n}[{Q\mid a_i}]a_i\) ,每个 \(a_i\) 枚举他的约数然后算一下就好了。
后面的 \(\displaystyle f(i) = \sum_{d\mid i}{1\over d}\mu({n\over d})\) 。这个也是可以线性筛筛出来的。
复杂度:\(O(n\sqrt{n}+n)\) 。
ps:以后要少用 #define int long long
,这玩意常数贼大,用了这个 \(20s+\), 不用这个 \(7s+\) ,就 \(nm\) 离谱.
6.P4051 [JSOI2007]字符加密
题意
题面懒得简化了 \(\text{QAQ}\)。
题解
先把整个字符串复制一倍,在接到原串的后面。
然后求一下后缀排序就好了。
7.P4070 [SDOI2016]生成魔咒
题意
有 \(n\) 次操作,每次操作在 \(s\) 的结尾加入一个字符。每次操作后问你当前字符串本质不同的子串个数。
数据范围:\(n\leq 10^5,x_i\leq 10^9\)
题解
本质不同的子串个数可以用后缀自动机来求,即 \(\displaystyle \sum_{u} maxlen(u)-maxlen(link(u))\) 。
然后这道题就可以用 \(\text{LCT}\) 动态来维护了
一个简单的做法,对整个串建立一个后缀自动机,然后每次添加新字符的时候维护一下答案的增量就好了。
由于这道题的字符集很大,所以要用 \(\text{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\n",ans);
}
return 0;
}
8.CF802I Fake News (hard)
题意
给你一个字符串,让你求 \(\sum_{p}cnt(s,p)^2\) 。
\(cnt(s,p)\) 表示子串 \(p\) 在 \(s\) 中的出现次数。
数据范围:\(|s| \leq 10^5, T\leq 10\)
题解
后缀自动机裸题。
先对整个串建立后缀自动机,然后求出每个节点 \(\text{endpos}\) 的大小。
然后答案为 \(\sum_{u} (max_len(u)-minlen(u))\times |endpos(u)|^2\) 。
9.CF852B Neural Network country
题意
有一幅有向图,除了源点和汇点有 \(L\) 层,每层 \(n\) 个点。 第 \(i+1\) 层的每个点到第 \(i+2\) 层的每个点都有一条边,边的权值为有向边终点的权值。求源点到汇点的路径长度能被 \(m\) 整除的个数。
数据范围: \(2\leq n\leq 10^6,L\leq 10^5,m\leq 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]玄武密码
题意描述
给你一个只包含 \(\text{E.S,W,N}\) 的字符串 \(s\),和 \(m\) 个字符串 \(t\) 。让你对每一个 \(t\) 求其最长的前缀 \(p\) ,满足 \(p\) 是 \(s\) 的子串。
数据范围:\(1\leq n\leq 10^7,m\leq 10^5,|t|\leq 100\)
题解
好像有 \(\text{AC自动机}\) 和 \(SAM\) 的两种做法。
做法1 SAM
\(s\) 的子串等价于 \(s\) 的一段后缀的前缀。
我们对 \(s\) 建立后缀自动机,然后对每个 \(t\) 串在上面跑匹配就好了。
由于 \(s\) 只包含 \(\text{E,S,W,N}\) 这四个字符,所以我们 \(trans\) 数组第二维只需要开到 \(4\) 就可以了,这样就可以省下不少空间。
做法2:AC自动机
由于自己写的是上面的那个做法,所以这个口胡一下就好了(不保证正确性)。
我们对 \(t\) 串建立 \(AC\) 自动机,然后对 \(s\) 串在上面进行匹配,并把经过的点标记一下。、
最后在对 \(t\) 串跑一下 \(\text{tire}\) 树(\(AC\) 自动机的插入所构成的 \(\text{tire}\) 树),如果当前节点有标记,就拿它来更新一下答案就好了。