n盏灯,按第i个开关会使第i盏灯和第a[i]盏灯改变状态,求最少操作次数使得全部灯灭

文章讲述了如何使用C++编程解决一个关于灯泡开关的问题,涉及邻接表表示、队列操作以及处理环内灯的逻辑。主要关注输入整数和字符串,计算开关灯泡使所有灯泡熄灭所需的最少操作次数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
int n;
int a[maxn], ind[maxn];
void solve(){
	int i, j;
	cin >> n;
	string s;
	cin >> s;
	s = ' ' + s;
	for(i = 1; i <= n; i++){
		ind[i] = 0;
	}
	for(i = 1; i <= n; i++){
		cin >> a[i];
		ind[a[i]]++;//i -> a[i]
	}
	queue<int> q;
	for(i = 1; i <= n; i++){
		if(ind[i] == 0){
			q.push(i);
		}
	}
	vector<int> ans;
	while(!q.empty()){
		int u = q.front();
		q.pop();
		int v = a[u];
		if(s[u] == '1'){//入度为0(其他开关不会影响自己了)且亮着的肯定得按一次开关
			s[u] = '0';
			s[v] ^= 1;
			ans.push_back(u);
		}
		ind[v]--;
		if(ind[v] == 0) q.push(v);
	}
	for(i = 1; i <= n; i++){//处理环内的灯
		if(ind[i]){//找到环内的灯
			int len = 0;//环的长度
			int j = i;//当前在哪个灯
			int res = 0;//把环内的灯关了要几次操作
			int t = 0;//当前灯是否需要操作
			/*
			环内有两种情况:(只有亮着的灯个数为偶数才能全部关)
			1、亮着的灯成对相邻存在
			  1 1 
			 0	 0
			 0	 0
				1	1 
			2、不相邻(此时一次操作相当于可以让1移动一步,在下图中向下移动最优)
			  1 0 0 0 0
				0       0
				0				0
				1	0	0	0	0
			*/
			while(ind[j]){
				if(s[j] == '1'){
					t ^= 1;
				}
				ind[j] = 0;
				len++;
				res += t;
				j = a[j];
			}
			if(t == 1){//奇数个灯亮
				cout << -1 << '\n';
				return;
			}
			for(int k = 1; k <= len; k++){
				if(s[j] == '1') t ^= 1;
				if(t == (res <= len - res)){//如果res <= len - res,说明从j开始操作是最优的,否则从j的下一个亮着的灯开始操作
					ans.push_back(j);
				}
				j = a[j];
			}
		}
	}
	cout << ans.size() << '\n';
	for(auto x : ans){
		cout << x << " \n"[x == ans.back()];
	}
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	int T;
	cin >> T;
	while(T--){
		solve();
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

__night_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值