CSP模拟23

电压、农民、奇迹树、暴雨

来自 \(\texttt{happyguy}\) 的馈赠。

A. 电压

我们考虑选一条边作为那条两边结点相同的边。

首先考虑,如果不选奇环上的边。奇环上的边一定有两端结点颜色相同的,所以如果图中有奇环,奇环上的边一定被选择。

考虑偶环,偶环上的边一定不能被选,选了的话偶环上的边也必定有一条左右结点颜色相同。

所以,如果没有奇环,那么除了偶环之外的边都能选。

如果有奇环,那么必须要选的是奇环上边的交集,并且还要保证这条边不在偶环上。

如果只有一个奇环,要记得把非树边加上。

最后树上差分求。

#include <bits/stdc++.h>

using namespace std;

const int N = 100500;

int n,m;

struct Edge{
    int next,to;
}e[N << 2];

int cnt = 1,h[N];

int root[N];

void Add(int u,int v) {
    cnt ++;
    e[cnt].next = h[u];
    h[u] = cnt;
    e[cnt].to = v;
}

bool vis[N];
int dep[N];

int sum[N][2];

int tot;// 奇环个数 

int fa[N];
// 这个点上面那条边的编号 

void dfs(int x) {
    vis[x] = 1;

    for(int i = h[x];i;i = e[i].next) {
        int to = e[i].to;

        if(i == fa[x] || (i ^ 1) == fa[x])
            continue;// 防止走回去 
        
        if(vis[to]) {
            // 非树边 
            if(dep[to] > dep[x])
                continue;
            
            int d = (dep[x] - dep[to]) & 1;
            // 判奇偶环 

            sum[x][d] ++;
            sum[to][d] --;

            if(d == 0)
                tot ++;
        }
        else {
            fa[to] = i;
            dep[to] = dep[x] + 1;
            dfs(to);
            
            sum[x][0] += sum[to][0];
            sum[x][1] += sum[to][1];
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin >> n >> m;

    for(int i = 1,u,v;i <= m; i++) {
        cin >> u >> v;
        Add(u,v);
        Add(v,u);
    }

    for(int i = 1;i <= n; i++) {
        if(!vis[i]) {
            root[i] = 1;
            dfs(i);
        }
    }

    int ans = 0;

    for(int i = 1;i <= n; i++)
        if(sum[i][0] == tot && !sum[i][1] && !root[i])
            ans ++;// 在所有奇环上 
    
    if(tot == 1)
        ans ++;
    cout << ans << "\n";
    return 0;
}

B. 农民

C. 奇迹树

题目大意

给定一棵 \(n\) 个节点的树,要求构造出一个点权序列 \(E\),满足以下三个条件:

  1. 所有 \(E_i\ge 1(1\le i\le n)\)
  2. 对于任意一组 \((i,j)(1 ≤ i < j ≤ N)\),使 \(|E_i-E_j|\geq \operatorname{dist}(i,j)\)\(\operatorname{dist}(i,j)\) 即树上 \(i\)\(j\) 两点距离。
  3. 使 \(E\) 中的最大值最小。

思路

首先只考虑前两个限制,设有点 \(i,j,k\) 满足

\[E_i < E_j < E_k \]

因为有

\[E_k-E_i=E_k-E_j+E_j-E_i \]

由于

\[E_k-E_j\geq \operatorname{dist}(k,j),E_j-E_i\geq \operatorname{dist}(i,j) \]

所以有

\[E_k-E_i \geq \operatorname{dist}(k,j) + \operatorname{dist}(i,j) \]

又因为

\[\operatorname{dist}(k,j) + \operatorname{dist}(i,j) \geq \operatorname{dist}(i,k) \]

使得 \(E_k-E_j\) 尽可能小,得到

\[E_k-E_i=\operatorname{dist}(k,i) \]

那么我们直接可以用欧拉序列进行构造,欧拉序列的长度为 \(2n - 1\)

再考虑第三个限制条件,让 \(E\) 中的最大值最小。

考虑我们欧拉序列有重复走的部分,我们使那个重复走的链的部分最短,那么那一段我们就可以选取树的直径。

那么我们怎么保证固定我们最后走哪个点呢?如果一条边在直径上,我们就最后经过这条边,这样在到达直径的第二个端点时,能够保证其他的点都已经遍历过。

#include <bits/stdc++.h>

using namespace std;

const int N = 200500;

vector<int> e[N];

int n;

int dis[N];

int EndPoint,End;

void dfs1(int x,int fa) {
    dis[x] = dis[fa] + 1;

    if(dis[x] > dis[EndPoint])
        EndPoint = x;
    
    for(auto const &to : e[x]) {
        if(to == fa)
            continue;
        
        dfs1(to,x);
    }
}

void GetCal() {
    dfs1(1,0);

    End = EndPoint;

    dfs1(EndPoint,0);
}

bool cal[N];

void dfs2(int x,int fa) {
    if(x == End)
        cal[x] = 1;
    for(auto const &to : e[x]) {
        if(to == fa)
            continue;
        
        dfs2(to,x);
        cal[x] |= cal[to];
    }
}

int ans[N],tot;

void dfs3(int x,int fa) {
    tot ++;
    ans[x] = tot;

    for(auto const &to : e[x]) {
        if(to == fa || cal[to])
            continue;
        
        dfs3(to,x);
        tot ++;
    }

    for(auto const &to : e[x]) {
        if(to == fa || !cal[to])
            continue;
        
        dfs3(to,x);
    }
}

int main() {
    cin >> n;

    for(int i = 1,u,v;i < n; i++) {
        cin >> u >> v;
        e[u].emplace_back(v);
        e[v].emplace_back(u);
    }

    GetCal();
    
    dfs2(EndPoint,0);
    dfs3(EndPoint,0);

    for(int i = 1;i <= n; i++) 
        cout << ans[i] << " ";
    
    cout << "\n";
    return 0;
}

D. 暴雨

posted @ 2023-08-17 20:13  -白简-  阅读(22)  评论(0编辑  收藏  举报