BFS黑白染色

有一个小技巧,每次开始时先把根节点放进队列中,第一层循环循环层数,令k=q.size(),第二层循环从0到k每次pop()一个,在第二层循环完成之前,所有pop()出的点都是属于同一层。

int f(int u){
    queue<int> q;
    q.push(u);
    int index=1;
    vis[u]=index;
    while(!q.empty()){
        index^=3;
        int k=q.size();
        for(int i=0;i<k;i++) {
            int x=q.front();q.pop();
            for(int i=0;i<edges[x].size();i++){
                if(!vis[ edges[x][i] ]) {
                    vis[ edges[x][i] ]=index;
                    q.push(edges[x][i]);
                }else if(vis[edges[x][i]]==vis[x]) return -1;
            }
        }
    }
}
----------
int main()
{
    for(int i=1;i<=n;i++){
        if(!vis[i]) {
            int u=f(i);
            if(u==-1) {
                printf("Impossible");
                return 0;
            }
        }
    }
}
请结合此题解分析并解释此代码一个 n×m 的 01 矩阵,其中 k 个位置的值给定。这个矩阵满足性质:对于每个 2×2 的小子矩阵中 0 与 1 的个数相同。求满足条件的矩阵数量,对 10 9 +7 取模。 n,m≤10 9 ,k≤10 5 。 一个显然的事实是:确认了第一行和第一列,就能推得所有矩阵。考虑如何确定第一行和第一列。 结论:一个矩阵是一个合法的矩阵,当且仅当其满足,要么第一行是 010101… 或 101010… 这样 01 交错的;要么第一列是这样 01 交错的。 证明可以画图,考虑一个这样的矩阵,使得其第一行和第一列都不是 01 相间的。那么可以找出一个位置 i,满足第 i 行的第一个数和第 i+1 行的第一个数相同(即第一列上连续的两个相同的数),为了方便,都假定为 1。我们考虑这两行的情况: 101⋯01101⋯1001⋯ 101⋯01101⋯1001⋯ ​ (由于 Latex 渲染问题,该公式建议在博客内查看) 注意到红色 1 的位置,在这个位置为了满足以它作为右下角的矩阵而值为 1,但这就导致了以它为左下角的矩阵出现了三个 1,成为了一个不合法的矩阵。这正是因为行上也有相邻的两个 1,有两个相邻的 0 也是这种情况(蓝色部分)。这是不可避免的,故而这样的矩阵都是不合法的。而如果不出现这种情况,则一定可以构造出合法方案。故而结论证毕。 同时这个结论可以推导出一些东西:对于一个第一行都是 01 相间的矩阵,它的每一行都是 01 相间的;对于一个第一列都是 01 相间的矩阵,它的每一列都是 01 相间的。 接下来考虑如何计数。根据容斥原理,我们用 行都是 01 相间的矩阵 + 列都是 01 相间的矩阵 − 行列都是 01 相间的矩阵(即类似棋盘的黑白染色矩阵)。 行都是 01 相间和列都是 01 相间做法类似,这里只介绍行的方式,列做法同理。 对于行上没有已确定的 k 个位置,这一行的情况可以任取 0101… 或 1010… 的一种,乘法原理对答案乘 2。 对于行上已经有确定的位置时,用奇偶性检测它们能否构成 01 相间的一行。如果可以,对答案乘 1;如果不行,答案为 0。 行列都是 01 相间的矩阵直接检查是否能够构成一个棋盘状的东西,同样判一下奇偶性。注意特判一下 k=0 的情况。 这题这样就做完了,时间复杂度 O(klogk+log(n+m))。 代码: #include <algorithm> #include <cstdio> #include <cstring> const int MaxN = 100000; const int Mod = 1000000007; inline int add(int x, int y) { return (x += y) >= Mod ? x - Mod : x; } inline int sub(int x, int y) { return (x -= y) < 0 ? x + Mod : x; } inline int mul(int x, int y) { return 1LL * x * y % Mod; } inline int pw(int x, int y) { int z = 1; for (; y; y >>= 1, x = mul(x, x)) if (y & 1) z = mul(z, x); return z; } inline int inv(int x) { return pw(x, Mod - 2); } inline int sep(int x, int y) { return mul(x, inv(y)); } inline void inc(int &x, int y = 1) { x = add(x, y); } inline void dec(int &x, int y = 1) { x = sub(x, y); } struct data_t { int x, y, d; data_t(int _x = 0, int _y = 0, int _d = 0) { x = _x, y = _y, d = _d; } }; int W, H, N; data_t A[MaxN + 5]; inline int getSit() { char c; do c = getchar(); while (c != '+' && c != '-'); return (c == '+') ? 1 : 0; } void init() { scanf("%d %d %d", &W, &H, &N); for (int i = 1; i <= N; ++i) { A[i].d = getSit(); scanf("%d %d", &A[i].x, &A[i].y); } } inline bool cmpx(const data_t &a, const data_t &b) { if (a.x != b.x) return a.x < b.x; else return a.y < b.y; } inline bool cmpy(const data_t &a, const data_t &b) { if (a.y != b.y) return a.y < b.y; else return a.x < b.x; } inline int calc1() { std::sort(A + 1, A + 1 + N, cmpx); int res = 0, prex = 0; for (int l = 1, r = 0; l <= N; l = r + 1) { while (r < N && A[r + 1].x == A[l].x) r++; for (int i = l; i <= r; ++i) { int x1 = (A[i].y ^ A[l].y) & 1, x2 = (A[i].d ^ A[l].d); if (x1 != x2) return 0; } res += A[l].x - prex - 1; prex = A[l].x; } res += W - prex; return pw(2, res); } inline int calc2() { std::sort(A + 1, A + 1 + N, cmpy); int res = 0, prey = 0; for (int l = 1, r = 0; l <= N; l = r + 1) { while (r < N && A[r + 1].y == A[l].y) r++; for (int i = l; i <= r; ++i) { int x1 = (A[i].x ^ A[l].x) & 1, x2 = (A[i].d ^ A[l].d); if (x1 != x2) return 0; } res += A[l].y - prey - 1; prey = A[l].y; } res += H - prey; return pw(2, res); } inline int calc3() { for (int i = 1; i <= N; ++i) { int x1 = (A[1].x ^ A[1].y ^ A[i].x ^ A[i].y) & 1, x2 = (A[1].d ^ A[i].d); if (x1 != x2) return 0; } return 1; } void solve() { int ans = 0; inc(ans, calc1()); inc(ans, calc2()); dec(ans, calc3()); if (N == 0) dec(ans); printf("%d\n", ans); } int main() { init(); solve(); return 0; }请以在代码中注释的方式解释此代码
最新发布
07-24
### 黑白马问题的广度优先搜索 (BFS) 实现 以下是基于黑白马问题的一个可能的解决方案,采用C语言实现。此问题通常涉及在一个棋盘上移动黑白色马,并通过一系列合法操作达到目标状态。 #### BFS算法的核心概念 广度优先搜索是一种用于图遍历或树遍历的方法,它按照层次顺序访问节点[^3]。对于黑白马问题,可以将每种棋盘布局视为一个节点,而每次合法的操作则表示一条边连接两个不同的节点。 #### 数据结构的选择 为了高效存储和检索已访问的状态以及队列中的当前状态,可以选择以下数据结构: - **数组** 或者 **位掩码** 来表示棋盘上的位置。 - 使用 FIFO 队列来保存待处理的状态。 #### C语言实现 下面是一个简单的C语言程序框架,展示如何利用BFS解决黑白马问题: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_QUEUE_SIZE 100000 #define BOARD_SIZE 8 typedef struct { int board[BOARD_SIZE]; } State; // 定义方向向量(假设特定规则下的移动方式) int dx[] = {2, 1, -1, -2, -2, -1, 1, 2}; int dy[] = {1, 2, 2, 1, -1, -2, -2, -1}; // 判断是否越界 int is_valid(int x, int y) { return (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE); } // 复制状态函数 void copy_state(State *dest, State *src) { memcpy(dest->board, src->board, sizeof(src->board)); } // 广度优先搜索核心逻辑 int bfs(State start, State goal) { State queue[MAX_QUEUE_SIZE]; int visited[(1 << (BOARD_SIZE))] = {0}; // 假设使用位掩码标记访问过的状态 int front = 0; int rear = 0; queue[rear++] = start; visited[state_to_int(start)] = 1; // 将初始状态加入队列并标记为已访问 while (front != rear) { State current = queue[front++]; if (memcmp(current.board, goal.board, sizeof(goal.board)) == 0) { return 1; // 找到解 } // 枚举所有可能的动作 for (int i = 0; i < BOARD_SIZE; ++i) { int new_x = current.board[i] / BOARD_SIZE + dx[k]; int new_y = current.board[i] % BOARD_SIZE + dy[k]; if (!is_valid(new_x, new_y)) continue; State next_state; copy_state(&next_state, ¤t); // 更新下一个状态的具体逻辑... int hash_value = state_to_int(next_state); if (!visited[hash_value]) { visited[hash_value] = 1; queue[rear++] = next_state; } } } return 0; // 如果无法到达目标,则返回失败标志 } int main() { State initial, target; memset(initial.board, 0, sizeof(initial.board)); // 初始化起始状态 memset(target.board, 7, sizeof(target.board)); // 设置目标状态 if (bfs(initial, target)) { printf("Solution found!\n"); } else { printf("No solution.\n"); } return 0; } ``` 上述代码提供了一个基本框架,具体细节取决于实际问题定义中的动作集合与终止条件。 #### 关键点说明 - `state_to_int` 函数应被设计用来唯一映射每个棋盘配置至整数空间以便快速查找重复项[^4]。 - 动作枚举部分需依据题目描述调整具体的跳动规则。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值