Cf #434 Div.1 D Wizard's Tour [构造题]

W i z a r d ′ s T o u r Wizard's Tour WizardsTour


最 初 想 法 \color{blue}{最初想法}

考虑枚举每个边 ( u , v ) (u, v) (u,v), 设 u u u 连出的点中度数最小的点为 a a a, v v v 连出的点中度数最小的点为 b b b,
比较 u , a u, a u,a v , b v, b v,b 的度数和大小, 贪心地选取度数较小的点, 得到一个三元路径,
使用 m u l t i s e t multiset multiset 维护度数, 枚举边按以上方法找路径即可 .
代码纪念


正 解 部 分 \color{red}{正解部分}

先从图中剥离出一颗树, 然后 D F S DFS DFS, 从底向顶处理, 设当前节点为 i i i, 度数为 c n t i cnt_i cnti,

  • c n t i cnt_i cnti 为偶数 2 n 2n 2n , 则直接转化为 n n n 条路径 .
  • c n t i cnt_i cnti 为奇数 2 n − 1 2n-1 2n1, 则连向父亲的边保存, 其余构成 ( 2 n − 2 ) / 2 (2n-2)/2 (2n2)/2 条路径 .

这样构造, 可以使得最后至多留下一条边, 这条边是连向根节点的 .


实 现 部 分 \color{red}{实现部分}

D F S DFS DFS 到一个点 k k k 时, 其连出去的点包含以下类型 ↓ \downarrow

  • 儿子
  1. 使用了与父亲相连的边的儿子
  2. 没有使用 …
  • 父亲
  1. 需要使用与父亲相连的边
  2. 不需要 …
  • 非子非父
  1. 已经连过边
  2. 没有…

  • 注意根节点没有父亲 .
  • 可以使用类似网络流的建图方式删边 .
  • 使用标记数组 v i s [ ] vis[] vis[] 辅助建树 .
  • 使用标记数组 u s e f a [ ] use_fa[] usefa[] 记录是否使用了 f a fa fa 的边 .
#include<bits/stdc++.h>
#define reg register
#define pb push_back

int read(){
        char c;
        int s = 0, flag = 1;
        while((c=getchar()) && !isdigit(c))
                if(c == '-'){ flag = -1, c = getchar(); break ; }
        while(isdigit(c)) s = s*10 + c-'0', c = getchar();
        return s * flag;
}

const int maxn = 200005;

int N;
int M;
int num0;
int A_cnt;
int cnt[maxn];
int vis[maxn];
int head[maxn];
int Ans[maxn][3];
int use_fa[maxn];

std::vector <int> A[maxn];

struct Edge{ int nxt, to; } edge[maxn<<1];

void Add(int from, int to){
        edge[++ num0] = (Edge){ head[from], to };
        head[from] = num0;
}

void DFS(int k, int fa, int have_fa){ 
        vis[k] = 1;
        for(reg int i = head[k]; i; i = edge[i].nxt){
                int to = edge[i].to;
                if(!to) continue ;
                edge[i].to = edge[i^1].to = 0;
                if(!vis[to]){ 
                        DFS(to, k, 1);
                        if(!use_fa[to]) A[k].pb(to); // to使用了与父亲k相连的边
                }else A[k].pb(to);
        }
        if(have_fa) A[k].pb(fa);
        int size = A[k].size();
        if(size <= 1) return ;
        use_fa[k] = !(size & 1);
        for(reg int i = 0; i+1 < size; i += 2)
                Ans[++ A_cnt][0] = A[k][i], Ans[A_cnt][1] = k, Ans[A_cnt][2] = A[k][i+1]; 

}

int main(){
        N = read(), M = read();
        num0 = 1;
        for(reg int i = 1; i <= M; i ++){
                int u = read(), v = read();
                Add(u, v), Add(v, u);
        }
        for(reg int i = 1; i <= N; i ++) if(!vis[i]) DFS(i, 0, 0);
        printf("%d\n", A_cnt);
        for(reg int i = 1; i <= A_cnt; i ++) printf("%d %d %d\n", Ans[i][0], Ans[i][1], Ans[i][2]);
        return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值