P6378 [PA2010] Riddle-2sat优化建图

发布时间 2023-10-20 18:00:30作者: yoshinow2001

P6378 [PA2010] Riddle-2sat优化建图

\(n\) 个点 \(m\) 条边的无向图被分成 \(k\) 个部分。每个部分包含一些点。

请选择一些关键点,使得每个部分恰有一个关键点,且每条边至少有一个端点是关键点。

\(1\leq n,m\leq 10^6\)


边的限制

\(n\) 个变量 \(x_1\dots x_n\) ,其中 \(x_i\) 表示命题:“ \(i\) 是关键点”,与此同时也需要另外 \(n\) 个变量 \(\neg x_1,\dots,\neg x_n\) ,自然地 \(\neg x_i\) 表示命题”\(i\) 不是关键点“。

那么对于一条边的约束,就变成形如:

\[x_i\lor x_j=(\neg x_i\to x_j)\land (\neg x_j\to x_i) \]

的约束,把 \(x_1\dots,x_n,\neg x_1,\dots,\neg x_n\) 看成 \(2n\) 个结点,问题约束就变成了要选择一些点,使得如果选了点 \(x\) ,那么所有 \(x\) 能到达的点也要选,以及很明显不能同时选择 \(x_i\)\(\neg x_i(\forall i=1,\dots,n)\)

每个部分的限制

每个部分 \(S\) 中的元素至多选择一个,即如果选择了 \(x\) ,那么要向所有\(\neg y(y\in S,y\neq x)\) 连边,暴力连边是 \(O(n^2)\) 的显然不行。

但这种建图其实也很常见,每次给集合内除了自己之外的点连边,假设 \(S\) 中的点有序,排成 \(a_1,\dots,a_w\) ,那么对于每个 \(a_i\) 只要给前 \(i-1\) 个点和 \(i+1\) 往后的 \(n-i\) 个点连边,所以可以额外创建 \(w\) 个点表示前缀,\(w\) 个点表示后缀。

建图代码:

rep(tc,1,k){
    int w,c;cin>>w;
    rep(i,1,w)cin>>a[i];
    rep(i,1,w)pre[i]=++tot;
    rep(i,1,w)suf[i]=++tot;
    rep(i,1,w){
        addEdge(pre[i],a[i]+n);
        addEdge(suf[i],a[i]+n);
        if(i>1){
            addEdge(pre[i],pre[i-1]);
            addEdge(a[i],pre[i-1]);
        }
        if(i<w){
            addEdge(suf[i],suf[i+1]);
            addEdge(a[i],suf[i+1]);
        }
    }
}

完整代码:

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define fastio ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
const int N=4e6+5;
vector<vector<int>> G,H;
int n,m,k,tot,pre[N],suf[N],a[N];
int bl[N],scc,dfscnt,dfn[N],low[N];
bool in_stack[N];
stack<int> S;
void tarjan(int x){
	low[x]=dfn[x]=++dfscnt;
	S.push(x);in_stack[x]=1;
	for(auto to:G[x]){
		if(!dfn[to]){
			tarjan(to);
			low[x]=min(low[x],low[to]);
		}else if(in_stack[to])
			low[x]=min(low[x],dfn[to]);
	}
	if(low[x]==dfn[x]){
		++scc;
		bool tag=0;
		while(1){
			bl[S.top()]=scc;
			if(S.top()==x)tag=1;
			in_stack[S.top()]=0;
			S.pop();
			if(tag)break;
		}
	}
}
void addEdge(int x,int y){G[x].push_back(y);}
int main(){
	fastio;
	cin>>n>>m>>k;
	G=vector<vector<int>>(4*n+5);
	rep(i,1,m){
		int x,y;cin>>x>>y;
		addEdge(x+n,y);
		addEdge(y+n,x);
	}
	tot=2*n;
	rep(tc,1,k){
		int w,c;cin>>w;
		rep(i,1,w)cin>>a[i];
		rep(i,1,w)pre[i]=++tot;
		rep(i,1,w)suf[i]=++tot;
		rep(i,1,w){
			addEdge(pre[i],a[i]+n);
			addEdge(suf[i],a[i]+n);
			if(i>1){
				addEdge(pre[i],pre[i-1]);
				addEdge(a[i],pre[i-1]);
			}
			if(i<w){
				addEdge(suf[i],suf[i+1]);
				addEdge(a[i],suf[i+1]);
			}
		}
	}
	rep(i,1,tot)if(!bl[i])tarjan(i);
	bool bad=0;
	rep(i,1,n)if(bl[i]==bl[i+n])bad=1;
	if(bad)cout<<"NIE";
	else cout<<"TAK";
	return 0;
}