牛客网 【每日一题】4月23日题目精讲 边的染色

链接:

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 32768K,其他语言65536K
64bit IO Format: %lld

题目描述

小团有一张n个点,m条边的无向图G,有些边上已经被标记了0或1,表示它的边权。
现在你需要给剩下的边标记边权为0或1,求有几种标记的方式满足:
对于G中任意一个环,里面所有边的边权的异或值为0。
环的定义如下:
对于任意k(k≥2)个点{a1,a2,...,ak},若对于所有的i<k满足ai与ai+1之间有边,且a1=ak成立,则这k个点构成一个环。

输入描述:

第一行两个整数n, m。

接下来m行,每行3个整数u, v, w,表示有一条边(u,v),若w=-1则这条边上没有标记,否则w=0或1表示这条边上标记了w。

数据保证没有重边和自环。

1≤n≤100,000 0≤m≤min(n*(n-1)/2, 100000)

输出描述:

输出方案数对998,244,353取模的值。

示例1
输入
3 3
1 2 -1
2 3 -1
3 1 -1
输出
4

题解:

看了邓老师的题解,恍然大悟,感谢大佬讲解
一下为我结合邓老师所理解的:
首先思考:一个连通图(图中任意两点都是连通的),给边标上0和1,使得任意一个环的所有边的异或和为0,问有多少种方案?

异或:相同为0,不同为1

题目要求我们给边赋值,但是边赋值很不方便,我们就看看选择给点赋值,为什么?因为边和点是共存的,特别是在一个环中,每个点都贡献了两个边,那我们就可以把这个边的值当做是边两端顶点的值的异或,一个环有m个点,m条边,异或m条边就等于将m个点都异或两次。相同为0,那么异或两次相同的值结果一定为0.那点就可以随意赋值

对于一个连通块:
将所有点的数都 ^ 1(也就是0变成1,1变成0),两点异或出来的边值并未发生改变(就相当于原本1 ^ 1 改成0 ^ 0,结果不变),n个点每个点都是有两个方案的(即0或1),那么给点赋值的方案也就是2n个,对应的边赋值方案是点的方案除以2,所以n个点的连通图有2n-1种方案。

对于不是一个连通图:
我们可以求分散的每个连通图种类,然后乘一起就可

但是原本题目中是存在有些边一开始就赋好值,我们可以从提前标好的边开始出发找连通块,不然到最后你自己标记的很有可能和题目给你不适配,又要重新改。在存有题目给的边的这个连通块里,我们只需要确定一个点的权值,其他部分也可以因此确定。如果把这个连通块大小记作ki,那么这个连通块的存在就会使得方案数需要在原来的基础上(不考虑之前有标记)除以2 ki-1,因为其他点不能自由选。

还有:题目给你的数据本身可能就是矛盾,判定远直接输出0
我尽量只能理解到这份上。。

借鉴其他大佬的代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 3;
const int mod = 998244353;
int f[maxn], col[maxn];
vector<pair<int,int> > edge[maxn];
bool flag;
int find(int x){
   
    return f[x] == x ? x : f[x] = find(f[x]);
}
void dfs(int u, int p){
   //染色过程
    if (!flag) return;
    for (int i = 0; i < edge[u].size(); ++i){
   
        int v = edge[u][i].first, w = edge[u][i].second;
        if (col[v] == -1){
   
            col[v] = col[u]^w;
            dfs(v, u);
        }else if (col[u]^col[v] != w){
   
            flag = 0;
			 return;
        }
    }
}
int main(void){
   
    int n, m,ans; 
  	int u, v, w;
	scanf("%d%d",&n,&m);
    for (int i = 1; i <= n; ++i) f[i] = i;
    ans = n;
    for (int i = 1; i <= m; ++i){
   
       cin >> u >> v >> w;
        if (find(u) != find(v)){
   
            ans--;
			f[find(u)] = find(v);/
        }
        if (w != -1){
   
            edge[u].push_back(make_pair(v, w));
            edge[v].push_back(make_pair(u, w));
        }
    }
    memset(col, -1, sizeof col);
    int cnt = 0;
    flag = 1;
    for (int i = 1; i <= n; ++i){
   
        if (col[i] == -1){
   
            col[i] = 0;
			dfs(i, i);
			cnt++;
        }
    }
    if (!flag){
   
        cout << 0 << endl; 
        return 0;
    }
    ll ans = 1;
    for (int i = 0; i < cnt-ans; ++i) ans = ans*2%mod;
    cout << ans << endl;
    return 0;
}

全部评论

相关推荐

不愿透露姓名的神秘牛友
07-09 12:11
点赞 评论 收藏
分享
06-27 12:54
已编辑
门头沟学院 Java
累了,讲讲我的大学经历吧,目前在家待业。我是一个二本院校软件工程专业。最开始选专业是觉得计算机感兴趣,所以选择了他。本人学习计算机是从大二暑假结束开始的,也就是大三开始。当时每天学习,我个人认为Java以及是我生活的一部分了,就这样持续学习了一年半,来到了大四上学期末,大概是在12月中旬,我终于找的到了一家上海中厂的实习,但我发现实习生的工作很枯燥,公司分配的活也不多,大多时间也是自己在自学。就这样我秋招末才找到实习。时间来到了3月中旬,公司说我可以转正,但是转正工资只有7000,不过很稳定,不加班,双休,因为要回学校参加答辩了,同时当时也是心高气傲,认为可以找到更好的,所以放弃了转正机会,回学校准备论文。准备论文期间就也没有投递简历。然后时间来到了5月中旬,这时春招基本也结束了,然后我开始投递简历,期间只是约到了几家下场面试。工资也只有6-7k,到现在我不知道该怎么办了。已经没有当初学习的心劲了,好累呀,但是又不知道该干什么去。在家就是打游戏,boss简历投一投。每天日重一次。26秋招都说是针对26届的人,25怎么办。我好绝望。要不要参加考公、考研、央国企这些的。有没有大佬可以帮帮我。为什么感觉别人找工作都是顺其自然的事情,我感觉自己每一步都在艰难追赶。八股文背了又忘背了又忘,我每次都花很长时间去理解他,可是现在感觉八股、项目都忘完了。真的已经没有力气再去学习了。图片是我的简历,有没有大哥可以指正一下,或者说我应该走哪条路,有点不想在找工作了。
码客明:太累了就休息一下兄弟,人生不会完蛋的
如果实习可以转正,你会不...
点赞 评论 收藏
分享
代码飞升:别用口语,后端就写后端,前端就写前端,最后别光后悔
点赞 评论 收藏
分享
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务