UOJ513 清扫银河
清扫银河
银河系共有 \(n\) 颗行星,编号为 \(1, \dots, n\)。环银河系航行可能会经过的无向航道共有 \(m\) 条,第 \(i\) 条航道连接 \(x_i\) 号行星和 \(y_i\) 号行星 \((x_i \neq y_i)\),用 \((x_i, y_i)\) 表示。保证同一条航道不会被重复列举多次。
由于部分航道存在容易被撞上的小行星带,初始时一部分航道是可通行的,一部分的航道是不可通行的。章北蚤说的清扫银河计划,就是希望你先向宇宙发射跳蚤激光,远距离清除所有阻碍计划的小行星带,将所有航道变得可通行。
如果跳蚤激光照射到了一个航道上,则该航道的通行状态会被翻转:如果一个航道原本不可通行,那么被照射后航道上的小行星带会被清除,航道也会变得可通行;反之若该航道原本可通行,那么跳蚤激光会因为未击中目标而在附近的宇宙空间中胡乱狂轰乱炸,炸出小行星带,导致该航道变得不可通行。
跳蚤激光只能批量发射,不能单独发射。发射方式只有如下两种:
-
选择一个由行星组成的简单环,发射跳蚤激光后,任意一条环路上的航道都会受到跳蚤激光照射。也就是说,选出一个由不同的行星编号组成的序列 \(p_1,\cdots,p_k\)(\(2 \le k \le n\)),使得对于所有 \(1 \le i \le k\) 都存在航道 \((p_i, p_{(i \bmod k)+1})\)。在发射跳蚤激光后,任意一条形如 \((p_i, p_{(i \bmod k)+1})\) 的航道的通行状态都会被翻转;特别的,如果简单环长度为2,则这一条边会被翻转两次。
-
把每个行星标记为黑色或者白色,发射跳蚤激光后,任意一条两端行星颜色不同的航道都会受到跳蚤激光照射。也就是说,对于所有的航道 \((x_i, y_i)\),如果 \(x_i\) 和 \(y_i\) 号行星的颜色不同,那么 \((x_i, y_i)\) 航道的通行状态会被翻转。
发射跳蚤激光的费用非常昂贵,所以章北蚤要求你判断是否能使用不超过 \(m+1\) 次发射就能完成清扫银河计划。
对于所有测试数据,满足 \(1 \le T \le 10,1 \le n \le 300,0 \le m \le \frac{n(n-1)}{2}\)。
题解
https://blog.csdn.net/suncongbo/article/details/105816960
考虑简化操作:
对于第二种操作,其实就可以等价于若干次单点操作,每次标记一个点,把和这个点相邻的边全部反转。即有用的操作只有\(n\)种。
对于第一种操作,众所周知一个无向图中所有的环都可以由若干个非树边覆盖的环异或得到。即有用的操作只有\((m−n+1)\)种。
这样我们可以得到一个异或方程组,变量数和方程数都为\((m+1)\). 这也证明了为什么只要有解就能在\((m+1)\)次操作内出解。
时间复杂度\(O(\frac{m^3}{ω})\),期望得分70分。
考虑优化,通过欧拉回路不难证明一个子图可以用第一种操作消去当且仅当子图内每个点度都是偶数。
于是问题就变成了用第二种操作使得每个点度均为偶数。变量数量和方程数量都为\(n\)个。
时间复杂度\(O(\frac{n^3}{ω})\).
CO int N=310;
bitset<N> a[N];
void real_main(){
int n=read<int>();
for(int i=1;i<=n;++i) a[i].reset();
for(int m=read<int>();m--;){
int x=read<int>(),y=read<int>(),w=read<int>();
a[x][x].flip(),a[y][y].flip(),a[x][y].flip(),a[y][x].flip();
if(w) a[x][n+1].flip(),a[y][n+1].flip();
}
for(int i=1;i<=n;++i){
int p=i;
for(int j=i;j<=n;++j)if(a[j][i]) {p=j; break;}
if(p!=i) swap(a[p],a[i]);
if(!a[i][i]) continue;
for(int j=1;j<=n;++j)if(j!=i and a[j][i]) a[j]^=a[i];
}
for(int i=1;i<=n;++i)if(!a[i][i] and a[i][n+1]) {puts("no"); return;}
puts("yes");
}
int main(){
for(int T=read<int>();T--;) real_main();
return 0;
}