牛课多校第一场
A - B-Suffix Array (后缀排序)
题意
字符串函数\(B(t_1t_2...t_k) = b_1b_2...b_k\)满足:
- 如果存在\(j<i\),\(t_j=t_i\),\(b_i=min_{1 \le j < i,t_j=t_i}\{i-j\}\),
- 否则,\(b_i=0\)
求字符串\(t_1t_2...t_k\)的每个后缀的B函数序列的排位(从小到大)。
字符串只包含a, b两种字符。
思路
假设当前字符到相同的下一个字符的距离是k,如果不存在下一个字符,则k=inf。
举几个例子:
- abbbba,距离为5,对应的B序列为 001115
- abbba,距离为4,对应的B序列为 00114
- aba,距离为2,对应的B序列为 002
- aa,距离为1,对应的B序列为 01
- abbb,距离为inf,对应的B序列为 00111
观察可以发现,这个距离越大,这一段对应的B序列就越小,所以可以计算出每个位置的距离k值,然后按照这个k值后缀排序即可。
#include <iostream>
#include <cstdio>
#include <queue>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <cstring>
#include <string>
#include <deque>
#include <cmath>
#include <iomanip>
#include <cctype>
#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define FILE freopen(".//data_generator//in.txt","r",stdin),freopen("res2.txt","w",stdout)
#define FI freopen(".//data_generator//in.txt","r",stdin)
#define FO freopen("res2.txt","w",stdout)
#define pb push_back
#define mp make_pair
#define seteps(N) fixed << setprecision(N)
typedef long long ll;
using namespace std;
/*-----------------------------------------------------------------*/
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f
const int N = 2e5 + 10;
const double eps = 1e5;
int last[N];
string s;
typedef pair<int, int> PII;
vector<PII> num;
int n, w, sa[N], rk[N << 1], oldrk[N << 1];
int ans[N];
int main() {
//FILE;
IOS;
int n;
while(cin >> n) {
num.clear();
cin >> s;
int prea = -1, preb = -1;
for(int i = 0; i < n; i++) last[i] = i;
for(int i = 0; i < s.size(); i++) {
if(s[i] == 'a') {
if(prea != -1) last[prea] = i;
prea = i;
} else {
if(preb != -1) last[preb] = i;
preb = i;
}
}
for(int i = 0; i <= min(N, 3 * n + 10); i++) rk[i] = 0;
int mx = 0;
for(int i = 0; i < s.size(); i++) {
num.push_back(mp(last[i] - i, i));
mx = max(last[i] - i, mx);
}
mx++;
int i, p;
for (i = 1; i <= n; i++) {
rk[i] = num[i - 1].first;
if(rk[i]) rk[i] = mx - rk[i] + 1;
else rk[i] = rk[i] + 1;
}
for (w = 1; w < n; w <<= 1) {
for (i = 1; i <= n; ++i) sa[i] = i;
sort(sa + 1, sa + n + 1, [](int x, int y) {return rk[x] == rk[y] ? rk[x + w] < rk[y + w] : rk[x] < rk[y];});
for(int i = 0; i <= min(N, 2 * n); i++) oldrk[i] = rk[i];
for(p = 0, i = 1; i <= n; i++) {
if(oldrk[sa[i]] == oldrk[sa[i - 1]] && oldrk[sa[i] + w] == oldrk[sa[i - 1] + w]) {
rk[sa[i]] = p;
} else {
rk[sa[i]] = ++p;
}
}
}
for(int i = 1; i <= n; i++) {
ans[rk[i]] = num[i - 1].second + 1;
}
for(int i = 1; i <= n; i++) cout << ans[i] << " \n"[i == n];
}
}
B - Infinite Tree (虚树)
题意
Let \(\mathrm{mindiv}\) be the minimum divisor greater than 1 of n. Bobo construsts a tree on all positive integer numbers \(\{1, 2, \dots, \}\) by adding edges between \(n\) and \(\frac{n}{\mathrm{mindiv}(n)}\) for all \(n > 1\).
Let \(\delta(u, v)\) be the number of edges between vertices u and v on the tree. Given \(m\) and \(w_1\dots w_m\) , Bobo would like to find \(\min_{u} \sum_{i = 1}^{m} w_i \delta(u, i!)\).
思路
这题使用了虚树的技巧。
虚树就是丢弃多余的结点,只保留待处理的结点和它们的公共祖先。
构造虚树方法是以dfs序遍历待处理的结点,用一个栈来保存当前处理的结点。每加入一个新结点,求出它与栈顶结点的LCA。有两种情况:
- LCA和栈顶结点相同,说明新结点和栈顶结点同在一条链中。于是新结点连边入栈。
- LCA和栈顶结点不同,说明新结点和栈顶结点不在一条链中。一直弹栈到栈顶结点为LCA,然后新结点连边入栈。
可以看出,这里的栈维护的是一条树链。
所以此题的数据范围巨大,显然我们只能保留n!的结点。构造出这个n!的虚树后,然后用类似dp的方法就可以求出答案。
所以任务就是构建虚树。首先,1!到n!的n个结点已经是dfs序了。观察可得,一个数在该树上的到根结点的路径长度和数的质因数相等,且较大的质因数首先影响影响路径。例如:
- 6的路径:3 -> 2
- 30的路径:5 -> 3 -> 2
所以相邻两个点的LCA的深度就是新结点新增的最大质因数之前的质因数个数。比如栈顶是6,插入30,新增最大质因数是5,在路径3 -> 2之前比5大的个数为0,所以LCA深度是0。可以画图证明。
详见代码
#include <iostream>
#include <cstdio>
#include <queue>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <cstring>
#include <string>
#include <deque>
#include <cmath>
#include <stack>
#include <iomanip>
#include <cctype>
#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define FILE freopen(".//data_generator//in.txt","r",stdin),freopen("res.txt","w",stdout)
#define FI freopen(".//data_generator//in.txt","r",stdin)
#define FO freopen("res.txt","w",stdout)
#define pb push_back
#define mp make_pair
#define seteps(N) fixed << setprecision(N)
typedef long long ll;
using namespace std;
/*-----------------------------------------------------------------*/
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f
const int N = 2e5 + 10;
const double eps = 1e5;
int w[N];
int arr[N];
int mindiv[N];
int dep[N];
int lcadep[N];
int n;
vector<int> np[N];
ll val[N];
ll allv;
ll tans;
int si;
int lowbit(int x) {
return x&(-x);
}
void add(int p, int val) {
while(p <= n) {
arr[p] += val;
p += lowbit(p);
}
}
int get(int p) {
int res = 0;
while(p) {
res += arr[p];
p -= lowbit(p);
}
return res;
}
void prework() {
for(int i = 2; i < N; i++) {
if(mindiv[i]) continue;
mindiv[i] = i;
for(int j = 2 * i; j < N; j += i) {
if(!mindiv[j]) mindiv[j] = i;
}
}
}
void adde(int u, int v) {
np[u].push_back(v);
np[v].push_back(u);
}
int st[N];
int top;
void build() {
top = 0;
st[++top] = 1;
si = n;
for(int i = 2; i <= n; i++) {
dep[i] = dep[i - 1] + 1;
int mxd = i;
for(; mxd != mindiv[mxd]; mxd /= mindiv[mxd]) dep[i]++;
lcadep[i] = get(n) - get(mxd - 1);
for(int j = i; j != 1; j /= mindiv[j]) add(mindiv[j], 1);
}
for(int i = 2; i <= n; i++) {
while(top > 1 && dep[st[top - 1]] >= lcadep[i]) {
adde(st[top - 1] ,st[top]);
top--;
}
if(dep[st[top]] != lcadep[i]) {
dep[++si] = lcadep[i];
adde(si, st[top]);
st[top] = si;
}
st[++top] = i;
}
while(top > 1) {
adde(st[top - 1], st[top]);
top--;
}
}
ll dfs(int p, int fa) {
ll res = 0;
for(int nt : np[p]) {
if(nt == fa) continue;
res += dfs(nt, p);
}
val[p] = res + w[p];
return val[p];
}
void solve(int p, int fa, ll ans) {
tans = min(ans, tans);
for(int nt : np[p]) {
if(nt == fa) continue;
int d = dep[nt] - dep[p];
ans += d * (allv - 2 * val[nt]);
solve(nt, p, ans);
ans -= d * (allv - 2 * val[nt]);
}
}
void init() {
allv = tans = top = 0;
for(int i = 0; i <= si; i++) {
np[i].clear();
val[i] = w[i] = lcadep[i] = dep[i] = arr[i] = 0;
}
}
int main() {
IOS;
//FILE;
prework();
while(cin >> n) {
init();
for(int i = 1; i <= n; i++) {
cin >> w[i];
allv += w[i];
}
build();
dfs(1, 0);
for(int i = 1; i <= n; i++) {
tans += 1ll * dep[i] * w[i];
}
solve(1, 0, tans);
cout << tans << endl;
}
}
D - Quadratic Form(矩阵的逆)
题意
\(X = (x_1,x_2,...,x_n)^T\),\(A\)为\(n×n\)的正定二次型,\(b\)为\(n×1\)的列向量。
求满足求\(X^TAX \le 1\),\((X^Tb)^2\)的最大值。
思路
结论是\(b^TA^{-1}b\)。
用了使用拉格朗日乘数法,具体计算过程待补。主要是记录求矩阵的逆的高斯消元模板。
#include <bits/stdc++.h>
#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define FILE freopen(".//data_generator//in.txt","r",stdin),freopen("res.txt","w",stdout)
#define FI freopen(".//data_generator//in.txt","r",stdin)
#define FO freopen("res.txt","w",stdout)
#define pb push_back
#define mp make_pair
#define seteps(N) fixed << setprecision(N)
typedef long long ll;
using namespace std;
/*-----------------------------------------------------------------*/
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
inline ll qmul(ll a, ll b, ll m) {
ll res = 0;
while(b) {
if(b & 1) res = (res + a) % m;
a = (a << 1) % m;
b = b >> 1;
}
return res;
}
inline ll qpow(ll a, ll b, ll m) {
ll res = 1;
while(b) {
if(b & 1) res = (res * a) % m;
a = (a * a) % m;
b = b >> 1;
}
return res;
}
#define INF 0x3f3f3f3f
const int N = 3e2 + 10;
const ll M = 998244353;
const double eps = 1e-5;
ll arr[N][N << 1];
ll b[N];
bool Gauss(ll a[][N << 1], int n) { //高斯消元求矩阵的逆
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
a[i][j + n] = 0;
}
a[i][i + n] = 1;
}
for(int i = 1; i <= n; i++) {
int r = i;
for(int j = i + 1; j <= n; j++) {
if(a[j][i] > a[r][i]) r = j;
}
if(r != i) swap(a[i], a[r]);
if(!a[i][i]) return false;
ll inv = qpow(a[i][i], M - 2, M);
for(int j = 1; j <= n; j++) {
if(j == i) continue;
ll da = a[j][i] * inv % M; //a[j][i]可能会被更新,所以要先保存
for(int k = i; k <= (n << 1); k++) {
ll t = a[i][k] * da % M;
a[j][k] = ((a[j][k] - t) % M + M) % M;
}
}
for(int j = i; j <= (n << 1); j++) a[i][j] = a[i][j] * inv % M;
}
return true;
}
int main() {
IOS;
int n;
while(cin >> n) {
memset(arr, 0, sizeof arr);
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
cin >> arr[i][j];
}
}
Gauss(arr, n);
for(int i = 1; i <= n; i++) {
cin >> b[i];
}
ll ans = 0;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
ans += b[i] * arr[i][j + n] % M * b[j] % M;
ans %= M;
}
}
cout << (ans % M + M) % M << endl;
}
}
F - Infinite String Comparision
题意
有字符串a,b。比较无限字符串aaa...和bbb...的大小。
思路
标程用了周期引理
\(若p,q是字符串s的循环节长度,则有p+q-gcd(p,q)\le |s|,且gcd(p,q)也是s的一个循环节长度。\)
个人理解是\(p+q-gcd(p,q)\le |s|\)划定了上界,第一个不相同的字符一定出现在前\(p+q-gcd(p,q)\)的范围内。因此只需比较前\(p+q-gcd(p,q)\)个字符。
代码略
I - 1 or 2 (一般图匹配)
题意
给定一个有n个点的图,每个点有点权\(d_i\),代表第i个点要有\(d_i\)个度。问是否可以从图中删除一些边使得满足条件。
思路
一般图匹配模板题。
- 若\(d_i=1\),从i连边到它所有相邻的点。
- 若\(d_i=2\),若相邻的点的d值为2的点大于1,则连边到它所有相邻的点,否则不连d值为2的点。
然后带花树跑一般图匹配看看是否完美匹配即可。
#include <iostream>
#include <cstdio>
#include <queue>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <cstring>
#include <string>
#include <deque>
#include <cmath>
#include <iomanip>
#include <cctype>
#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define FILE freopen(".//data_generator//in.txt","r",stdin),freopen("res.txt","w",stdout)
#define FI freopen(".//data_generator//in.txt","r",stdin)
#define FO freopen("res.txt","w",stdout)
#define pb push_back
#define mp make_pair
#define seteps(N) fixed << setprecision(N)
typedef long long ll;
using namespace std;
/*-----------------------------------------------------------------*/
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f
const int N = 5e2 + 10;
const double eps = 1e5;
struct edge {
int nt, ne;
}ed[N * N];
int head[N];
int si = 0;
int pre[N];
int dfn[N], match[N], vis[N];
int fa[N];
int cnt;
void init() {
memset(head, 0, sizeof head);
memset(match, 0, sizeof match);
si = 0;
}
void Add(int u, int v) {
si++;
ed[si] = edge{v, head[u]};
head[u] = si;
}
void add(int u, int v) {
Add(u, v);
Add(v, u);
}
int find(int x) {
if(fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
int lca(int u, int v) {
cnt++;
while(1) {
swap(u, v);
if(u) { //wow!!不加直接T,有可能其中一个比另一个浅,变成0了。
u = find(u);
if(dfn[u] == cnt) return u;
else {
dfn[u] = cnt;
u = pre[match[u]];
}
}
}
}
void shrink(int u, int v, int rt, queue<int> &q) {
while(find(u) != rt) {
pre[u] = v;
v = match[u];
if(vis[v] == 2) {
vis[v] = 1;
q.push(v);
}
if(find(u) == u) fa[u] = rt;
if(find(v) == v) fa[v] = rt;
u = pre[v];
}
}
bool aug(int s, int n) {
for(int i = 1; i <= n; i++) fa[i] = i;
memset(vis, 0, sizeof vis);
memset(pre, 0, sizeof pre);
vis[s] = 1;
queue<int> q;
q.push(s);
while(!q.empty()) {
int u = q.front();
q.pop();
for(int e = head[u]; e; e = ed[e].ne) {
int v = ed[e].nt;
if(vis[v] == 2 || find(u) == find(v)) continue;
if(!vis[v]) {
vis[v] = 2;
pre[v] = u;
if(!match[v]) {
int up;
for(int cur = v; cur; cur = up) {
int bp = pre[cur];
up = match[bp];
match[cur] = bp;
match[bp] = cur;
}
return true;
}
vis[match[v]] = 1;
q.push(match[v]);
} else {
int rt = lca(u, v);
shrink(u, v, rt, q);
shrink(v, u, rt, q);
}
}
}
return false;
}
int d[N];
vector<int> np[N];
int main() {
//IOS;
int n, m;
while(cin >> n >> m) {
init();
for(int i = 1; i <= n; i++) {
cin >> d[i];
np[i].clear();
}
for(int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
np[u].push_back(v);
np[v].push_back(u);
add(u, v);
}
for(int i = 1; i<= n; i++) {
if(d[i] == 2) {
int dcnt = 0;
n++;
for(auto nt : np[i]) {
if(d[nt] == 2) {
dcnt++;
} else {
add(n, nt);
}
}
if(dcnt > 1) {
for(auto nt : np[i]) {
if(d[nt] == 2) {
add(n, nt);
}
}
}
}
}
int ans = 0;
for(int i = 1; i <= n; i++) {
if(!match[i]) {
if(aug(i, n)) ans++;
}
}
//cout << n << " " << ans << endl;
if(ans == n / 2) cout << "Yes" << endl;
else cout << "No" << endl;
}
}