[题解]CF1907G Lights
我们可以把灯抽象成节点,而开关抽象成无向边(重边算作\(1\)条)。
显然每个连通块要么是一棵树,要么是一棵基环树。
对于基环树,我们把它看做若干棵树处理,最后我们再考虑如何处理环。
如下图,这是一棵树,黄色的点表示亮灯。
我们选定任意一条边,可以改变子节点和父节点的状态。
那么对于每一棵树,我们的想法是把亮的灯转换到根节点上,这样才能在基环树的环上继续操作。
我们可以用拓扑排序解决。对于叶子节点,如果该点亮着灯,我们按下开关,改变自己和父节点的状态。这样此点已经熄灭,我们可以将其删掉。继续重复上述步骤,直到只剩下根节点。根节点可能亮也可能灭。
对整个图拓扑排序完,就只剩下若干个环了。对于每个环,我们进行如下考虑:
首先如果环上亮着的灯有奇数个,说明无论怎么操作都无法让所有灯都灭掉,输出\(-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)