2019 Multi-University Training Contest 3

2019 Multi-University Training Contest 3

题目链接

Blow up the city

首先考虑建立一个虚根,与所有反图中入度为\(0\)的点连边形成一颗树,然后考虑建出其支配树。对于\(DAG\)来说比较简单,反图中按着拓扑序来搞,这样就可以保证处理一个点时,其父亲们都在支配树中了,一个点在支配树中的父亲就是所有能到达它的点在支配树中的\(lca\)
支配树有一个性质就是,一个结点到其根这条链上所有的点都支配它,那么根据这个性质处理出深度,利用树上倍增求\(lca\),可以做到单词询问\(O(logn)\)的复杂度。
代码如下:

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5, M = 2e5 + 5;
int T;
vector <int> G[N], rG[N];
int n, m, tot;
int d[N], f[N][20], a[N], dep[N];
int lca(int x, int y) {
    if(dep[y] > dep[x]) swap(x, y);
    for(int i = 19; i >= 0; i--) {
        if(dep[f[x][i - 1]] >= dep[y]) x = f[x][i - 1];
    }
    if(x == y) return x;
    for(int i = 19; i >= 0; i--) {
        if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
    }
    return f[x][0];
}
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> T;
    while(T--) {
        cin >> n >> m;
        for(int i = 1; i <= n + 1; i++)
            G[i].clear(), rG[i].clear(), d[i] = 0;
        for(int i = 1; i <= m; i++) {
            int u, v; cin >> u >> v;
            rG[v].push_back(u);
            G[u].push_back(v);
            d[u]++;
        }
        queue <int> q;
        for(int i = 1; i <= n; i++) {
            if(!d[i]) {
                q.push(i);
                G[i].push_back(n + 1);
            }
        }
        tot = 0; dep[n + 1] = 0;
        while(!q.empty()) {
            int u = q.front(); q.pop();
            a[++tot] = u;
            for(auto v : rG[u]) {
                if(--d[v] == 0) q.push(v);
            }
        }
        for(int i = 1; i <= tot; i++) {
            int x = a[i], p = -1;
            for(auto y : G[x]) {
                if(p == -1) p = y;
                else p = lca(p, y);
            }
            f[x][0] = p; dep[x] = dep[p] + 1;
            for(int j = 1; j < 20; j++) f[x][j] = f[f[x][j - 1]][j - 1] ;
        }
        int Q; cin >> Q;
        while(Q--) {
            int x, y; cin >> x >> y;
            int ans = dep[x] + dep[y] - dep[lca(x, y)];
            cout << ans << '\n';
        }
    }
    return 0;
}

Distribution of books

如果\(a_i\)只能为正的话就是一个很简单的二分答案了,但这里能够为负数,也就是说之前贪心地来选取是不行的。所以我们就\(dp\)一下处理出前\(i\)个数能分配的最大组数就行了,转移的时候用线段树优化一下。

Code
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int N = 4e5 + 5;
ll b[N], a[N];
int T, D, n, k;
int get(ll x) {
    return lower_bound(b + 1, b + D + 1, x) - b;
}
int mx[N << 2];
void push_up(int o) {
    mx[o] = max(mx[o << 1], mx[o << 1|1]);
}
void build(int o, int l, int r) {
    mx[o] = -INF;
    if(l == r) return;
    int mid = (l + r) >> 1;
    build(o << 1, l, mid); build(o << 1|1, mid + 1, r);
}
void update(int o, int l, int r, int pos, int v) {
    if(l == r && l == pos) {
        mx[o] = max(mx[o], v);
        return ;
    }
    int mid = (l + r) >> 1;
    if(pos <= mid) update(o << 1, l, mid, pos, v);
    else update(o << 1|1, mid + 1, r, pos, v);
    push_up(o);
}
int query(int o, int l, int r, int v) {
    if(l >= v) return mx[o];
    int mid = (l + r) >> 1;
    int ans = query(o << 1|1, mid + 1, r, v);
    if(v <= mid) ans = max(ans, query(o << 1, l, mid, v));
    return ans;
}
bool check(ll x) {
    D = 0;
    for(int i = 1; i <= n; i++) {
        b[++D] = a[i];
        b[++D] = a[i] - x;
    }
    b[++D] = 0;
    sort(b + 1, b + D + 1);
    D = unique(b + 1, b + D + 1) - b - 1;
    build(1, 1, D);
    update(1, 1, D, get(0), 0);
    for(int i = 1; i <= n; i++) {
        int f = query(1, 1, D, get(a[i] - x)) + 1;
        if(f >= k) return 1;
        update(1, 1, D, get(a[i]), f);
    }
    return 0;
}
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> T;
    while(T--) {
        cin >> n >> k;
        for(int i = 1; i <= n; i++) cin >> a[i], a[i] += a[i - 1];
        ll l = -2e14, r = 1e9 + 1, mid;
        while(l < r) {
            mid = (l + r) >> 1;
            if(check(mid)) r = mid;
            else l = mid + 1;
        }
        cout << l << '\n';
    }
    return 0;
}

Fansblog

考虑威尔逊定理:对于一个质数\(p\),有\((p-1)!=p-1(\) mod \(p)\),并且还需要知道素数的间隔一般来讲是不超过\(264\)的(可能记忆有点错误)。所以就暴力找上一个素数,然后暴力求逆元来搞就行了。
代码如下:

Code
#include <bits/stdc++.h>
using namespace std;
typedef __int128 ll;
ll p;
int T;
struct Istream {
    template <class T>
    Istream &operator >>(T &x) {
        static char ch;static bool neg;
        for(ch=neg=0;ch<'0' || '9'<ch;neg|=ch=='-',ch=getchar());
        for(x=0;'0'<=ch && ch<='9';(x*=10)+=ch-'0',ch=getchar());
        x=neg?-x:x;
        return *this;
    }
}fin;

struct Ostream {
    template <class T>
    Ostream &operator <<(T x) {
        x<0 && (putchar('-'),x=-x);
        static char stack[233];static int top;
        for(top=0;x;stack[++top]=x%10+'0',x/=10);
        for(top==0 && (stack[top=1]='0');top;putchar(stack[top--]));
        return *this;
    }

    Ostream &operator <<(char ch) {
        putchar(ch);
        return *this;
    }
}fout;

bool Is_prime(long long x) {
    bool ok = true;
    for(int i = 2; (long long)i * i <= x; i++) {
        if(x % i == 0) {
            ok = false;
            break ;
        }
    }
    return ok;
}
ll qp(ll a, long long b) {
    ll ans = 1;
    while(b) {
        if(b & 1) ans = ans * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return ans;
}
int main() {
    cin >> T;
    while(T--) {
        fin >> p;
        ll ans = p - 1;
        long long q;
        for(long long i = p - 1;;i--) {
            if(Is_prime(i)) {
                q = i; break;
            }
        }
        for(long long i = q + 1; i <= p - 1; i++) {
            ans = ans % p * qp(i, p - 2) % p;
        }
        fout << ans << '\n';
    }
    return 0;
}

Find the answer

队友写的伸展树。。实际上不用这么麻烦,考虑将问题转换一下:保留最小的几个数使得满足\(\sum_{j=1}^{i-1}W_j\leq m-W_i\)。因为这是前缀,所以直接用线段树搞一搞即可。
还是给队友写的代码吧:

Code
#include<bits/stdc++.h>
typedef long long ll;
const int MAXN = 2e5 + 5, MAXM = 2e5 + 5, INF = 0x3f3f3f3f, MOD = 998244353;
const ll INFL = 0x3f3f3f3f3f3f3f3f;
using namespace std;
const int oo = (1e9) - (1e6);
#define lson o<<1,l,m
#define rson o<<1|1,m+1,r
#define mid l + ((r-l)>>1)
#define pb push_back
#define RR register
#define random(a,b) ((a)+rand()%((b)-(a)+1))
#define all(v) (v.begin(),v.end())
#define lc(x) c[x][0]
#define rc(x) c[x][1]
typedef long double db;
typedef unsigned int uint;
int t, n, m, a;
namespace Rin {
    int c[MAXN][2], fa[MAXN], siz[MAXN], cnt[MAXN], rt, tot = 0;
    ll v[MAXN], sum[MAXN];
    void init() {
        rt = tot = 0;
        memset(siz, 0, sizeof(siz));
        memset(cnt, 0, sizeof(cnt));
        memset(v, 0, sizeof(v));
        memset(sum, 0, sizeof(sum));
        memset(c, 0, sizeof(c));
        memset(fa, 0, sizeof(c));
    }
    void pushUp(int x) {
        siz[x] = siz[lc(x)] + siz[rc(x)] + cnt[x];
        sum[x] = sum[lc(x)] + sum[rc(x)] + v[x] * cnt[x];
    }
    inline void connect(int x, int f, int k) {
        c[f][k] = x;
        fa[x] = f;
    }
    void rotate(int x) {
        int y = fa[x], z = fa[y], k = x == c[y][1];
        connect(c[x][k ^ 1], y, k);
        connect(y, x, k ^ 1);
        connect(x, z, y == c[z][1]);
        pushUp(y); pushUp(x);
    }

    void splay(int x, int t) {
        while (fa[x] != t) {
            int y = fa[x], z = fa[y];
            if (z != t)(x == c[y][0]) ^ (y == c[z][0]) ? rotate(x) : rotate(y);
            rotate(x);
        }
        if (t == 0)rt = x;
    }
    void insert(int x) {
        int u = rt, f = 0;
        while (u) {
            f = u; siz[u]++; sum[u] += x;
            u = c[u][x > v[u]];
        }
        if (!u) {
            u = ++tot;
            siz[u] = cnt[u] = 1;
            v[u] = sum[u] = x;
            fa[u] = f;
            if (f)c[f][x > v[f]] = u;
        }
        splay(u, 0);
    }
    int find(int x, ll k) {
        k += INF;
        int ans = 0;
        while (1) {
            if (sum[c[x][1]] >= k)x = c[x][1];
            else if (sum[c[x][1]] + v[x] * cnt[x] < k)k -= sum[c[x][1]] + v[x] * cnt[x], ans += siz[c[x][1]] + cnt[x], x = c[x][0];
            else return ans += (k - sum[c[x][1]] + v[x] - 1) / v[x] + siz[c[x][1]];
        }
    }
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> t;
    while (t--) {
        cin >> n >> m;
        Rin::init();
        Rin::insert(0);
        Rin::insert(INF);
        ll sum = 0;
        for (int i = 1; i <= n; i++) {
            cin >> a;
            sum += a;
            if (sum <= m) {
                cout << "0" << " ";
                Rin::insert(a);
                continue;
            }
            cout << Rin::find(Rin::rt, sum - m) - 1 << " ";
            Rin::insert(a);
        }
        cout << '\n';
    }
    return 0;
}

Game

题目问的是先手必赢的情况,我们知道先手必输的情况就是区间中所有数的异或和为0,由于这个比较好求,所以可以将问题稍微转换一下,即求区间中异或和为0的个数。
因为多个询问并且不强制在线,并且一个数的贡献比较好算,那就考虑莫队了。2操作也只会影响一个位置的前缀异或和,也算是比较好写的。
注意一下区间变成了\([l-1,r]\),结合一下前缀异或和理解一下就行了。
代码如下:

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
int a[N], sum[N], upd[N];
ll cnt[(1 << 21) + 5];
int n, m, block;
int l, r, t;
ll ans;
struct query{
    int id, l, r, k;
    ll ans;
}q[N];
int blo(int x) {
    return (x - 1) / block;
}
bool cmp(query A, query B) {
    if(blo(A.l) == blo(B.l) && blo(A.r) == blo(B.r)) return blo(A.k) < blo(B.k);
    if(blo(A.l) == blo(B.l)) return blo(A.r) < blo(B.r);
    return blo(A.l) < blo(B.l);
}
void add(int p, int v) {
    if(v == 1) ans += cnt[sum[p]]++;
    else ans -= --cnt[sum[p]];
}
void Update(int pos) {
    if(l <= pos && pos <= r) add(pos, -1);
    sum[pos] ^= a[pos];
    swap(a[pos], a[pos + 1]);
    sum[pos] ^= a[pos];
    if(l <= pos && pos <= r) add(pos, 1);
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    while(cin >> n >> m) {
        memset(cnt, 0, sizeof(cnt));
        block = (int)pow(n, 0.66666);
        for(int i = 1; i <= n; i++) cin >> a[i], sum[i] = sum[i - 1] ^ a[i];
        int tot = 0, num = 0;
        for(int i = 1; i <= m; i++) {
            int op; cin >> op;
            if(op == 1) {
                int l, r; cin >> l >> r; ;
                q[++tot].id = tot;
                q[tot].l = l - 1; q[tot].r = r;
                q[tot].k = num;
            } else {
                int p; cin >> p;
                upd[++num] = p;
            }
        }
        sort(q + 1, q + tot + 1, cmp);
        l = t = 0; r = -1; ans = 0;
        for(int i = 1; i <= tot; i++) {
            
            for(; r < q[i].r; r++) add(r + 1, 1);
            for(; r > q[i].r; r--) add(r, -1);
            for(; l < q[i].l; l++) add(l, -1);
            for(; l > q[i].l; l--) add(l - 1, 1);
for(; t < q[i].k; t++) Update(upd[t + 1]);
            for(; t > q[i].k; t--) Update(upd[t]);
            q[q[i].id].ans = 1ll * (r - l + 1) * (r - l) / 2 - ans;
        }
        for(int i = 1; i <= tot; i++) cout << q[i].ans << '\n';
    }
    return 0;
}

K Subsequence

网络流的一个经典应用。

Code
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int N = 2015;
struct edge {
    int to, capacity, cost, rev;
    edge() {}
    edge(int to, int _capacity, int _cost, int _rev) :to(to), capacity(_capacity), cost(_cost), rev(_rev) {}
};
struct Min_Cost_Max_Flow {
    int V, H[N << 1], dis[N << 1], PreV[N << 1], PreE[N << 1];
    vector<edge> G[N << 1];
    void Init(int n) {
        V = n;
        for (int i = 0; i <= V; ++i)G[i].clear();
    }
    void Add_Edge(int from, int to, int cap, int cost) {
        G[from].push_back(edge(to, cap, cost, G[to].size()));
        G[to].push_back(edge(from, 0, -cost, G[from].size() - 1));
    }
//flow是自己传进去的变量,就是最后的最大流,返回的是最小费用,f=INF
    int Min_cost_max_flow(int s, int t, int f, int& flow) {
        int res = 0; fill(H, H + 1 + V, 0);
        while (f) {
            priority_queue <pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>> > q;
            fill(dis, dis + 1 + V, INF);
            dis[s] = 0; q.push(pair<int, int>(0, s));
            while (!q.empty()) {
                pair<int, int> now = q.top(); q.pop();
                int v = now.second;
                if (dis[v] < now.first)continue;
                for (int i = 0; i < G[v].size(); ++i) {
                    edge& e = G[v][i];
                    if (e.capacity > 0 && dis[e.to] > dis[v] + e.cost + H[v] - H[e.to]) {
                        dis[e.to] = dis[v] + e.cost + H[v] - H[e.to];
                        PreV[e.to] = v;
                        PreE[e.to] = i;
                        q.push(pair<int, int>(dis[e.to], e.to));
                    }
                }
            }
            if (dis[t] == INF)break;
            for (int i = 0; i <= V; ++i)H[i] += dis[i];
            int d = f;
            for (int v = t; v != s; v = PreV[v])d = min(d, G[PreV[v]][PreE[v]].capacity);
            f -= d; flow += d; res += d*H[t];
            for (int v = t; v != s; v = PreV[v]) {
                edge& e = G[PreV[v]][PreE[v]];
                e.capacity -= d;
                G[v][e.rev].capacity += d;
            }
        }
        return res;
    }
    int Max_cost_max_flow(int s, int t, int f, int& flow) {
        int res = 0;
        fill(H, H + 1 + V, 0);
        while (f) {
            priority_queue <pair<int, int>> q;
            fill(dis, dis + 1 + V, -INF);
            dis[s] = 0;
            q.push(pair<int, int>(0, s));
            while (!q.empty()) {
                pair<int, int> now = q.top(); q.pop();
                int v = now.second;
                if (dis[v] > now.first)continue;
                for (int i = 0; i < G[v].size(); ++i) {
                    edge& e = G[v][i];
                    if (e.capacity > 0 && dis[e.to] < dis[v] + e.cost + H[v] - H[e.to]) {
                        dis[e.to] = dis[v] + e.cost + H[v] - H[e.to];
                        PreV[e.to] = v;
                        PreE[e.to] = i;
                        q.push(pair<int, int>(dis[e.to], e.to));
                    }
                }
            }
            if (dis[t] == -INF)break;
            for (int i = 0; i <= V; ++i)H[i] += dis[i];
            int d = f;
            for (int v = t; v != s; v = PreV[v])d = min(d, G[PreV[v]][PreE[v]].capacity);
            f -= d; flow += d;
            res += d*H[t];
            for (int v = t; v != s; v = PreV[v]) {
                edge& e = G[PreV[v]][PreE[v]];
                e.capacity -= d;
                G[v][e.rev].capacity += d;
            }
        }
        return res;
    }
}sol;
int T, n, k;
int a[N];
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> T;
    while(T--) {
        cin >> n >> k;
        for(int i = 1; i <= n; i++) cin >> a[i];
        int s1 = 0, s2 = 2 * n + 1, t = 2 * n + 2;
        sol.Init(t);
        sol.Add_Edge(s1, s2, k, 0);
        for(int i = 1; i <= n; i++) {
            sol.Add_Edge(i, i + n, 1, -a[i]);
        }
        for(int i = 1; i <= n; i++) {
            for(int j = i + 1; j <= n; j++) {
                if(a[j] >= a[i]) sol.Add_Edge(i + n, j, 1, 0);
            }
        }
        for(int i = 1; i <= n; i++) {
            sol.Add_Edge(s2, i, INF, 0);
            sol.Add_Edge(i + n, t, INF, 0);
        }
        int flow = 0;
        cout << -sol.Min_cost_max_flow(s1, t, INF, flow) << '\n';
    }
    return 0;
}

Squrirrel

考虑不删边的情况,那么我们就需要通过两次dfs来进行dp,并且要维护子树内最远距离和次远距离以及从父亲那边的最远距离。
因为在这种题目中,维护最大距离时,肯定是优先考虑最大的那几个的。
删边的话就再加一维dp状态就好了。并且还要维护第三大的距离。
代码里面的\(g\)数组就表示在子树中删边的最大、次大距离,\(f\)数组是不删边的情况,\(h\)数组就是从父亲那边转移过来时的最大距离,\(0/1\)分别代表不删边和删边。
详见代码:

Code
#include <bits/stdc++.h>
#define mp make_pair
#define fi first
#define se second
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 2e5 + 5;
int T, n;
struct Edge{
    int v, w, next;
}e[N << 1];
int head[N], tot;
void adde(int u, int v, int w) {
    e[tot].v = v; e[tot].w = w; e[tot].next = head[u]; head[u] = tot++;
}
pii f[N][3];
int g[N][2], h[N][2];
int d[N];
pii res;
void init() {
    for(int i = 1; i <= n; i++) {
        for(int j = 0; j < 2; j++) {
            f[i][j] = mp(0, 0);
            g[i][j] = g[i][j] = 0;
        }
        f[i][3] = mp(0, 0);
    }
    res = mp(INF, INF);
}
void dfs(int u, int fa) {
    for(int i = head[u]; i != -1; i = e[i].next) {
        int v = e[i].v, w = e[i].w;
        if(v == fa) continue;
        d[v] = w;
        dfs(v, u);
        if(f[v][0].fi + w > f[u][0].fi) {
            f[u][2] = f[u][1];
            f[u][1] = f[u][0];
            f[u][0].fi = f[v][0].fi + w;
            f[u][0].se = v;
        } else if(f[v][0].fi + w > f[u][1].fi) {
            f[u][2] = f[u][1];
            f[u][1].fi = f[v][0].fi + w;
            f[u][1].se = v;
        } else if(f[v][0].fi + w > f[u][2].fi) {
            f[u][2].fi = f[v][0].fi + w;
            f[u][2].se = v;
        }
    }
    int v0 = f[u][0].se, v1 = f[u][1].se;
    g[u][0] = min(f[v0][0].fi, max(f[v0][1].fi, g[v0][0]) + d[v0]);
    g[u][1] = min(f[v1][0].fi, max(f[v1][1].fi, g[v1][0]) + d[v1]);
}
void dfs2(int u, int fa) {
    pii tmp = mp(min(max(h[u][0], max(g[u][0], f[u][1].fi)), max(h[u][1], f[u][0].fi)), u);
    res = min(res, tmp);
    for(int i = head[u]; i != -1; i = e[i].next) {
        int v = e[i].v;
        if(v == fa) continue ;
        if(v == f[u][0].se) {
            h[v][0] = max(h[u][0], f[u][1].fi) + d[v];
            h[v][1] = max(h[u][1], f[u][1].fi) + d[v];
            h[v][1] = min(h[v][1], max(h[u][0], f[u][1].fi));
            h[v][1] = min(h[v][1], max(max(g[u][1], h[u][0]), f[u][2].fi) + d[v]);
        } else {
            h[v][0] = max(h[u][0], f[u][0].fi) + d[v];
            h[v][1] = max(h[u][1], f[u][0].fi) + d[v];
            if(v == f[u][1].se) h[v][1] = min(h[v][1], max(max(h[u][0], g[u][0]), f[u][2].fi) + d[v]);
            else h[v][1] = min(h[v][1], max(max(h[u][0], g[u][0]), f[u][1].fi) + d[v]);
            h[v][1] = min(h[v][1], max(f[u][0].fi, h[u][0]));
        }
        dfs2(v, u);
    }
}
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> T;
    while(T--) {
        cin >> n;
        memset(head, -1, sizeof(head)); tot = 0;
        for(int i = 1; i < n; i++) {
            int u, v, w;
            cin >> u >> v >> w;
            adde(u, v, w); adde(v, u, w);
        }
        init();
        dfs(1, -1);
        dfs2(1, -1);
        cout << res.se << ' ' << res.fi << '\n';
    }
    return 0;
}
posted @ 2019-08-02 08:57  heyuhhh  阅读(290)  评论(0编辑  收藏  举报