题目

题目描述:
小团有一张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. 异或的概念想必都知道,相同为0,不同为1。
  2. 但是边是真的不好下手,所以我们这一步就非常重要了:将我们要计算的边权化为点权。(就是每个点分别有自己的权值)
  3. 但是点权又怎么下手呢?又开始疑惑了。我们现在知道每个点假设涂色两种(0/1),我们能知道一共有2n种涂色方法
    但是这些全是吗?是的!(在不限定边权的情况下):因为所有边权化为所有点权的时候(一边对两点),每个点都操作了两次,抵消了!
    我们举个栗子,有四个点(a,b,c,d)边权分别为:a^b,b^c,c^d,d^a。如果全部异或起来就有a^b^b^c^c^d^d^a:两两对应,当然为0
  4. 我们知道了点的方法数,再来推出边的方法数:由于点权得到边权只有两种方案,所以点权就是边权的两倍:2n-1。(不在同一个环里面的话乘起来就好了,答案不变)
  5. 但是我们现在有边权!边权给了我们一些限制。但是这很好理解!加入你已知了一些边权,以边权可以抵达的某一点出发,已知这个点,其他点都能算出来
  6. 意思就是说,我们只要知道有几组边权连通图就行了。加入有k个,我们只要去掉2k-1组情况就行了。所以答案就是2n-k

然后更重要的是!代码怎么写!
  1. 这里用到了一个我不会的新概念就是前向星(点开看专栏)
  2. 其实知道前向星怎么用以后就不难了。我们首先该存的存进前向星结构体数组里面。
  3. 然后要进行一个判断:题目给我们的可能本来就不成立,那我们只要判断,我们推算出来的边权和题目边权不一样就行了。(就是题目的边权条件互斥)
  4. 如果互斥输出个0,然后结束就好了。如果不是呢我们就进入下一步。
  5. 我们再只要遍历算出总节点数量(得到中点权推算出总边权),和有边权节点数量(删除这些组的可能性)。
  6. 最后来个快速幂输出就好了!!!!!
  7. 剩下来看我详细的代码吧!(累了,困了)


AC代码

#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
//代码预处理区

const int MOD = 998244353;
const int MAX = 1e5 + 7;
int n, m;//总点数,总边数
int head[MAX], total = 1;//前向星:查找数组,计数
int color[MAX], visited[MAX];//每节点颜色数组,观测数组
struct EDGE {
    int me, next, val;
    EDGE() {};
    void init(int from, int to, int val) {
        this->me = to;
        this->next = head[from];
        this->val = val;
        head[from] = total++;
    }
} edge[MAX << 1];
//全局变量区

template<class T>inline void read(T& res);//整型快读函数
int check(int node, int col);//检查题目合法性函数
void get_all(int node, int& sum);//计算连通块总节点数
void get_del(int node, int& sum);//计算连通块被删除节点数
ll qpow(ll n, ll m);//快速幂
//函数预定义区

int main() {
    read(n); read(m);
    for (int i = 1; i <= m; i++) {
        int u, v, w; read(u); read(v); read(w);
        edge[total].init(u, v, w);
        edge[total].init(v, u, w);
    }
    //前向星初始化
    memset(color, -1, sizeof(color));
    for (int i = 1; i <= n; i++)
        if (color[i] == -1)
            if (!check(i, 1)) {
                printf("0\n");
                return 0;
            }
    //题目合法性判断,不合法直接输出0并结束
    int cnt = 0;//计算指数
    memset(visited, 0, sizeof(visited));
    for (int i = 1; i <= n; i++) {
        int sum = 0;//连通块总节点数
        if (!visited[i]) {
            get_all(i, sum);
            cnt += sum - 1;
        }
    }
        //加上没有边权的情况下的指数大小
    memset(visited, 0, sizeof(visited));
    for (int i = 1; i <= n; i++) {
        int sum = 0;
        if (!visited[i]) {
            get_del(i, sum);
            cnt -= sum - 1;
        }
    }
        //减去边权所占用的指数大小
    cout << qpow(2, cnt) << endl;
    return 0;
}
template<class T>inline void read(T& res) {
    char c; T flag = 1;
    while ((c = getchar()) < '0' || c > '9')
        if (c == '-')
            flag = -1;
    res = c - '0';
    while ((c = getchar()) >= '0' && c <= '9')
        res = res * 10 + c - '0';
    res *= flag;
}
int check(int node, int col) {
    color[node] = col;
    for (int i = head[node]; i; i = edge[i].next) {
        int to = edge[i].me;//当前节点连接的下一个节点
        int weight = edge[i].val;//当前节点与下一节点之间的边权
        if (weight == -1) continue;
        //没有边权位置一定存在
        if (color[to] != -1)
            if (color[node] ^ color[to] != weight)
                return 0;
            else;
        //有颜色则判断是否相等
        else
            if (!check(to, col ^ weight)) 
                return 0;
        //无颜色则继续判断下一个节点
    }
    return 1;
} 
void get_all(int node, int& sum) {
    sum++;
    visited[node] = 1;
    for (int i = head[node]; i; i = edge[i].next)
        if (!visited[edge[i].me])
            get_all(edge[i].me, sum);
                //查找联通的点,每次查找一个环
}
void get_del(int node, int& sum) {
    sum++;
    visited[node] = 1;
    for (int i = head[node]; i; i = edge[i].next)
        if (edge[i].val != -1)
            if (!visited[edge[i].me])
                get_del(edge[i].me, sum);
                        //查找边权点

}
ll qpow(ll n, ll m) {
    ll mul = 1;
    while (m) {
        if (m & 1) mul = (mul * n) % MOD;
        m >>= 1;
        n = (n * n) % MOD;
    }
    return mul;
}
//函数区