Poj 2723 Go Deeper【2-SAT-----Tarjan强连通+二分】

Go Deeper

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 2860    Accepted Submission(s): 916

Problem Description

Here is a procedure's pseudocode:

go(int dep, int n, int m)
begin
output the value of dep.
if dep < m and x[a[dep]] + x[b[dep]] != c[dep] then go(dep + 1, n, m)
end

In this code n is an integer. a, b, c and x are 4 arrays of integers. The index of array always starts from 0. Array a and b consist of non-negative integers smaller than n. Array x consists of only 0 and 1. Array c consists of only 0, 1 and 2. The lengths of array a, b and c are m while the length of array x is n. Given the elements of array a, b, and c, when we call the procedure go(0, n, m) what is the maximal possible value the procedure may output?

Input

There are multiple test cases. The first line of input is an integer T (0 < T ≤ 100), indicating the number of test cases. Then T test cases follow. Each case starts with a line of 2 integers n and m (0 < n ≤ 200, 0 < m ≤ 10000). Then m lines of 3 integers follow. The i-th(1 ≤ i ≤ m) line of them are ai-1 ,bi-1 and ci-1 (0 ≤ ai-1, bi-1 < n, 0 ≤ ci-1 ≤ 2).

Output

For each test case, output the result in a single line.

Sample Input

3

2 1

0 1 0

2 1

0 0 0

2 2

0 1 0

1 1 2

Sample Output

1

1

2

Author

CAO, Peng

Source

2010 Asia Chengdu Regional Contest

 

 题目大意:

 给你四个数组a【】,b【】,c【】,x【】。其中a,b数组的值小于n,c数组的值范围是0,1,2。x数组的值范围是0,1

然后有一个递归函数。

go(int dep, int n, int m)
begin
output the value of dep.
if dep < m and x[a[dep]] + x[b[dep]] != c[dep] then go(dep + 1, n, m)
end

问,能够输出的最大的dep为多少。


思路:


概述:

相信做过2-sat的题目的小伙伴们,都知道2-sat题目的概述:

一共有n个党派,每个党派有两个人,每个党派之间的人会有矛盾关系,问是否能够选出n个人,使得这些人在场没有矛盾关系。

那么我们做这类题的核心思想就锁定到转化难题到这样的基础问题上,相信大家就会建边和解题了。

1、对于公式的分析:

①首先,输入进来的值是a,b,c。也就是说,对于递归的每一层的c【dep】,a【dep】,b【dep】的值都是确定的,只要我们对x【】数组找到一种合理赋值即可。而且x【】只能取0或者1,这个时候我们抽象化x【】=0为2-sat中的a,x【】=1为2-sat中的!a,这个时候,我们就可以理解为一共这个图中有n个点。那么x【i】=0的节点我们设定为i,x【i】=1的节点我们设定为i+n即可。

那么i就表示第i个党派的0号人,i+n就表示第i个党派的1号人。

②公式中c【dep】的值就是让我们寻找矛盾边的关键。

如果c【dep】==0,那么就表示x【a【dep】】和x【b【dep】】都不能为0,那么就是说,a【dep】和b【dep】有矛盾。

如果c【dep】==1,那么就表示x【a【dep】】为1的时候x【b【dep】】不能为0,同理,x【a【dep】】为0的时候,x【b【dep】】不能为1,也就是说a【dep】+n和b【dep】有矛盾,而且a【dep】和b【dep】+n有矛盾。

如果c【dep】==2,那么就表示x【a【dep】】和x【b【dep】】都不能为1,那么就是说,a【dep】+n和b【dep】+n有矛盾。

③既然找到了所有的矛盾边,那么建边问题也就解决了,对于a,b有矛盾的边,要建立对应的两条边:(a,!b)(b,!a)。我们根据这个原理,建边。


以上,我们将问题转化到了2-sat的基础问题上来,建边的问题也就搞定了。


2、对于题目解法的分析:

正常的思路应该是这样的:

①i=0

②建m=i时的边

③搞一下强连通然后判断是否符合2-sat。如果符合,跳④,如果不符合,结束算法。

④i++,跳②

然而m比较大,而且搞强连通的复杂度为n+m,然后我们对m进行枚举,总时间复杂度大概为:O((n+m)*m)。其中m==10000,必然超时。


所以这个时候我们就要优化算法:

考虑到这个问题的递增性,我们可以使用二分的算法来优化时间复杂度。

这样我们就从建一条边搞一发强连通,变成了建一堆边搞一发强连通,时间复杂度降低大概为:O((n+m)*logm)


3、实现代码,完成AC!


AC代码:


#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
int head[100000];
struct EdgeNode
{
    int to;
    int flag;
    int next;
}e[100000];
int a[10050];
int b[10050];
int c[10050];
int vis[10050];
int low[10050];
int dfn[10050];
int color[10050];
int stack[100500];
int cont,n,m,sig,tt,cnt;
void add(int from,int to)
{
    e[cont].to=to;
    e[cont].next=head[from];
    head[from]=cont++;
}
void Tarjan(int u)
{
    vis[u]=1;
    stack[++tt]=u;
    low[u]=dfn[u]=cnt++;
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        int v=e[i].to;
        if(vis[v]==0)Tarjan(v);
        if(vis[v]==1)low[u]=min(low[u],low[v]);
    }
    if(dfn[u]==low[u])
    {
        sig++;
        do
        {
            color[stack[tt]]=sig;
            vis[stack[tt]]=-1;
        }
        while(stack[tt--]!=u);
    }
}
int Slove()
{
    tt=-1;
    sig=0;
    cnt=1;
    memset(stack,0,sizeof(stack));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(color,0,sizeof(color));
    memset(vis,0,sizeof(vis));
    for(int i=0;i<n*2;i++)
    {
        if(vis[i]==0)
        {
            Tarjan(i);
        }
    }
    for(int i=0;i<n;i++)
    {
        if(color[i]==color[i+n])return 0;
    }
    return 1;
}
void erfen()
{
    int l=0;
    int r=m-1;
    int mid;
    while(r-l>=0)
    {
        mid=(l+r)/2;
        cont=0;
        memset(head,-1,sizeof(head));
        for(int i=0;i<=mid;i++)
        {
            int dep=i;
            if(c[i]==0)
            {
                add(a[dep],b[dep]+n);
                add(b[dep],a[dep]+n);
            }
            if(c[i]==1)
            {
                add(a[dep],b[dep]);
                add(b[dep],a[dep]);
                add(a[dep]+n,b[dep]+n);
                add(b[dep]+n,a[dep]+n);
            }
            if(c[i]==2)
            {
                add(a[dep]+n,b[dep]);
                add(b[dep]+n,a[dep]);
            }
        }
        if(Slove()==0)//左边
        {
            r=mid-1;
        }
        if(Slove()==1)//右边
        {
            l=mid+1;
        }
    }
    printf("%d\n",l);
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        for(int i=0;i<m;i++)
        {
            scanf("%d%d%d",&a[i],&b[i],&c[i]);
        }
        int i;
        erfen();
    }
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值