题解 P6378 【[PA2010]Riddle】

题目链接

Solution [PA2010]Riddle

题目大意:(n) 个点 (m) 条边的无向图被分成 (k) 个部分。每个部分包含一些点。请选择一些关键点,使得每个部分恰有一个关键点,且每条边至少有一个端点是关键点。

2-SAT、建图优化


分析:

首先原问题我们转化为若干个形如

  • (u,v)两点至少选一个
  • 一个部分的点集(S)内恰有一个点被选

这样的限制条件

这个是可以用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