Ural 1003
Parity 奇偶
Time Limit: 2.0 second
Memory Limit: 16MB
Background
偶尔你跟朋友玩以下的游戏:你的朋友写下一串包含1和0的数串让你猜,你可以从中选择一个连续的子串(例如其中的第3到第5个数字)问他,该子串中包含了奇数个还是偶数个1,他会回答你的问题,然后你可以问其他子串,等等。
Problem
你的任务是猜出完整的子串。你怀疑你的朋友的答案可能有错,你想质疑他的错误。 所以要写这个程序帮忙验证。程序接受一系列你的问题以及你朋友给出的答案。程序的目标是找出错误的第一个答案,也就是某一子串满足先前的所有答案,而不满足当前这个答案。
Input
输入包含多组测试数据。 首行为整个数串的长度,小于或等于1000000000。第二行为一个正整数,表示问题及答案数,小于或等于5000。之后每行表示问题及答案。格式:2个整数(即子串的第一个及最后一个数位),答案为一个单词:'even'(偶),或'odd'(奇),即选择的子串中1的个数是奇数个,则答案为odd,否则为even。最后以-1结束。
Output
输出x,x表示存在01数串满足前x个奇偶条件,但不满足第x+1个条件。。假如存在满足所有条件的01串,则输出的数字为问题总数。
Sample Input
10 5 1 2 even 3 4 odd 5 6 even 1 6 even 7 10 odd -1
Sample Output
3
#include <cstdio>
#include <map>
#include <cstdlib>
#ifdef Debug
#include <iostream>
#endif
using std::map;
long n;
long m;
std::map<long,long> fa;
std::map<long,long> col;
long l[5010];
long r[5010];
long w[5010];
struct node
{
long l;
long r;
long w;
node(long _l,long _r,long _w):l(_l),r(_r),w(_w){}
node(){}
}dm[5010];
long getroot(long l)
{
if (fa[l]==l) return l;
long tmp = fa[l];
fa[l] = getroot(tmp);
col[l] = (col[l]^col[tmp]);
return fa[l];
}
void merge(long a,long b,long c)
{
// getroot(a);
// col[a] = (c^col[getroot(b)]);
// fa[fa[a]] = fa[b];
getroot(a);
getroot(b);
long tmp = col[a];
col[a] = c^col[b];
col[fa[a]] = col[a]^tmp;
getroot(a);
fa[fa[a]] = fa[b];
}
int main()
{
freopen("parity.in","r",stdin);
freopen("parity.out","w",stdout);
freopen("err","w",stderr);
while (1)
{
scanf("%ld",&n);
if (n == -1) break;
scanf("%ld",&m);
// for (long i=1;i<m*2;i++)
// {
// col[i] = 0;
// fa[i] = i;
// }
for (long i=1;i<m+1;i++)
{
char t[5];
scanf("%ld%ld",l+i,r+i);
scanf("%s",t);r[i]++;
fa[l[i]] = l[i];
fa[r[i]] = r[i];
col[l[i]] = col[r[i]] = 0;
if (t[0] == 'e')
{
w[i] = 0;
node tmp(l[i],r[i],0);
dm[i] = tmp;
}
else
{
w[i] = 1;
node tmp(l[i],r[i],1);
dm[i] = tmp;
}
}
bool ok = false;
for (long i=1;i<m+1;++i)
{
// #ifdef Debug
// std::cerr << "551 " << fa[551] << "\n";
// #endif
if (l[i]<0||r[i]-1>n)
{
printf("%ld",i-1);
return 0;
}
if (getroot(l[i]) == getroot(r[i]))
{
#ifdef Debug
std::cerr << i << " " << fa[l[i]] << " " << fa[r[i]] << std::endl;
#endif
if ((col[r[i]]^col[l[i]])!=w[i])
{
ok = true;
printf("%ld\n",i-1);
#ifdef Debug
std::cerr << " a";
#endif
break;
}
}
else
{
merge(l[i],r[i],w[i]);
}
}
if (!ok) printf("%ld",m);
}
return 0;
}
标程:
#include<iostream>
#include<string>
const int BLOCK=10000;
const int HASHTHING=6000;
int hashTable[20000];
int father[20000]; //0..9999 , 10000..19999
int Hash(int number)
{
int pos=number % HASHTHING;
while(hashTable[pos]!=-1 && hashTable[pos]!=number) pos=(pos+1)%HASHTHING;
hashTable[pos]=number;
return pos;
}
int Find(int x)
{
int root=x;
while(root!=father[root]) root=father[root];
while(x!=root)
{
int temp=father[x];
father[x]=root;
x=temp;
}
return root;
}
void Union(int x,int y)
{
int p=Fnd(x);
int q=Find(y);
if (p!=t)
father[p]=q;
}
int main()
{
while(true)
{
int len;
std::cin>>len;
if(len==-1) break;
int n;
std::cin>>n;
memset(hashTable,0xff,sizeof(hashTable));
for(int i=0;i<20000;++i) father[i]=i;
int count;
for(count=1;count<=n;++count)
{
int a,b;
std::string signal;
std::cin>>a>>b>>signal;
bool even=(signal=="even");
a=Hash(a-1);
b=Hash(b);
if(even)
{
if(Find(a)==Find(b+BLOCK)) break;
Union(a,b);
Union(a+BLOCK,b+BLOCK);
}else{ //odd
if(Find(a)==Find(b)) break;
Union(a,b+BLOCK);
Union(a+BLOCK,b);
}
}
int ans=count-1;
{
int tempa,tempb;std::string tempstr;
for(++count;count<=n;++count)
std::cin>>tempa>>tempb>>tempstr;
}
std::cout<<ans<<std::endl;
}
return 0;
}
其实有一个点没有过,但是其实答案有错呀,给出的区间右界大于最右边了。
本题我采用的方法不是最好的,最好的可以参考标程。都是并查集,但是我的方法比较麻烦。
讲我的方法,我的是用了类似以前OJ讲的,向量的思想。
比较简单,因为判断一个区间的1的奇偶个数,实际上必须知道多个区间(这几个区间相加等于此区间,但不是求并,只能是相加)的奇偶性。
也就是说,把这个区间划分成很多个区间,即如果某个区间确定了,必定是这两个点都在一个集合中。
把每个区间的奇偶性求异或(0表示相同,1表示相反,col[i]表示此点和他的根节点的关系)
(由于他给的是闭区间,所以把它转化成左开右闭的区间,即r+1,就行了)
当压缩路径的时候,就把它的col和更新后的父亲的col异或。
当合并的时候,就先压缩路径,col[fa[a]] = col[a]^w^col[b]。之后压缩的时候自动会更新a的col