跳转至

250813 模拟赛

T1 largenumber

给定一个长度为 \(n\)\(n\) 是奇数)的序列,你可以进行 \(\frac{n-1}2\) 次操作,将序列删为只剩一个数。每次操作你选择序列中一个数字(不能是开头结尾),然后将它相邻两个数字删掉。一个数字被删掉后,两侧的数字变为相邻。操作的价值定义为你选择的数字大小。问操作价值最小值的最大值。

\(n\le 2\times 10^6,\ 1\le a_i\le 10^9\)

考虑二分答案,问题转化为:序列上一些数字被标记为 \(1\),你只能对标记为 \(1\) 的位置进行操作,问能否将所有 \(0\) 都删去。

不断操作序列中心的位置是平凡的,先特判掉。我们考虑经典思路:一次操作最优删掉两个 \(0\),而一次操作不可能同时删去同一个连续段内的两个 \(0\),因此操作总次数不小于最长 \(0\) 连续段,因此 \(0\) 连续段的长度不能超过最多操作次数 \(\frac{n-3}2\)\(1\) 的数量大于一个,将连续段删空之后至少还要再删一次,所以要减一)。

尝试构造并证明上面的条件是充要的。假设最后一次操作的数字在初始时位于下标 \(x\),不妨设 \(x\) 在序列左半段。如果我们能删去 \([x+1,n]\) 中的 \(n-2x-1\) 个数字,那么然后我们只需要一直操作 \(x\) 即可。假设我们在右边区间内一直操作一个下标 \(y\),那么 \(y\) 需要满足 \(y\ge x+\frac{n-2x+1}2\)\(y\le n-\frac{n-2x-1}2\)。其中第一个条件等价于 \(y\) 要在序列的右半区间内,第二个条件等价于 \(y\)\(x\) 之间的间隔不超过 \(\frac{n-3}{2}\)。两个条件等价于序列中点所在的连续段长度不超过 \(\frac{n-3}{2}\)

最长连续段是容易 \(O(n)\) 求出的。时间复杂度 \(O(n\log V)\)

代码
#include<iostream>
using namespace std;
const int N = 2e6 + 10;

int T, n;
int a[N];

bool check(int mid) {
    int pre = 0;
    for(int i = 1; i <= n + 1; i++) {
        if(a[i] < mid) continue;
        if(i - pre - 1 > (n - 3) / 2) return false;
        pre = i;
    }
    return true;
}

int main() {

    freopen("largenumber.in", "r", stdin);
    freopen("largenumber.out", "w", stdout);

    ios::sync_with_stdio(0);
    cin.tie(0);

    cin >> T;
    while(T--) {
        cin >> n;
        for(int i = 1; i <= n; i++) cin >> a[i];
        a[n + 1] = 1e9 + 20;
        int l = 1, r = 1e9 + 10;
        while(l < r) {
            int mid = (l + r + 1) >> 1;
            if(check(mid)) l = mid;
            else r = mid - 1;
        }
        l = max(l, a[(n + 1) / 2]);
        cout << l << '\n';
    }

    return 0;
}

T2

状压板子,没动脑子写了个 \(32\times nm\) 的做法,据说可以 \(4\times nm\)

T3 walk

给定一个简单无向图和其上的一个节点 \(t\),请你判定它是否存在一个子图(原图点集和边集各选出一个子集,边集必须是导出子图边集的子集)满足:

  • \(t\) 相连的边都在该子图内;
  • 该图存在一条欧拉路径满足 \(t\) 是起点(终点);

\(n,m\le 10^6\)

先把原图上和 \(t\) 不在同一连通块的节点删掉。分讨 \(t\) 度数的奇偶性。若 \(deg[t]\) 是偶数,等价于需要把 \(t\) 的邻居两两匹配,满足匹配双方存在一条简单路径(不能经过 \(t\)),并且路径之间没有公共边。

发现如果 \(t\) 的一个儿子连通块中包含奇数个 \(t\) 的邻居,那么无解。剩下的构造部分其实很容易,考虑每个儿子连通块的一棵生成树,然后任意匹配连通块内 \(t\) 的邻居,将匹配双方路径上的边全都 flip 即可。

\(deg[t]\) 是奇数,任意选择一个包含奇数邻居的连通块,然后再任选其中的一个邻居作为起点,删掉它,就和偶数的情况一样了。

代码
#include<iostream>
using namespace std;
const int N = 1e6 + 10;

struct Edge {
    int v, next;
} pool[2 * N];
int ne, head[N];

void addEdge(int u, int v) {
    pool[++ne] = {v, head[u]};
    head[u] = ne;
}

int T;
int n, m, s, t;
int tag[N], cnt;
int d[N], vis[N];
int ans[N], top;

bool flag;

namespace G2 {
    Edge pool[2 * N];
    int ne, head[N];
    void addEdge(int u, int v) {
        pool[++ne] = {v, head[u]};
        head[u] = ne;
    }
    void clear() {
        for(int i = 0; i <= n; i++) head[i] = 0;
        ne = 1;
    }
}

void clear() {
    for(int i = 0; i <= n + 5; i++) head[i] = tag[i] = d[i] = vis[i] = 0;
    ne = cnt = 0;
    flag = true;
    G2::clear();
}

int dfs1(int u) {
    vis[u] = 1;
    int res = tag[u];
    for(int e = head[u]; e; e = pool[e].next) {
        int v = pool[e].v;
        if(vis[v] || v == t) continue;
        res += dfs1(v);
    }
    return res;
}

void dfs2(int u, int fa) {
    vis[u] = 1;
    for(int e = head[u]; e; e = pool[e].next) {
        int v = pool[e].v;
        if(vis[v] || v == t) continue;
        dfs2(v, u);
        d[u] ^= d[v];
    }
    if(d[u]) {
        if(!fa) flag = false;
        else G2::addEdge(u, fa), G2::addEdge(fa, u);
    }
}

void dfs3(int u) {
    for(int e = G2::head[u]; e; e = G2::pool[e].next) {
        int v = G2::pool[e].v;
        if(!v) continue;
        G2::pool[e].v = 0;
        G2::pool[e ^ 1].v = 0;
        dfs3(v);
    }
    ans[++top] = u;
}

int main() {

    freopen("walk.in", "r", stdin);
    freopen("walk.out", "w", stdout);

    cin >> T;
    while(T--) {
        cin >> n >> m >> t;
        s = t;
        clear();
        for(int i = 1; i <= m; i++) {
            int u, v;
            cin >> u >> v;
            addEdge(u, v);
            addEdge(v, u);
        }
        for(int e = head[t]; e; e = pool[e].next) {
            int v = pool[e].v;
            G2::addEdge(t, v);
            G2::addEdge(v, t);
            tag[v] = d[v] = 1;
            ++cnt;
        }
        if(cnt & 1) {
            for(int e = head[t]; e; e = pool[e].next) {
                int v = pool[e].v;
                if(vis[v]) continue;
                if(dfs1(v) & 1) { s = v; break; }
            }
            d[s] = 0;
        }
        for(int i = 0; i <= n + 5; i++) vis[i] = 0;
        for(int e = head[t]; e && flag; e = pool[e].next) {
            int v = pool[e].v;
            if(!vis[v]) dfs2(v, 0);
        }
        if(!flag) { cout << -1 << '\n'; continue; }
        top = 0;
        dfs3(s);
        cout << top << ' ';
        for(int i = top; i >= 1; i--) cout << ans[i] << ' ';
        cout << '\n';
    }

    return 0;
}

T4

整体二分 / 树套树板子。multiseterase 又忘了套 find,挂成 85 分。