题目大意:\(n\) 个点 \(m\) 条边的无向图被分成 \(k\) 个部分。每个部分包含一些点。请选择一些关键点,使得每个部分恰有一个关键点,且每条边至少有一个端点是关键点。
2-SAT、建图优化
分析:
首先原问题我们转化为若干个形如
这样的限制条件
这个是可以用2-SAT来判定的
问题在于,暴力连边的话,第二个限制条件的边会非常多,但是我们注意到,对每个部分的点钦定一个顺序排成序列的话,每个点连出去的边终点是两段区间
你大可以线段树优化建图然后尝试MLE/TLE
前(后)缀和优化建图可以将边数优化到\(O(n)\)级别,然后就可以放心的跑2-SAT了
#include <cstdio>
#include <cctype>
#include <vector>
#include <stack>
using namespace std;
const int maxn = 5e6 + 100;
inline int read(){
int x = 0;char c = getchar();
while(!isdigit(c))c = getchar();
while(isdigit(c))x = x * 10 + c - ‘0‘,c = getchar();
return x;
}
struct edge{int u,v;};
vector<edge> edges;
vector<int> G[maxn],vec[maxn];
inline void addedge(int u,int v){
G[u].push_back(v);}
int n,m,k,tot,dfs_tot,scc_tot,scc[maxn],to[maxn],dfn[maxn],low[maxn],instk[maxn];
stack<int> stk;
inline void tarjan(int u){
dfn[u] = low[u] = ++dfs_tot;
stk.push(u),instk[u] = 1;
for(int v : G[u]){
if(!dfn[v])tarjan(v),low[u] = min(low[u],low[v]);
else if(instk[v])low[u] = min(low[u],dfn[v]);
}
if(low[u] == dfn[u]){
scc_tot++;
int t;
do{
t = stk.top();stk.pop(),instk[t] = 0;
scc[t] = scc_tot;
}while(t != u);
}
}
int main(){
n = read(),m = read(),k = read();
for(int u,v,i = 1;i <= m;i++)
u = read(),v = read(),edges.push_back(edge{u,v});
for(int x,i = 1;i <= k;i++){
int num = read();
while(num--)x = read(),vec[i].push_back(x),to[x] = ++tot;
}
for(int i = 1;i <= n;i++)addedge(n + i,i),addedge(2 * n + i,i);
for(int id = 1;id <= k;id++){
for(unsigned int i = 0;i < vec[id].size();i++){
if(i != 0)addedge(n + to[vec[id][i]],n + to[vec[id][i]] - 1),addedge(3 * n + vec[id][i],n + to[vec[id][i]] - 1);
if(i != vec[id].size() - 1)addedge(2 * n + to[vec[id][i]],2 * n + to[vec[id][i]] + 1),addedge(3 * n + vec[id][i],2 * n + to[vec[id][i]] + 1);
}
}
for(auto e : edges){
addedge(to[e.u],3 * n + e.v);
addedge(to[e.v],3 * n + e.u);
}
for(int i = 1;i <= 4 * n;i++)
if(!dfn[i])tarjan(i);
for(int i = 1;i <= n;i++)
if(scc[to[i]] == scc[3 * n + i])return puts("NIE"),0;
puts("TAK");
return 0;
}
原文:https://www.cnblogs.com/colazcy/p/13543299.html