牛课多校第一场

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;
    }
}
posted @ 2020-08-04 21:35  limil  阅读(99)  评论(0编辑  收藏  举报