2020牛客暑期多校训练营(第一场)

Contest Info


传送门

Solved A B C D E F G H I J
5 / 10 Ø - - - - O - Ø Ø O
  • O 在比赛中通过
  • Ø 赛后通过
  • ! 尝试了但是失败了
  • - 没有尝试

Solutions


A. B-Suffix Array

题意:
给定一个只包含(a,b)的字符串,现有一函数(B),字符串通过(B)作用能得到一个新的数组(b),其中(displaystyle b_i=min_{1leq j<i,t_j=t_i}(i-j))
现在找到一个排列(p),使得(B(t_{p_i...p_n})<B_{p_{i+1}...p_n},1leq i<n)都成立,这里是进行字典序比较。

思路:
题解的做法是关于一个结论,不过较难想到,这里说一下一种比较容易想的做法:

  • 首先我们得到(b)数组。
  • 对于所有的后缀(i...n),我们找到(R[i])表示当前后缀第(2)(0)的位置,比如后缀以(aaaab)开头时(R[i]=5)
  • 那么显然可以按照(R[i]-i)从小到大排序,对于(R[i]-i=R[j]-j)相等的情况,我们只需要按照(j+1...n)后缀从小到大排序即可。

容易证明这样排序得到的顺序符合条件。
细节见代码:

Code
// Author : heyuhhh
// Created Time : 2020/07/15 14:46:06
#include<bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 1e5 + 5;
 
struct SA{                                       //sa:1...n  Rank:0...n-1
    int sa[N], c[N], Rank[N], t1[N], t2[N];
    int n;                                          //length
    void da(int* s, int m){
        s[++n] = 0;    //!!  s[n]=0
        int* x = t1, *y = t2;
        for(int i = 0; i < m; ++i) c[i] = 0;
        for(int i = 0; i < n; ++i) c[x[i] = s[i]]++;
        for(int i = 1; i < m; ++i) c[i] += c[i - 1] ;
        for(int i = n - 1; i >= 0; i--) sa[--c[x[i]]] = i;
        for(int k = 1; k <= n; k <<= 1) {
            int p = 0 ;
            for(int i = n - k; i < n; ++i) y[p++] = i ;
            for(int i = 0; i < n; ++i) if(sa[i] >= k) y[p++] =sa[i] - k;
            for(int i = 0; i < m; ++i) c[i] = 0;
            for(int i = 0; i < n; ++i) c[x[y[i]]]++;
            for(int i = 1; i < m; ++i) c[i] += c[i - 1];
            for(int i = n - 1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i] ;
            swap(x , y); p = 1; x[sa[0]] = 0;
            for(int i = 1; i < n; ++i)
                x[sa[i]] = y[sa[i - 1]] == y[sa[i]] && y[sa[i-1] + k] == y[sa[i] + k] ? p - 1 : p++;
            if(p >= n) break ;
            m = p;
        }
        n--;
        for(int i = 0; i <= n; ++i) Rank[sa[i]] = i;
    }
}suf;
 
int n;
char s[N];
int R[N], a[N];
 
void run() {
    suf.n = n;
    for (int i = 0, j; i < n; i = j + 1) {
        j = i;
        while (j + 1 < n && s[j + 1] == s[j]) ++j;
        for (int k = i; k <= j; k++) {
            R[k] = j + 1;
        }
    }
    int pre[2] = {-1, -1};
    for (int i = 0; i < n; ++i) {
        if (pre[s[i] - 'a'] == -1) {
            a[i] = 0;
        } else {
            a[i] = i - pre[s[i] - 'a'];
        }
        pre[s[i] - 'a'] = i;
    }
 
    suf.da(a, n + 2);

    vector<int> ord(n);
    iota(all(ord), 0);
    auto get = [&] (int i) {
        return i >= n ? -1 : suf.Rank[i];
    };
     
    sort(all(ord), [&] (int i, int j) {
        int len1 = R[i] - i, len2 = R[j] - j;
        if (len1 != len2) {
            return len1 < len2;
        }
        if (R[i] >= n || R[j] >= n) {
            return R[i] >= n;
        }
        return get(R[i] + 1) < get(R[j] + 1);
    });
 
    for (int i = 0; i < n; ++i) {
        printf("%d", ord[i] + 1);
        if (i < n - 1) printf(" ");
        else printf("
");
    }
}
int main() {
    while (scanf("%d%s", &n, s) == 2) run();
    return 0;
}

F. Infinite String Comparision

将两个串倍长比较即可,其实只需要比较到(|a|+|b|-gcd(|a|,|b|))即可。

Code
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+233;
 
long long n,k,t;
int main(){
string a,b;
    while(cin>>a>>b){
        int i=0,j=0;
        int len1=a.size();
        int len2=b.size();
        int g=__gcd(len1,len2);
        if(len1==len2){
            if(a==b) cout<<"="<<endl;
            if(a>b) cout<<">"<<endl;
            if(a<b) cout<<"<"<<endl;
            continue;
        }
        int f = 0;
        int maxlen=10*max(len1, len2);
        while(maxlen--){
            if(a[i]>b[j]) {
                f=1;
                break;
            }
            if(a[i]<b[j]){
                f=2;
                break;
            }
            i++;
            j++;
            i%=len1;
            j%=len2;
        }
        if(f==1) cout<<">"<<endl;
        if(f==2) cout<<"<"<<endl;
        if(!f) cout<<"="<<endl;
    }
 
    return 0;
}

H. Minimum-cost Flow

因为给出的就是一个网络,具有网络的性质。
所以我们将网络看作容量为单位(1)的网络,因为最多增广(O(m))次,预处理关于最小费用的前缀和即可。
询问时找到最小增广次数过后可以直接(O(1))计算。
注意分子分母的计算,分母为(v),而分子都应乘上(u)

Code
// Author : heyuhhh
// Created Time : 2020/07/12 14:24:44
#include<bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 100 + 5, M = 200 + 5;

#define INF 0x3f3f3f3f
struct E {
    int from, to, cp, v;
    E() {}
    E(int f, int t, int cp, int v) : from(f), to(t), cp(cp), v(v) {}
};
int f[M];
struct MCMF {
    int n, m, s, t;
    vector<E> edges;
    vector<int> G[N];
    bool inq[N];
    int d[N], p[N], a[M];

    void init(int _n, int _s, int _t) {
        n = _n; s = _s; t = _t;
        for(int i = 0; i <= n; i++) G[i].clear();
        edges.clear(); m = 0;
    }

    void addedge(int from, int to, int cap, int cost) {
        edges.emplace_back(from, to, cap, cost);
        edges.emplace_back(to, from, 0, -cost);
        G[from].push_back(m++);
        G[to].push_back(m++);
    }

    bool BellmanFord(int &flow, int &cost) {
        for(int i = 0; i <= n; i++) d[i] = INF;
        memset(inq, 0, sizeof inq);
        d[s] = 0, a[s] = INF, inq[s] = true;
        queue<int> Q; Q.push(s);
        while (!Q.empty()) {
            int u = Q.front(); Q.pop();
            inq[u] = false;
            for (int& idx: G[u]) {
                E &e = edges[idx];
                if (e.cp && d[e.to] > d[u] + e.v) {
                    d[e.to] = d[u] + e.v;
                    p[e.to] = idx;
                    a[e.to] = min(a[u], e.cp);
                    if (!inq[e.to]) {
                        Q.push(e.to);
                        inq[e.to] = true;
                    }
                }
            }
        }
        if (d[t] == INF) return false;
        flow += a[t];
        cost += a[t] * d[t];
        int u = t;
        while (u != s) {
            edges[p[u]].cp -= a[t];
            edges[p[u] ^ 1].cp += a[t];
            u = edges[p[u]].from;
        }
        return true;
    }

    void go(int& flow) {
        flow = 0;
        int cost = 0;
        while (BellmanFord(flow, cost)) {
            f[flow] = f[flow - 1] + cost;
            cost = 0;
        };
    }
} MM;

int n, m;

void run() {
    MM.init(n, 1, n);
    for (int i = 0; i < m; i++) {
        int u, v, w; cin >> u >> v >> w;
        MM.addedge(u, v, 1, w);
    }
    int npath;
    MM.go(npath);
    int q; cin >> q;
    while (q--) {
        int u, v; cin >> u >> v;
        if (u == 0) {
            cout << "NaN" << '
';
            continue;
        }
        int need = (v + u - 1) / u;
        if (need > npath) {
            cout << "NaN" << '
';
        } else {
            assert(u > 0 && v >= u);
            assert(need > 0);
            ll fz = 1ll * f[need - 1] * u + 1ll * (f[need] - f[need - 1]) * (v - (need - 1) * u);
            ll fm = v;
            ll g = __gcd(fz, fm);
            assert(g > 0);
            fz /= g, fm /= g;
            cout << fz << "/" << fm << '
';
        }
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
    while (cin >> n >> m) run();
    return 0;
}

I. 1 or 2

题意:
给出一张(n)个点(m)条边的无向图,回答是否能找到一个子图使得每个点的度数都恰好为(d_i)

思路:
是一道(hduoj)上的原题,做法很巧妙。
(d_i)都为(1),那么这便是一个裸的一般图最大匹配。
考虑(d_i>1)的情况,其实可以得到启示:对度数进行拆点。
这样对于两个点(u,v),将他们按照度数拆为若干个点,若现在存在一条边连接(u,v),那么一端连接所有的(u),一端连接所有的(v)即可。
最后在匹配中,因为是最大匹配,那么只会有两种情况:

  • 两端的边各选一条,这种即是我们选择((u,v))这条边保留,对应度数减少(1)
  • 选择中间的边,这即是在图中删除这条边,度数不发生变化。

所以正确性就比较显然了。
细节见代码:

Code
// Author : heyuhhh
// Created Time : 2020/07/12 20:50:24
#include<bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 500 + 5, M = 100 + 5;

int n, m;
int d[N];

struct mf {
    // Time complexity: O(n^3)
    // 1-based Vertex index
    // match[x]: vertex matched with x
    // N: numbers of vertex
    int vis[N], par[N], orig[N], match[N], aux[N], t, n;
    vector<int> conn[N];
    queue<int> Q;
    void add_edge(int u, int v) {
        conn[u].push_back(v);
        conn[v].push_back(u);
    }
    void init(int _n) {
        n = _n;
        t = 0;
        for (int i = 0; i <= n; ++i) {
            conn[i].clear();
            match[i] = aux[i] = par[i] = 0;
        }
    }
    void augment(int u, int v) {
        int pv = v, nv;
        do {
            pv = par[v];
            nv = match[pv];
            match[v] = pv;
            match[pv] = v;
            v = nv;
        } while (u != pv);
    }
    int lca(int v, int w) {
        ++t;
        while (true) {
            if (v) {
                if (aux[v] == t) return v;
                aux[v] = t;
                v = orig[par[match[v]]];
            }
            swap(v, w);
        }
    }
    void blossom(int v, int w, int a) {
        while (orig[v] != a) {
            par[v] = w;
            w = match[v];
            if (vis[w] == 1) Q.push(w), vis[w] = 0;
            orig[v] = orig[w] = a;
            v = par[w];
        }
    }
    bool bfs(int u) {
        fill(vis + 1, vis + 1 + n, -1);
        iota(orig + 1, orig + n + 1, 1);
        Q = queue<int>();
        Q.push(u);
        vis[u] = 0;
        while (!Q.empty()) {
            int v = Q.front();
            Q.pop();
            for (int x : conn[v]) {
                if (vis[x] == -1) {
                    par[x] = v;
                    vis[x] = 1;
                    if (!match[x]) return augment(u, x), true;
                    Q.push(match[x]);
                    vis[match[x]] = 0;
                } else if (vis[x] == 0 && orig[v] != orig[x]) {
                    int a = lca(orig[v], orig[x]);
                    blossom(x, v, a);
                    blossom(v, x, a);
                }
            }
        }
        return false;
    }
    int Match() {
        int ans = 0;
        // find random matching (not necessary, constant improvement)
        vector<int> V(n - 1);
        iota(V.begin(), V.end(), 1);
        shuffle(V.begin(), V.end(), mt19937(61471));
        for (auto x : V)
            if (!match[x]) {
                for (auto y : conn[x])
                if (!match[y]) {
                    match[x] = y, match[y] = x;
                    ++ans;
                    break;
                }
            }
        for (int i = 1; i <= n; ++i)
            if (!match[i] && bfs(i)) ++ans;
        return ans;
    }
} mf;

void run() {
    mf.init(500);
    int deg = 0;
    for (int i = 1; i <= n; i++) {
        cin >> d[i];
        deg += d[i];
    }
    int nxt = 150;
    for (int i = 1; i <= m; i++) {
        int u, v; cin >> u >> v;
        mf.add_edge(nxt, nxt + 1);
        for (int j = 0; j < d[u]; j++) {
            mf.add_edge(2 * u + j, nxt);
        }
        for (int j = 0; j < d[v]; j++) {
            mf.add_edge(nxt + 1, 2 * v + j);
        }
        nxt += 2;
    }
    int Max = mf.Match();
    if (deg == 2 * Max - 2 * m) {
        cout << "Yes" << '
';
    } else {
        cout << "No" << '
';
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
    while (cin >> n >> m) run();
    return 0;
}

J. Easy Integration

oeis即可。
也可以进行相关数学推导,会涉及到分部积分。可以直接通过分部积分,也可以三角换元,也可以直接利用相关公式。我就不一一赘述了。

Code
// Author : heyuhhh
// Created Time : 2020/07/12 12:16:05
#include<bits/stdc++.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define Local
void err(int x) {cerr << x;}
void err(long long x) {cerr << x;}
void err(double x) {cerr << x;}
void err(char x) {cerr << '"' << x << '"';}
void err(const string &x) {cerr << '"' << x << '"';}
void _print() {cerr << "]
";}
template<typename T, typename V>
void err(const pair<T, V> &x) {cerr << '{'; err(x.first); cerr << ','; err(x.second); cerr << '}';}
template<typename T>
void err(const T &x) {int f = 0; cerr << '{'; for (auto &i: x) cerr << (f++ ? "," : ""), err(i); cerr << "}";}
template <typename T, typename... V>
void _print(T t, V... v) {err(t); if (sizeof...(v)) cerr << ", "; _print(v...);}
#ifdef Local
#define dbg(x...) cerr << "[" << #x << "] = ["; _print(x)
#else
#define dbg(x...)
#endif
//head
const int N = 2e6 + 5, MOD = 998244353;

int qpow(ll a, ll b) {
    ll res = 1;
    while(b) {
        if (b & 1) res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return res;
}

int f[N];
int fac[N];

void init() {
    fac[0] = 1;
    for (int i = 1; i < N; i++) {
        fac[i] = 1ll * fac[i - 1] * i % MOD;
    }
}

int n;

void run() {
    int ans = fac[2 * n + 1];
    int res = 1ll * fac[n] * fac[n] % MOD;
    ans = 1ll * ans * qpow(res, MOD - 2) % MOD;
    ans = qpow(ans, MOD - 2);
    cout << ans << '
';
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cout << fixed << setprecision(20);
    init();
    while (cin >> n) run();
    return 0;
}