【趣题&输入略坑】【并查集(带权)】NKOJ 3762 守夜人

8 篇文章 0 订阅
5 篇文章 0 订阅

NKOJ 3762 守夜人
时间限制 : - MS 空间限制 : 65536 KB
评测说明 : 时限1000ms

问题描述
鉴于john snow当选了新的守夜人总司令,艾里沙爵士感觉非常不爽,想搞点事情来难倒snow。艾里沙爵士告诉你有一个n项的序列X0,X1,X2…..Xn-1。(其中每一项均在int范围之内)但是你现在不知道其中的任何一项。艾里沙会逐步的告诉你一些信息并且问你一些问题。共有两种类型的信息和一种类型的询问。

I p v : tell you Xp = v

I p q v : tell you Xp XOR Xq = v

Q k p1 p2 … pk : 询问Xp1 XOR Xp2 XOR … XOR Xpk的值 k≤15

输入格式
会有多组测试数据但不会超过10组。
每一组数据以两个整数开始:n , Q(1 ≤ n ≤ 20, 000, 2 ≤ Q ≤ 40, 000)题目描述中的k是一个不大于15的整数。
最后一组数据为n==Q==0,不用进行运算。

输出格式
对于每组数据,输出第一行为数据的组数,接下来的每一行对应一次询问的答案。
如果根据前面给出的信息无法算出答案,则输出“I don’t know.”。
如果与已知信息冲突,输出“The first i facts are conflicting.”,并结束对于这一组数据的运算,其中i为这组数据出现过的的信息条数(不含询问,包含当前这一条信息)。

样例输入
2 6
I 0 1 3
Q 1 0
Q 2 1 0
I 0 2
Q 1 1
Q 1 0
3 3
I 0 1 6
I 0 2 2
Q 2 1 2
2 4
I 0 1 7
Q 2 0 1
I 0 1 8
Q 2 0 1
0 0

样例输出
Case 1:
I don’t know.
3
1
2
Case 2:
4
Case 3:
7
The first 2 facts are conflicting.

提示
注释:
鉴于两种I操作的输入比较麻烦,这里给出一种参考输入方法:
gets(s);
if(sscanf(s,”%d%d%d”,&a,&b,&v)==2)
//这一行输入了两个整数,要先除去行首的字母
{

}

来源 老王

思路:
并查集,将有关联的点加入一个集合,val[x]表示x^be[x].
特别的,建立一个虚拟点n,作为其所在集合的根,val[n]=0,即该集合中val[x]的值为x号本身。判断一个点是否已知,就看改点的集合根节点是否为n。
注意合并时,n优先作为父亲
对于Q,暴力枚举所有数,若已知,则^起来;若未知但be[x]出现次数为偶数,也可直接^起来,否则无解。

#include<cstdio> 
#include<iostream> 
#include<sstream> 
using namespace std; 
const int need=20004; 

int val[need],be[need],cn[need];
//........................................................... 
int getbe(int x)
{
    if(x!=be[x])
    {
        int t=getbe(be[x]);
        val[x]^=val[be[x]];
        return be[x]=t;
    }
    return x;
}
void addbe(int x,int fx,int y,int fy,int z)
{
    be[fx]=fy;
    val[fx]=val[x]^val[y]^z;
}
//........................................................... 
string c; 
int cnt,k[17],a; 

void in_() 
{ 
    cnt=0; 
    getline(cin,c,'\n'); 
    stringstream os(c); 
    while(os>>a)  k[++cnt]=a;
} 
//........................................................... 

int main() 
{ 
    //freopen("a.txt","r",stdin); 
    int n,m,f1,f2,tot=0,ans2;
    char t; 
    bool mark;
while(true) 
{ 
    scanf("%d%d",&n,&m); 
    if(n==0) return 0;
    printf("Case %d:\n",++tot); 
    mark=true;
    ans2=0;
    for(int i=0;i<=n;i++) be[i]=i,val[i]=0;
    for(int i=1,j;i<=m;i++) 
    { 
        while(true) 
        { 
            t=getchar(); 
            if(t=='I'||t=='Q') break; 
        } 
        in_(); 
        if(!mark) continue;
        if(t=='I') 
        { 
            ans2++;
            if(cnt==3)  
            {
                f1=getbe(k[1]),f2=getbe(k[2]);
                if(f1!=f2)//不在一个集合中,合并,若该集合所有节点已知,即有根节点为n,则n做合并后的根节点
                {
                    if(f1==n) addbe(k[2],f2,k[1],f1,k[3]);//有n就n做根节点
                    else addbe(k[1],f1,k[2],f2,k[3]);
                }
                else if((val[k[1]]^val[k[2]])!=k[3]) //若在同一集合,num[x]^num[y]=val[x]^val[fx]^val[y]^val[fy]=val[x]^val[y]
                {
                    printf("The first %d facts are conflicting.\n",ans2);
                    mark=false;
                }
            }
            else if(cnt==2)
            {
                f1=getbe(k[1]);
                if(f1!=n)//改点未知,改为已知,加入n集合中
                {
                    be[f1]=n;
                    val[f1]=k[2]^val[k[1]];//val[fx]=num[fx]=num[x]^val[x]
                }
                else if(val[k[1]]!=k[2]) 
                {
                    printf("The first %d facts are conflicting.\n",ans2);
                    mark=false;
                }
            }
        } 
        else if(t=='Q')
        {
            for(j=0;j<=n;j++) cn[j]=0;
            int ans=0;
            bool flag=true;
            for(j=2;j<=cnt;j++)
            {
                f1=getbe(k[j]);
                if(f1!=n) cn[f1]++;
                ans^=val[k[j]];
            }
            for(j=0;j<n;j++) 
             if(cn[j]&1) 
             {
                puts("I don't know.");
                flag=false;
                break;
             }
            if(flag) printf("%d\n",ans);
        }
    } 
} 
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值