2018-2019 ACM-ICPC Brazil Subregional Programming Contest 补题

题目链接

https://codeforces.com/gym/101908

参考题解

B - Marbles

简要题意:

给定 n n n 个弹珠,第 i i i 个坐标为 ( r i , c i ) (r_i, c_i) (ri,ci) A B AB AB 两人轮流行动, A A A 先手,每次选择一个弹珠和一个正整数 u u u,将其移动到 ( r i − u , c i ) (r_i - u, c_i) (riu,ci) ( r i , c i − u ) (r_i, c_i - u) (ri,ciu) ( r i − u , c i − u ) (r_i - u, c_i - u) (riu,ciu) 的位置,最先将一个弹珠移动到 ( 0 , 0 ) (0, 0) (0,0) 的玩家获胜,问是否先手必胜。

解题思路:

若最开始就有弹珠在 r = c r = c r=c 位置,则先手必胜。若弹珠都在 ( 1 , 2 ) (1, 2) (1,2) ( 2 , 1 ) (2, 1) (2,1) 的位置,则为必败态,因为下一次行动不管移动哪个弹珠,就会让对方直接能获胜。故将一个弹珠获胜条件转化为全部弹珠不能移动则落败, s g sg sg 打表即可。

参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e2 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
 
const int xp[] = {0, -1, -1};
const int yp[] = {-1, 0, -1};
int sg[maxn][maxn];
int n;
 
void init(int n){
 
    sg[1][2] = sg[2][1] = 0;
    for(int i = 1; i <= n; ++i){
 
        for(int j = 1; j <= n; ++j){
 
            if(i == j) continue;
            set<int> vis;
            int lim = max(i, j);
            for(int k = 1; k <= lim; ++k){
 
                for(int o = 0; o < 3; ++o){
 
                    int x = i + k * xp[o], y = j + k * yp[o];
                    if(x < 1 || y < 1 || x == y) continue;
                    vis.emplace(sg[x][y]);
                }
            }
            for(int k = 0; ; ++k){
 
                if(!vis.count(k)) { sg[i][j] = k; break; }
            }
        }
    }
    // for(int i = 0; i <= 10; ++i){
 
    //     for(int j = 0; j <= 10; ++j){
 
    //         cout << sg[i][j] << " ";
    //     }
    //     cout << endl;
    // }
}
 
int main(){
 
    ios::sync_with_stdio(0); cin.tie(0);
    init(100);
    cin >> n;
    int ret = 0, flg = 0;
    for(int i = 1; i <= n; ++i){
 
        int r, c; cin >> r >> c;
        ret ^= sg[r][c];
        flg |= r == c;
    }
    if(flg) ret = 1;
    cout << (ret ? "Y" : "N") << endl;
    return 0;
}

C - Pizza Cutter

简要题意:

给定一张纸,有 n n n 刀从水平剪过、 m m m 刀从竖直方向剪,剪切痕迹不需要为直线、任意两刀最多一个交点,给定 n + m n + m n+m 次剪切在纸边缘的坐标,求最多将纸剪成几份。

解题思路:

对于水平的剪切 ( y i 1 , y i 2 ) (y_{i1}, y_{i2}) (yi1,yi2) ( y j 1 , y j 2 ) (y_{j1}, y_{j2}) (yj1,yj2),两者能相交仅当 y i 1 < y j 1 ∧ y i 2 > y j 2 y_{i1} \lt y_{j1} \land y_{i2} \gt y_{j2} yi1<yj1yi2>yj2,竖直方向同理,每交一次则多一块。树状数组维护即可。

参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
 
pii a[maxn], b[maxn];
int xi[maxn], cntX, yi[maxn], cntY;
int n, m;
 
struct BIT{
 
    int c[maxn], n;
    void init(int nn){
 
        n = nn;
        for(int i = 1; i <= n; ++i) c[i] = 0;
    }
    #define lowb(x) ((x)&(-(x)))
    void add(int x, int v){
 
        while(x <= n) c[x] += v, x += lowb(x);
    }
    int sum(int x){
 
        int ret = 0;
        while(x > 0) ret += c[x], x -= lowb(x);
        return ret;
    }
} bit;
 
int main(){
 
    ios::sync_with_stdio(0); cin.tie(0);
    int X, Y; cin >> X >> Y;
    cin >> n >> m;
    for(int i = 1; i <= n; ++i){
 
        cin >> a[i].first >> a[i].second;
        yi[++cntY] = a[i].second;
    }
    for(int i = 1; i <= m; ++i){
 
        cin >> b[i].first >> b[i].second;
        xi[++cntX] = b[i].second;
    }
    sort(a + 1, a + 1 + n, greater<pii>());
    sort(b + 1, b + 1 + m, greater<pii>());
    sort(yi + 1, yi + 1 + cntY);
    sort(xi + 1, xi + 1 + cntX);
    cntY = unique(yi + 1, yi + 1 + cntY) - yi - 1;
    cntX = unique(xi + 1, xi + 1 + cntX) - xi - 1;
    ll ret = 1;
    bit.init(cntY);
    for(int i = 1; i <= n; ++i){
 
        int y = lower_bound(yi + 1, yi + 1 + cntY, a[i].second) - yi;
        ret += bit.sum(y - 1) + 1;
        bit.add(y, 1);
    }
    bit.init(cntX);
    for(int i = 1; i <= m; ++i){
 
        int x = lower_bound(xi + 1, xi + 1 + cntX, b[i].second) - xi;
        ret += bit.sum(x - 1) + n + 1;
        bit.add(x, 1);
    }
    cout << ret << endl;
    return 0;
}

D - Unraveling Monty Hall

简要题意:

签到题。

解题思路:

签到题。

参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
 
int main(){
 
    ios::sync_with_stdio(0); cin.tie(0);
    int n; cin >> n;
    int ret = 0;
    for(int i = 1; i <= n; ++i){
 
        int x; cin >> x;
        if(x != 1) ++ret;
    }
    cout << ret << endl;
    return 0;
}

E - Enigma

简要题意:

签到题。

解题思路:

暴力即可。

参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
 
char s[maxn], t[maxn];
int n, m;
 
int main(){
 
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> s + 1 >> t + 1;
    n = strlen(s + 1);
    m = strlen(t + 1);
    int ret = 0;
    for(int i = m; i <= n; ++i){
 
        int j = 1;
        while(j <= m && s[i - m + j] != t[j]) ++j;
        ret += j > m;
    }
    cout << ret << endl;
    return 0;
}

F - Music Festival

简要题意:

音乐节有 n n n 个阶段,每阶段有若干艺术家在时间段 [ i j , f j ] [i_j, f_j] [ij,fj] 演出,并有数据 o j o_j oj 表示用户听过该艺术节的 o j o_j oj 首歌曲,现在要为用户规划观看哪些艺术家的演出,满足每个阶段至少观看一个节目、节目时间不冲突的情况下,使得 ∑ o j \sum o_j oj 最大。

解题思路:

状压 d p dp dp,先将时间离散化,让 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示 j j j 状态下,前 i i i 时间内的最大权值,转移详见代码。

参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e3 + 25;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
 
struct Node{
    int l, r, w, c;
} a[maxn];
vector<Node> G[maxn << 1];
int xi[maxn << 1], dp[maxn << 1][maxn];
int n, m, cntX;
 
int main(){
 
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n;
    for(int i = 0; i < n; ++i){
 
        int ki; cin >> ki;
        while(ki--){
 
            int l, r, w; cin >> l >> r >> w;
            xi[++cntX] = l, xi[++cntX] = r;
            a[++m] = Node{l, r, w, i};
        }
    }
    sort(xi + 1, xi + 1 + cntX);
    cntX = unique(xi + 1, xi + 1 + cntX) - xi - 1;
    for(int i = 1; i <= m; ++i){
 
        a[i].l = lower_bound(xi + 1, xi + 1 + cntX, a[i].l) - xi;
        a[i].r = lower_bound(xi + 1, xi + 1 + cntX, a[i].r) - xi;
        G[a[i].r].pb(a[i]);
    }
    int lim = (1 << n) - 1;
    for(int i = 0; i <= cntX; ++i){
 
        for(int j = 0; j <= lim; ++j) dp[i][j] = -inf;
    }
    dp[0][0] = 0;
    for(int i = 1; i <= cntX; ++i){
 
        memcpy(dp[i], dp[i - 1], sizeof dp[i - 1]);
        for(auto &e : G[i]){
 
            for(int j = 0; j <= lim; ++j){
 
                dp[i][j | (1 << e.c)] = max(dp[i][j | (1 << e.c)], dp[e.l][j] + e.w);
            }
        }
    }
    int ret = -1;
    for(int i = 1; i <= cntX; ++i){
 
        ret = max(ret, dp[i][lim]);
    }
    cout << ret << endl;
    return 0;
}

G - Gasoline

简要题意:

P P P 个加油站,每个需要 D i D_i Di 的燃料, R R R 个炼油厂,每个有 E i E_i Ei 的库存,已知有 C C C 个供应路径,最短时间已知,假设卡车无限多、容量无限大。问能否供应全部加油站,若能,求出最短时间。

解题思路:

二分答案,使用所有小于等于时间 t t t 的边建图,网络流经典建模。

参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
 
struct Edge{
    int u, v, w;
} ei[maxn];
int D[maxn], E[maxn];
int P, R, C;
 
struct Dinic{
    struct Edge{
        int v, rev, cap;
    };
    vector<Edge> G[maxn];
    int dis[maxn], cur[maxn], n, sp, tp;
    void init(int nn){
 
        n = nn;
        for(int i = 1; i <= n; ++i) G[i].clear();
    }
    void add(int u, int v, int cap, int rcap = 0){
 
        G[u].pb(Edge{v, sz(G[v]), cap});
        G[v].pb(Edge{u, sz(G[u]) - 1, rcap});
    }
    int bfs(){
 
        queue<int> q;
        for(int i = 1; i <= n; ++i) dis[i] = 0;
        dis[sp] = 1, q.push(sp);
        while(!q.empty()){
 
            int u = q.front(); q.pop();
            for(auto &e : G[u]){
 
                if(e.cap && !dis[e.v]){
 
                    dis[e.v] = dis[u] + 1;
                    if(e.v == tp) return 1;
                    q.push(e.v);
                }
            }
        }
        return 0;
    }
    int dfs(int u, int flow){
 
        if(u == tp || !flow) return flow;
        int ret = 0, tmp;
        for(int &i = cur[u]; i < sz(G[u]); ++i){
 
            auto &e = G[u][i];
            if(dis[e.v] == dis[u] + 1 && (tmp = dfs(e.v, min(e.cap, flow - ret)))){
 
                e.cap -= tmp, G[e.v][e.rev].cap += tmp, ret += tmp;
                if(ret == flow) return ret;
            }
        }
        if(!ret) dis[u] = 0;
        return ret;
    }
    int solve(int s, int t){
 
        sp = s, tp = t;
        int ret = 0;
        while(bfs()){
 
            for(int i = 1; i <= n; ++i) cur[i] = 0;
            ret += dfs(sp, inf);
        }
        return ret;
    }
} dn;
 
int judge(int mid){
 
    int S = P + R + 1, T = S + 1;
    dn.init(T);
    for(int i = 1; i <= R; ++i){
 
        dn.add(S, i, E[i]);
    }
    int sum = 0;
    for(int i = 1; i <= P; ++i){
 
        dn.add(R + i, T, D[i]);
        sum += D[i];
    }
    for(int i = 1; i <= C; ++i){
 
        if(ei[i].w <= mid) dn.add(ei[i].u, R + ei[i].v, inf);
    }
    return dn.solve(S, T) == sum;
}
 
int main(){
 
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> P >> R >> C;
    for(int i = 1; i <= P; ++i) cin >> D[i];
    for(int i = 1; i <= R; ++i) cin >> E[i];
    for(int i = 1; i <= C; ++i) cin >> ei[i].v >> ei[i].u >> ei[i].w;
    int l = 1, r = 1000000, mid, ret = -1;
    while(l <= r){
 
        mid = gmid;
        if(judge(mid)) ret = mid, r = mid - 1;
        else l = mid + 1;
    }
    cout << ret << endl;
    return 0;
}

I - Switches

简要题意:

m m m 盏灯, n n n 个开关,每个开关控制若干盏灯,拨动开关改变灯的明暗情况。最初有若干灯亮着,问依次拨动第 1 1 1 n n n 个开关,如此循环,最少需要多少次拨动能让灯全暗,无解输出 − 1 -1 1

解题思路:

拨动 2 n 2n 2n 次则回到初始状态,故枚举 1 1 1 2 n − 1 2n - 1 2n1 次拨动,判断是否全暗即可。

参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e3 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
 
bitset<maxn> a[maxn], b;
int n, m;
 
int main(){
 
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n >> m;
    int L; cin >> L;
    for(int i = 1; i <= L; ++i){
 
        int x; cin >> x;
        b[x] = 1;
    }
    for(int i = 1; i <= n; ++i){
 
        int L; cin >> L;
        while(L--){
 
            int x; cin >> x;
            a[i][x] = 1;
        }
    }
    int ret = -1;
    for(int i = 1; i < 2 * n; ++i){
 
        int k = i <= n ? i : i - n;
        b ^= a[k];
        if(!b.count()) { ret = i; break; }
    }
    cout << ret << endl;
    return 0;
}

J - Joining Capitals

简要题意:

给定 n n n 个城市,其中有 k k k 个首都,城市间边权为欧氏距离,问在 k k k 个首都只有一个直接邻接城市的情况下,将 k k k 个首都连通的最小边权和。

解题思路:

最小斯坦纳树,改变转移方程使得 k k k 个首都不成为根。

参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e2 + 5;
const int mod = 1e9 + 7;
// const int inf = 0x3f3f3f3f;
const double inf = 1e20;
 
#define eps 1e-7
pii a[maxn];
double wi[maxn][maxn], f[1 << 10][maxn]; 
int vis[maxn];
int n, k;
 
void spfa(double dis[]){
 
    queue<int> q;
    for(int i = 0; i < n; ++i) if(dis[i] < inf + eps) q.push(i), vis[i] = 1;
    while(!q.empty()){
 
        int u = q.front(); q.pop(); vis[u] = 0;
        for(int v = 0; v < n; ++v){
 
            if(u == v) continue;
            if(dis[v] > dis[u] + wi[u][v] + eps){
 
                dis[v] = dis[u] + wi[u][v];
                if(!vis[v]) q.push(v), vis[v] = 1;
            }
        }
    }
}
 
#define sqr(x) ((x)*(x))
double solve(){
 
    for(int i = 0; i < n; ++i){
 
        for(int j = i + 1; j < n; ++j){
 
            wi[i][j] = wi[j][i] = sqrt(sqr(a[i].first - a[j].first) + sqr(a[i].second - a[j].second));
        }
    }
    int lim = (1 << k) - 1;
    for(int i = 0; i <= lim; ++i){
        
        for(int j = 0; j < n; ++j) f[i][j] = inf;
    }
    for(int i = 0; i < k; ++i) f[1 << i][i] = 0;
    for(int i = 1; i <= lim; ++i){
 
        for(int j = k; j < n; ++j){
 
            for(int s = (i - 1) & i; s; s = (s - 1) & i){
 
                if(f[s][j] >= inf - eps || f[i ^ s][j] >= inf - eps) continue;
                if(f[i][j] > f[s][j] + f[i ^ s][j] + eps) f[i][j] = f[s][j] + f[i ^ s][j];
            }
        }
        spfa(f[i]);
    }
    double ret = inf;
    for(int i = k; i < n; ++i) ret = min(ret, f[lim][i]);
    return ret;
}
 
int main(){
 
    // ios::sync_with_stdio(0); cin.tie(0);
    scanf("%d%d", &n, &k);
    for(int i = 0; i < n; ++i){
 
        scanf("%d%d", &a[i].first, &a[i].second);
    }
    double ret = solve();
    printf("%.5f\n", ret);
    return 0;
}

L - Subway Lines

简要题意:

给定一棵 n n n 个结点的树, q q q 次询问,每次给出 a , b , c , d a, b, c, d a,b,c,d,问 ( a , b ) (a, b) (a,b) ( c , d ) (c, d) (c,d) 的公共结点个数。

解题思路:

分类讨论,利用 L C A LCA LCA 和深度信息可解。或者树剖做法,将 ( a , b ) (a, b) (a,b) 间点权 + 1 +1 +1,再查询 ( c , d ) (c, d) (c,d) 的点权和。或者虚树做法,将关键点提取,在虚树上暴力求解(需要特别注意深度最小的公共点的点权)。这里提供虚树做法。

参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
 
vector<int> G[maxn], P[maxn], pi;
int dfn[maxn], fa[maxn][21], dep[maxn], stk[maxn], par[maxn], dt[maxn];
int n, q, tim, top, ans;
 
void dfs(int u, int f){
 
    dep[u] = dep[f] + 1, dfn[u] = ++tim;
    for(auto &v : G[u]){
 
        if(v == f) continue;
        fa[v][0] = u;
        for(int i = 1; i <= 20; ++i) fa[v][i] = fa[fa[v][i - 1]][i - 1];
        dfs(v, u);
    }
}
 
int lca(int u, int v){
 
    if(dep[u] < dep[v]) swap(u, v);
    for(int i = 20; i >= 0; --i){
 
        if(dep[fa[u][i]] >= dep[v]) u = fa[u][i];
    }
    if(u == v) return u;
    for(int i = 20; i >= 0; --i){
 
        if(fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
    }
    return fa[u][0];
}
 
void link(int u, int v){
 
    P[u].pb(v), par[v] = u;
}
 
int build(){
 
    sort(pi.begin(), pi.end(), [](int x, int y){ return dfn[x] < dfn[y]; });
    pi.erase(unique(pi.begin(), pi.end()), pi.end());
    stk[top = 1] = pi[0], pi.erase(pi.begin());
    for(auto &v : pi){
 
        int lc = lca(stk[top], v);
        while(dep[lc] < dep[stk[top - 1]]) link(stk[top - 1], stk[top]), --top;
        if(lc != stk[top]){
 
            link(lc, stk[top]), --top;
            if(lc != stk[top]) stk[++top] = lc;
        }
        stk[++top] = v;
    }
    while(--top) link(stk[top], stk[top + 1]);
    return stk[1];
}
 
void cal(int u){
 
    for(auto &v : P[u]){
 
        cal(v);
        dt[u] += dt[v];
    }
    ans += dt[u] == 2 ? dep[u] - dep[par[u]] : 0;
}
 
void clear(int u){
 
    for(auto &v : P[u]){
 
        clear(v);
    }
    dt[u] = 0, P[u].clear();
}
 
int main(){
 
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n >> q;
    for(int i = 1; i < n; ++i){
 
        int u, v; cin >> u >> v;
        G[u].pb(v), G[v].pb(u);
    }
    dfs(1, 0);
    while(q--){
 
        int u, v, x, y; cin >> u >> v >> x >> y;
        pi.pb(1), pi.pb(u), pi.pb(v), pi.pb(x), pi.pb(y);
        auto add = [](int u, int v){
            int lc = lca(u, v); pi.pb(lc);
            if(lc != 1) pi.pb(fa[lc][0]);
        };
        add(u, v), add(u, x), add(u, y);
        add(v, x), add(v, y), add(x, y);
        int rt = build();
        int lc1 = lca(u, v), lc2 = lca(x, y);
        // cout << lc1 << " xxx " << lc2 << endl;
        ++dt[u], ++dt[v], --dt[lc1], --dt[par[lc1]];
        ++dt[x], ++dt[y], --dt[lc2], --dt[par[lc2]];
        ans = 0, cal(rt), clear(rt), pi.clear();
        cout << ans << "\n";
    }
    return 0;
}

 
另一种构建虚树的方法,只加入关键点。最终交出来的链在虚树上的端点为深度最大的两个点,故不需要显式建树,顺便还能求出交集的两个端点。

#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
 
vector<int> G[maxn];
int dfn[maxn], fa[maxn][21], dep[maxn];
int n, q, tim;
 
void dfs(int u, int f){
 
    dep[u] = dep[f] + 1, dfn[u] = ++tim;
    for(auto &v : G[u]){
 
        if(v == f) continue;
        fa[v][0] = u;
        for(int i = 1; i <= 20; ++i) fa[v][i] = fa[fa[v][i - 1]][i - 1];
        dfs(v, u);
    }
}
 
int lca(int u, int v){
 
    if(dep[u] < dep[v]) swap(u, v);
    for(int i = 20; i >= 0; --i){
 
        if(dep[fa[u][i]] >= dep[v]) u = fa[u][i];
    }
    if(u == v) return u;
    for(int i = 20; i >= 0; --i){
 
        if(fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
    }
    return fa[u][0];
}

int getDis(int u, int v){
    
    return dep[u] + dep[v] - 2 * dep[lca(u, v)];
}

int onPath(int x, int u, int v){

    return getDis(u, v) == getDis(u, x) + getDis(v, x);
}

struct Chain{
    
    int u, v;
    friend Chain operator & (const Chain &a, const Chain &b){

        vector<int> vc = {a.u, a.v, b.u, b.v};
        auto cmp = [](int x, int y){ return dfn[x] < dfn[y]; };
        sort(vc.begin(), vc.end(), cmp);
        vector<int> pi = vc;
        for(int i = 1; i < sz(vc); ++i) pi.pb(lca(vc[i], vc[i - 1]));
        sort(pi.begin(), pi.end(), cmp);
        pi.erase(unique(pi.begin(), pi.end()), pi.end());
        int x = 0, y = 0;
        for(auto &v : pi){

            if(!onPath(v, a.u, a.v) || !onPath(v, b.u, b.v)) continue;
            if(dep[v] > dep[x]) y = x, x = v;
            else if(dep[v] > dep[y]) y = v;
        }
        if(x && !y) y = x;
        return Chain{x, y};
    }
};

int main(){

    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n >> q;
    for(int i = 1; i < n; ++i){
 
        int u, v; cin >> u >> v;
        G[u].pb(v), G[v].pb(u);
    }
    dfs(1, 0);
    while(q--){
 
        int u, v, x, y; cin >> u >> v >> x >> y;
        Chain ret = Chain{u, v} & Chain{x, y};
        if(!ret.u) cout << "0\n";
        else cout << getDis(ret.u, ret.v) + 1 << "\n";
    }
    return 0;
}

M - Modifying SAT

简要题意:

给定 m m m 个二元变量,再给出 n n n 个形如 X 1 ∧ X 2 ∧ ⋯ ∧ X X_1 \land X_2 \land\cdots\land X X1X2X 的合取式,其中每个子句 X i X_i Xi 有至多 3 3 3 个变量的,形如 x 1 ∨ x 2 ∨ ¬ x 3 x_1 \lor x_2 \lor \lnot x_3 x1x2¬x3 的析取式,每个子句 X i X_i Xi 中需要有一个或三个子项取值为真,问是否有解,有解则求字典序最大的解。

解题思路:

转化后即给定 n n n 个方程,每个方程形如 x 1 ⊕ x 2 ⊕ x 3 = 0 / 1 x_1 \oplus x_2 \oplus x_3 = 0/1 x1x2x3=0/1,异或高斯消元求解。若有解,由于字典序 T > F T \gt F T>F,令所有自由元取值为 T T T 得到一组特解,消元从第 m m m 列自右向左进行,可让主元尽可能靠后、自由元尽可能靠前,如此字典序最大。

参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 2e3 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
 
bitset<maxn> A[maxn];
int vis[maxn], n, m;
 
struct Gauss{
 
    int piv[maxn], fre[maxn], ans[maxn], tot;
    int solve(bitset<maxn> A[], int n, int m){
 
        int r = 0, c = 0; tot = 0;
        for( ;c < m && r < n; ++c, ++r){
 
            int mx = r;
            for(int i = r; i < n; ++i){
 
                if(A[i][c]) { mx = i; break; }
            }
            if(!A[mx][c]) { --r; fre[tot++] = c; continue; }
            piv[r] = c;
            swap(A[r], A[mx]);
            for(int i = 0; i < n; ++i){
 
                if(i == r || !A[i][c]) continue;
                A[i] ^= A[r];
            }
        }
        for(int i = r; i < n; ++i) if(A[i][m]) return -1;
        while(c < m) fre[tot++] = c++;
        for(int i = 0; i < tot; ++i) ans[fre[i]] = 1;
        for(int i = 0; i < r; ++i){
 
            for(int j = 0; j < tot; ++j) A[i][m] = A[i][m] ^ A[i][fre[j]];
            ans[piv[i]] = A[i][m];
        }
        return r;
    }
} gs;
 
vector<pii> read(){
 
    string s; getline(cin, s);
    vector<pii> vc;
    int pos = s.find('x');
    while(pos != string::npos){
 
        int flg = pos >= 2 && s[pos - 2] == 't';
        int val = 0; ++pos;
        while(isdigit(s[pos])) val = val * 10 + (s[pos] - '0'), ++pos;
        vc.pb(pii{flg, val});
        pos = s.find('x', pos);
    }
    return vc;
}
 
int main(){
 
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n >> m; cin.ignore();
    for(int i = 0; i < n; ++i){
 
        auto vc = read();
        A[i][m] = 1;
        for(auto &e : vc){
 
            A[i][m - e.second] = A[i][m - e.second] ^ 1;
            A[i][m] = A[i][m] ^ e.first;
            vis[m - e.second] = 1;
            // cout << e.first << " " << e.second << endl;
        }
    }
    int rk = gs.solve(A, n, m);
    if(rk == -1) cout << "impossible" << endl;
    else{
 
        for(int i = m - 1; i >= 0; --i){
 
            if(!vis[i]) cout << "T";
            else cout << (gs.ans[i] ? "T" : "F");
        }
        cout << endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值