Codeforces Round #317 [AimFund Thanks-Round] (Div. 1) C. CNF 2 无向图找环

C. CNF 2
time limit per test1 second
memory limit per test256 megabytes
inputstandard input
outputstandard output
'In Boolean logic, a formula is in conjunctive normal form (CNF) or clausal normal form if it is a conjunction of clauses, where a clause is a disjunction of literals' (cited from https://en.wikipedia.org/wiki/Conjunctive_normal_form)


In the other words, CNF is a formula of type , where & represents a logical "AND" (conjunction),  represents a logical "OR" (disjunction), and vij are some boolean variables or their negations. Each statement in brackets is called a clause, and vij are called literals.


You are given a CNF containing variables x1, ..., xm and their negations. We know that each variable occurs in at most two clauses (with negation and without negation in total). Your task is to determine whether this CNF is satisfiable, that is, whether there are such values of variables where the CNF value is true. If CNF is satisfiable, then you also need to determine the values of the variables at which the CNF is true.


It is guaranteed that each variable occurs at most once in each clause.


Input
The first line contains integers n and m (1 ≤ n, m ≤ 2·105) — the number of clauses and the number variables, correspondingly.


Next n lines contain the descriptions of each clause. The i-th line first contains first number ki (ki ≥ 1) — the number of literals in the i-th clauses. Then follow space-separated literals vij (1 ≤ |vij| ≤ m). A literal that corresponds to vij is x|vij| either with negation, if vij is negative, or without negation otherwise.


Output
If CNF is not satisfiable, print a single line "NO" (without the quotes), otherwise print two strings: string "YES" (without the quotes), and then a string of m numbers zero or one — the values of variables in satisfying assignment in the order from x1 to xm.


Sample test(s)
input
2 2
2 1 -2
2 2 -1
output
YES
11
input
4 3
1 1
1 2
3 -1 -2 3
1 -3
output
NO
input
5 6
2 1 2
3 1 -2 3
4 -3 5 4 6
2 -6 -4
1 5
output
YES
100010
Note
In the first sample test formula is . One of possible answer is x1 = TRUE, x2 = TRUE.
题意,给出与或式,每个子名包括一些或式,每个条件在一个子句中只会出现一次,在所有子句中x 和~x最多只会出现两次,要求构造每个条件真假,使整个与或式成立。
这个题的关键在于,每个子句只会出现2次这个条件。如果只出现x 或只出现~x那么简单,直接确定真假即可。如果,出现了x 也出现了~x.我们可以这样做,把子句看成点,条件x ~x看成边,每个点,至少要有一条入边就可以。这样连一条x 与~x的无向边。如果是树,那一定无解, 因为,边少于点数,总会有一个点没有入边,不能成立。如果不是树那一定存在解,因为,只要在环上的边,随便取一条成为入边,这样每一个点都有一条边了。
这个问题转化成了无向图找在环上的边的问题。
无向图怎么确定有没有环呢,两种方法,
1.
第一步:删除所有度<=1的顶点及相关的边,并将另外与这些边相关的其它顶点的度减一。
第二步:将度数变为1的顶点排入队列,并从该队列中取出一个顶点重复步骤一。
如果最后还有未删除顶点,则存在环,否则没有环。
2.深搜,找到已经访问的点,那就是环了,但注意,不能是一条边,如 1 - 2   2  - 1,这是一条边,没有环,解决的方法,就是把边标号,与上一次的边,不是同一边就可以了。
有向图求环,拓扑排序 强连通分量大于2就可以求出来了。
第一种方法,因为要删边,删点,复杂度,用set 优先队列优化,复杂度达到O(n * log(n));
在这一题,可以用类似1的算法,先把所有的度数小的先确定,度数大的边后确定,如果有解,肯定能找到,没有解,自然也找不到,复杂度为o(n * log(n));代码十分简单

#define N 400005
#define M 400005
#define maxn 205
#define MOD 1000000000000000007
int n,m,k,t;
bool vis[N],ans[N];
int v[M];
set<int> p[N];
priority_queue<pii> q;
int main()
{
    //freopen("in.txt", "r", stdin);
    //freopen("out.txt", "w", stdout);
     while(S2(n,m)!=EOF)
    {
        while(!q.empty())   q.pop();
        FI(n+1) p[i].clear();
        fill(vis,false);
        fill(ans,false);
        FI(2 * m + 2) v[i] = -1;
        FI(n){
            S(k);
            FJ(k){
                S(t);
                v[t + m] = i;
                p[i].insert(t+m);
            }
            q.push(mp(-p[i].size(),i));
        }
        bool flag = true;
        while(!q.empty()){
            int top = q.top().second;
            q.pop();
            if(vis[top]) continue;
            if(p[top].empty()){
                flag = false;
                break;
            }
            int x = *p[top].begin();
            vis[top] = true;
            ans[x] = true;
            int y = m + m - x;
            if(v[y] != -1 && !vis[v[y]]){
                p[v[y]].erase(y);
                q.push(mp(-p[v[y]].size(),v[y]));
            }
        }
        if(flag){
            printf("YES\n");
            For(i,m+1,m+m+1)
                if(!ans[i])  printf("0");
                else printf("1");
            printf("\n");
        }
        else {
            printf("NO\n");
        }
    }
    //fclose(stdin);
    //fclose(stdout);
    return 0;
}

第二种方法,用DFS找环,找到环的边后,用BFS确定,这个环所在的连通分量的所有边。如果,只出现x ~x,那么可以直接确定边,来推出整个连通分量其它的边。复杂度为o(n);

#define N 400005
#define M 400005
#define maxn 205
#define MOD 1000000000000000007
int n,m,k,t,all,from;
int v[M],ans[N];
bool vis[N],Dvis[N];
vector<int> p[N];
queue<int> q;
void BFS(){
    while(!q.empty()){
        int top = q.front();
        q.pop();
        FI(p[top].size()){
            int g = p[top][i];
            int gx = v[g];
            if(ans[g] == -1 && ans[m + m - g] == -1){
                ans[g] = 1;ans[m + m - g] = 0;
                if(!vis[gx]){
                    q.push(gx);
                    vis[gx] = true;
                }
            }
        }
    }
}
bool DFS(int x,int fa){
    all++;
    Dvis[x] = true;
    FI(p[x].size()){
        int g = p[x][i];
        int gx = v[g];
        if((g != fa && g != m + m - fa) && ans[g] == -1 && ans[m + m - g] == -1){
            if(Dvis[gx] || vis[gx]){
                from = g;
                return true;
            }
            else if(DFS(gx,g))
            return true;
        }
    }
    return false;
}
int main()
{
    //freopen("in.txt", "r", stdin);
    //freopen("out.txt", "w", stdout);
     while(S2(n,m)!=EOF)
    {
        FI(2 * m + 2) v[i] = -1,ans[i] = -1;
        fill(vis,false);
        fill(Dvis,false);
        FI(n){
            S(k);
            FJ(k){
                S(t);
                if(v[t+m] != -1){
                    vis[v[t+m]] = 1;
                    vis[i] = 1;
                    ans[t+m] = 1;
                }
                else
                    v[t + m] = i;
            }
        }
        For(i,m + 1,m + m + 1){
            if(v[i] >= 0 && v[m + m - i] >= 0){
                int s = v[i],e = v[m + m -i];
                p[s].push_back(m + m - i);
                p[e].push_back(i);
            }
            else if(v[i] >= 0 && v[m + m - i] == -1){
                ans[i] = 1;ans[m + m - i] = 0;vis[v[i]] = true;
            }
            else if(v[i] == -1 && v[m + m - i] >= 0){
                ans[m + m - i] = 1;ans[i] = 0;vis[v[m + m - i]] = true;
            }
        }
        while(!q.empty()) q.pop();
        FI(n){
            if(vis[i]){
                q.push(i);
            }
        }
        BFS();
        bool flag = true;
        FI(n){
            if(!vis[i] && !Dvis[i]){
                from = -1;all = 0;
                DFS(i,-1);
                if(all >= 2 && from == -1)
                {
                    flag = false;
                    break;
                }
                if(from != -1){
                    ans[from] = 1;ans[m + m - from] = 0;
                    while(!q.empty()) q.pop();
                    q.push(v[from]);
                    BFS();
                }
            }
        }
        if(flag){
            printf("YES\n");
            For(i,m+1,m+m+1)
                if(ans[i] == 1)  printf("1");
                else printf("0");
            printf("\n");
        }
        else {
            printf("NO\n");
        }
    }
    //fclose(stdin);
    //fclose(stdout);
    return 0;
}


你可能感兴趣的:(Codeforces Round #317 [AimFund Thanks-Round] (Div. 1) C. CNF 2 无向图找环)