M. Obliviate, Then Reincarnate 题解

[ M. Obliviate, Then Reincarnate ]( Problem - M - Codeforces )

1753853863150

读题读不懂怎么办qwq~

1753854298668

题意分析

  1. 首先,我们要知道,房间编号是需要通过楼层确定的,即房间a编号变成 ((a mod n)+n) mod n,即将编号变成 [0,n] 中的正数。
//获取编号
void get(int x){
	return (x%n+n)%n;
}

指令(a,b)表示将房间a所在楼层的人移到 房间 a+b ,建图就是将 get(a) 和 get(a+b) 连边权为b的边。

  1. 然后分析什么情况会使可能到达的房间编号组成无限集?

通过上面的建图后我们得到了n个点m条边的有向图,这张图意味着经过指令后可以到达的房间编号,并且每个新的房间编号是通过加权值得到的,如果出现了一个环,并且环的权值和不为0,那么我们就可以一直沿着这个环一直生成无限个数,即满足无限集条件。

  1. 怎么处理多次查询从x出发是否能构成无限集?

定义权值和非0的环为 “坏”环 ,反之为 “好”环

如果某个点能够到达 “坏”环 ,那么这个点就能构成无限集,包括 “好”环 中的点也是如此。

那么我们只需要通过BFS或DFS对“坏”环跑个反向图,每次对连接的点做个标记即可。

然后查询的时候就可以O(1)查询这个点是否可以构成无限集了~

解题思路

  1. 先确定环,可以利用 tarjan 缩点

  2. 然后跑 dfs 检测非零环,注意我们可以找到每个环的一个代表点,然后以此为根dfs,每次注意检测是否在同一个环内。

    检测环权值的方法利用势能标记法,即设根节点的势能为0,下一个点的势能就是加上边权,即从 x->y 的势能是h[y]=h[x]+w,当再次到达 已经到过的节点 时判断势能是否有变化, 即 是否 ( h[y]==h[x]]+w ),如果相同说明环权值为0,否则就是非零环,也就是“坏”环,那么就将此环编号标记。

  3. 然后我们构建DAG 的反向图,跑BFS 标记能到达“坏”环的连通分量。

代码

#include <bits/stdc++.h>
using namespace std;
//-------------------------------------------------------------------------------------------
#define int long long 
#define lost_R ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define P pair<int,int>
#define lowbit(x) (x&(-x))
#define dbg1(x) cout<<"# "<<x<<endl
#define dbg2(x,y) cout<<"# "<<x<<" "<<y<<endl
#define endl '\n'
const int mod=998244353;
const int N=1e6+10;
const int INF=0x3f3f3f3f3f3f3f3f;
const int inf=0x3f3f3f3f;
using ar3=array<int,3>;
using ar2=array<int,2>;
//--------------------------------------------------------------------------------------
int n,m,q;
vector<P> g[N];
int get(int x){
    return (x%n+n)%n;
}
int dfn[N],low[N],col[N],num[N],tot,cnt;
int vis[N];
stack<int> st;
int rt[N];
//tarjan缩点
void tarjan(int x,int r){
    low[x]=dfn[x]=++tot;
    st.push(x);
    vis[x]=1;
    for(auto [y,w]:g[x]){
        if(!dfn[y]){
            tarjan(y,x);
            low[x]=min(low[x],low[y]);
        }else if(vis[y]) low[x]=min(low[x],dfn[y]);
    }
    if(dfn[x]==low[x]){
        col[x]=++cnt;
        num[cnt]++;
        vis[x]=0;
        while(st.top()!=x){
            int y=st.top();
            st.pop();
            col[y]=cnt;
            num[cnt]++;
            vis[y]=0;
        }
        st.pop();
    }
}
//势能标记法检测非零环
int flag[N],h[N],res[N];
void dfs(int x){
    if(res[col[x]]) return; //如果已经标记过是"坏"环,剪枝
    flag[x]=1;
    for(auto [y,w]:g[x]){
        if(col[y]!=col[x]) continue;
        if(!flag[y]){ //没到过这个点
            h[y]=h[x]+w;
            dfs(y);
        }else{ //到过这个点就检测势能变化
            if(h[y]!=h[x]+w){ 
                res[col[x]]=1;
                return;
            }
        }
    }
}
vector<int> ne[N];

void solve(){
    cin>>n>>m>>q;
    for(int i=1;i<=m;i++){
        int a,b;
        cin>>a>>b;
        int x=get(a);
        int y=get(a+b);
        g[x].push_back({y,b});
    }    
    for(int i=0;i<n;i++){
        if(!dfn[i]) tarjan(i,0);
    }
    for(int i=0;i<n;i++){
        rt[col[i]]=i;//每个强连通分量找一个代表点作为根
    }
    for(int i=1;i<=cnt;i++){
        dfs(rt[i]);//检测非零环
    }
    for(int x=0;x<n;x++){
        for(auto [y,w]:g[x]){
            if(col[x]==col[y]) continue;
            ne[col[y]].push_back(col[x]);//构建反向DAG
        }
    }
    queue<int> qq;
    for(int i=1;i<=cnt;i++){
        if(res[i]) qq.push(i);//把“坏”环的编号加进去
    }
    while(!qq.empty()){
        int y=qq.front();
        qq.pop();
        for(auto x:ne[y]){
            if(!res[x]){
                res[x]=1;
                qq.push(x);
            }
        }
    }
    while(q--){
        int x;
        cin>>x;
        x=get(x);
        if(res[col[x]]) cout<<"Yes"<<endl;
        else cout<<"No"<<endl;
    }
}
signed main(){
    lost_R;
    // freopen("jia.in","r",stdin);
    // freopen("jia.out","w",stdout);
    int T=1;
    //cin>>T;
    for(int i=1;i<=T;i++){
        solve();
    }
    return 0;
}
posted @ 2025-07-30 14:40  RYRYR  阅读(84)  评论(0)    收藏  举报