[题解]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)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效