[题解]CF1907G Lights

CF1907G Lights

我们可以把灯抽象成节点,而开关抽象成无向边(重边算作\(1\)条)。

显然每个连通块要么是一棵树,要么是一棵基环树。

对于基环树,我们把它看做若干棵树处理,最后我们再考虑如何处理环。

如下图,这是一棵树,黄色的点表示亮灯。
image
我们选定任意一条边,可以改变子节点和父节点的状态。

那么对于每一棵树,我们的想法是把亮的灯转换到根节点上,这样才能在基环树的环上继续操作。

我们可以用拓扑排序解决。对于叶子节点,如果该点亮着灯,我们按下开关,改变自己和父节点的状态。这样此点已经熄灭,我们可以将其删掉。继续重复上述步骤,直到只剩下根节点。根节点可能亮也可能灭。

对整个图拓扑排序完,就只剩下若干个环了。对于每个环,我们进行如下考虑:
image

首先如果环上亮着的灯有奇数个,说明无论怎么操作都无法让所有灯都灭掉,输出\(-1\)即可。

如果有偶数个亮灯,如上图,有两种情况灭掉所有灯:

  • \(2\)\(3\)之间所有开关(边)都按一遍,这样可以同时把\(2,3\)熄灭;再把\(5\)\(7\)之间的所有开关都按一遍,同时把\(5,7\)熄灭。
  • 我们也可以错一下位,选择\(3\ -\ 5\)\(7\ -\ 2\)熄灭。

因此对于每一个环,我们需要依次对上面两种情况按的开关数进行计数,哪种按开关次数(经过边的个数)最少就选哪种。

树上的操作\(+\)环上的操作\(=\)总操作。使用一个\(vector\)记录下结果,输出即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int t,n,a[100010];
bool s[100010];
int deg[100010];
vector<int> ans;
queue<int> q;
void solve(){
	memset(deg,0,sizeof deg);
	ans.clear();
	cin>>n;
	for(int i=1;i<=n;i++){
		char c;
		cin>>c;
		s[i]=c-'0';
	}
	for(int i=1;i<=n;i++){
		cin>>a[i];
		deg[a[i]]++;
	}
	for(int i=1;i<=n;i++){
		if(deg[i]==0) q.push(i);
	}
	while(!q.empty()){
		int t=q.front();
		int r=a[t];
		q.pop();
		deg[r]--;
		if(s[t]){//上移标记,记录结果
			s[r]=!s[r];
			ans.emplace_back(t);
		}
		if(!deg[r]){//入度为0则入队列
			q.push(r);
		}
	}
	for(int i=1;i<=n;i++){
		if(deg[i]&&s[i]){
			vector<int> aa,bb;
			int pos=i;
			bool sta=0;
			while(deg[pos]){
				deg[pos]--;
				if(s[pos]) sta=!sta;
				if(sta) aa.push_back(pos);
				else bb.push_back(pos);
				pos=a[pos];
			}
			if(sta){
				cout<<"-1\n";
				return;
			}
			if(aa.size()<bb.size()){
				ans.insert(ans.end(),aa.begin(),aa.end());
			}else{
				ans.insert(ans.end(),bb.begin(),bb.end());
			}
		}
	}
	cout<<ans.size()<<"\n";
	for(auto i:ans) cout<<i<<" ";
	cout<<"\n";
}
int main(){
	cin>>t;
	while(t--) solve();
	return 0;
}

(代码有借鉴此文章 by _Ink

posted @ 2024-05-08 23:06  Sinktank  阅读(19)  评论(0编辑  收藏  举报
★CLICK FOR MORE INFO★ TOP-BOTTOM-THEME
Enable/Disable Transition
Copyright © 2023 ~ 2024 Sinktank - 1328312655@qq.com
Illustration from 稲葉曇『リレイアウター/Relayouter/中继输出者』,by ぬくぬくにぎりめし.