CodeForces-776D The Door Problem

                                                   The Door Problem

做这个题做了几天,中间各种事耽搁,不过也总结了很多。

题意:n扇门,初始状态可能开着也可能关着,m个开关,每个开关可以改变若干个门的状态,每扇门只由两个开关控制,问是否存在一种方法把所有的门都打开。

因为做并查集碰到了个问题,于是队友甩给我这道题,说实话很难想到用并查集,看了一下别人思路才知道的。

把门作为边,连接两个开关,如果门是开着,所连的两个开关属于一个集合,同时开关,如果门是关着,两个开关只能选一个,所以属于不同的集合,问题就转化到了求这些开关是否能分成两个集合。

这就是并查集问题了,用类似POJ-1703的方法是MLE,很不明白为什么,因为之前做的是HDU-5971,用的种类并查集却RE了,我还怀疑是递归的问题,可那道题数据量很小,不应该有这样的问题的。做这个题用种类并查集MLE,也是莫名奇妙。

MLE代码:

void init()
{
    for(int i=1; i<=n; i++) f[i]=p[i]=i;
    memset(mp,0,sizeof(mp));
    tot=0;
}
int find(int x)
{
    return f[x]==x?x:f[x]=find(f[x]);
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        init();
        for(int i=1; i<=n; i++) scanf("%d",&a[i]);
        for(int i=1; i<=m; i++)
        {
            int k,x;
            scanf("%d",&k);
            for(int j=1; j<=k; j++)
            {
                scanf("%d",&x);
                if(!mp[x][0]) mp[x][0]=i;
                else mp[x][1]=i;
            }
        }
        int flag=0;
        for(int i=1; i<=n; i++)
        {
            int u=mp[i][0],v=mp[i][1];
            int f1=find(u),f2=find(v);
            if(a[i]) f[f1]=f2;
            else
            {
                if(p[v]==v) p[v]=u;
                if(p[u]==u) p[u]=v;
                int f3=find(p[u]),f4=find(p[v]);
                if(f1!=f4) f[f1]=f4;
                if(f2!=f3) f[f2]=f3;
            }
        }
        for(int i=1; i<=n&&!flag; i++)
            if(i!=p[i]&&find(i)==find(p[i])) flag=1;
        if(flag) puts("NO");
        else puts("YES");
    }
    return 0;
}

AC代码:

void init()
{
    for(int i=1; i<N; i++) f[i]=p[i]=i;
    memset(mp,0,sizeof(mp));
}
int find(int x)
{
    return f[x]==x?x:f[x]=find(f[x]);
}
void merge(int u,int v)
{
    int f1=find(u),f2=find(v);
    f[f1]=f2;
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        init();
        for(int i=1; i<=n; i++) scanf("%d",&a[i]);
        for(int i=1; i<=m; i++)
        {
            int k,x;
            scanf("%d",&k);
            for(int j=1; j<=k; j++)
            {
                scanf("%d",&x);
                if(!mp[x][0]) mp[x][0]=i;
                else mp[x][1]=i;
            }
        }
        int flag=0;
        for(int i=1; i<=n; i++)
        {
            int u=mp[i][0],v=mp[i][1];
            int f1=find(u),f2=find(v);
            int f3=find(u+m),f4=find(v+m);
            if(a[i])
            {
//                f[f1]=f2;
                 merge(u,v);
                 merge(u+m,v+m);
            }
            else
            {
//                 f[f1]=f4;
//                 f[f2]=f3;
                 merge(u,v+m);
                 merge(v,u+m);
            }
        }
        for(int i=1; i<=m&&!flag; i++)
            if(find(i)==find(i+m)) flag=1;
        if(flag) puts("NO");
        else puts("YES");
    }
    return 0;
}

很不明白为什么放在merge函数里分别合并就对了,但直接合并就MEL。队友说直接合并相当于把树压缩了,然后下次查找的时候不断的压栈,导致内存超限,还是不太明白。

不过同一个集合合并的时候也要将其对立合并,这里也不是很明白。

关键就在这个对立问题上,在做POJ-1703的时候,直接用第一份代码的方式就可以AC了,直接将其对立存起来,合并只需将自己与对立的对立合并就行了,但这里涉及到同集合合并问题,用相同的放法就行不通了。如果所有的关系都是对立的也许可以。第二种写法在很多种类并查集的题都可以过,所以总结是POJ/HDU的数据出水,误人啊。。



CodeForces - 616D是一个关于找到一个序列中最长的第k好子段的起始位置和结束位置的问题。给定一个长度为n的序列和一个整数k,需要找到一个子段,该子段中不超过k个不同的数字。题目要求输出这个序列最长的第k好子段的起始位置和终止位置。 解决这个问题的方法有两种。第一种方法是使用尺取算法,通过维护一个滑动窗口来记录\[l,r\]中不同数的个数。每次如果这个数小于k,就将r向右移动一位;如果已经大于k,则将l向右移动一位,直到个数不大于k。每次更新完r之后,判断r-l+1是否比已有答案更优来更新答案。这种方法的时间复杂度为O(n)。 第二种方法是使用枚举r和双指针的方法。通过维护一个最小的l,满足\[l,r\]最多只有k种数。使用一个map来判断数的种类。遍历序列,如果当前数字在map中不存在,则将种类数sum加一;如果sum大于k,则将l向右移动一位,直到sum不大于k。每次更新完r之后,判断i-l+1是否大于等于y-x+1来更新答案。这种方法的时间复杂度为O(n)。 以上是两种解决CodeForces - 616D问题的方法。具体的代码实现可以参考引用\[1\]和引用\[2\]中的代码。 #### 引用[.reference_title] - *1* [CodeForces 616 D. Longest k-Good Segment(尺取)](https://blog.csdn.net/V5ZSQ/article/details/50750827)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Codeforces616 D. Longest k-Good Segment(双指针+map)](https://blog.csdn.net/weixin_44178736/article/details/114328999)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值