题
有一个长度为
n
n
的数列 ,按顺序给定
m
m
个命题,第 个命题由参数
li,ri,odd/even
l
i
,
r
i
,
o
d
d
/
e
v
e
n
构成,表示这个命题为:
∑rij=liaj
∑
j
=
l
i
r
i
a
j
为 奇/偶 数。
请找到一个最小的
i
i
,使得第 个命题与前
i
i
个命题矛盾。找不到请输出“ORZZGG”(不含引号)。
范围:,
m≤20000
m
≤
20000
。
解
50分暴力,用map来模拟,代码如下:
#include <bits/stdc++.h>
using namespace std;
#define R register
#define Maxn 1000005
#define Maxm 20005
int n,m;
int Lp[Maxm<<2],Rp[Maxm<<2],cntl,cntr;
bool visl[Maxn],visr[Maxn];
char str[10];
map <int,map<int,int> > lrf;
inline bool insert(R int ll,R int rr,R int va)
{
if(lrf[ll][rr]) return (lrf[ll][rr] == va);
lrf[ll][rr] = va;
for (R int i=1;i<=cntr;++i)
{
R int tmp=lrf[rr+1][Rp[i]];
if(!tmp) continue;
if(!insert(ll,Rp[i],((va-1)^(tmp-1))+1)) return 0;
}
for (R int i=1;i<=cntl;++i)
{
R int tmp=lrf[Lp[i]][ll-1];
if(!tmp) continue;
if(!insert(Lp[i],rr,((va-1)^(tmp-1))+1)) return 0;
}
if(!visl[ll]) Lp[++cntl] = ll,visl[ll] = 1;
if(!visr[rr]) Rp[++cntr] = rr,visr[rr] = 1;
return 1;
}
int main()
{
scanf("%d %d",&n,&m);
for (R int i=1,l,r;i<=m;++i)
{
scanf("%d %d %s",&l,&r,str);
if(!insert(l,r,(str[0]=='o')+1))
{
printf("%d\n",i-1);
goto T;
}
// printf("%d\n",lrf[l][r]);
}
puts("ORZZGG");
T:
return 0;
}
正解:并查集+离散化
离散化之后,记
vali
v
a
l
i
为以该点为右端点的已经确定的最长区间的权值,这样记的话那么维护
vali
v
a
l
i
就是要靠右合并。
举个例子,对于两段,如下:(直接拿原来的样例吧)
a1 a2 a3 a4 a5 a6
a
1
a
2
a
3
a
4
a
5
a
6
命题依次为:
- 1 2 even
- 3 4 odd
- 5 6 even
- 1 6 even
如果我们直接维护:
- val2=val2⊕val1⊕even=0 v a l 2 = v a l 2 ⊕ v a l 1 ⊕ e v e n = 0 (加上前一段的和,然后在加命题的贡献)
- val4=val4⊕val3⊕odd=1 v a l 4 = v a l 4 ⊕ v a l 3 ⊕ o d d = 1
- val6=val6⊕val5⊕even=0 v a l 6 = v a l 6 ⊕ v a l 5 ⊕ e v e n = 0
发现问题了吗?相邻区间根本没合并!所以我们要在初始时把区间右端点统一+1(或者左端点统一-1),来让相邻的区间有公共点,这样才能合并。
- 1 3 even
- 3 5 odd
- 5 7 even
- 1 7 even
然后像上面那样:
- val3=val3⊕val1⊕even=0 v a l 3 = v a l 3 ⊕ v a l 1 ⊕ e v e n = 0
- val5=val5⊕val3⊕odd=1 v a l 5 = v a l 5 ⊕ v a l 3 ⊕ o d d = 1
- val7=val7⊕val5⊕even=1 v a l 7 = v a l 7 ⊕ v a l 5 ⊕ e v e n = 1
这样才对嘛~
接下来一个命题
- 1 7 even
我们发现在前面一连串的操作后这两个点应该已经连在一起了,因此我们应该用 带权并查集 维护这些点。然后怎样判断这个命题有没有违反规则呢?
很简单,我们已经知道了
val1
v
a
l
1
和
val7
v
a
l
7
,利用前缀和思想,中间这一段的权应该等于右边减左边(由于右边是开,不包括,所以左端点不用-1)。由于异或的加减统一性,这一段的奇偶性就是
val1⊕val7
v
a
l
1
⊕
v
a
l
7
。接下来比一比有没有相同就行了。
要特别注意的是,带权并查集的合并、路径压缩这样的东西要小心,要按照贡献的顺序合并,不能当做普通并查集。
代码见下:
#include <bits/stdc++.h>
using namespace std;
#define R register
#define Maxn 1000005
#define Maxm 20005
#define Map(_a) lower_bound(poi+1,poi+len+1,_a)-poi
int n,m,ll[Maxm],rr[Maxm],val[Maxm],val1[Maxm<<1];
char str[10];
int poi[Maxm<<1],len,fa[Maxm<<1];
int find(R int x)
{
if(fa[x] == x) return x;
R int tmp=find(fa[x]); val1[x]^=val1[fa[x]]; return fa[x]=tmp;
}
int main()
{
scanf("%d %d",&n,&m);
for(R int i=1;i<=m;++i)
{
scanf("%d %d %s",&ll[i],&rr[i],str); ++rr[i];
poi[i<<1] = rr[i],poi[(i<<1)-1] = ll[i],val[i]=(str[0]=='o');
}
sort(poi+1,poi+(m<<1)+1);
len = unique(poi+1,poi+(m<<1)+1)-(poi+1);
for (R int i=1;i<=len;++i) fa[i]=i;
for (R int i=1;i<=m;++i)
{
R int u=Map(ll[i]),v=Map(rr[i]);
find(u),find(v);
if(fa[u]^fa[v]) val1[fa[u]]=(val1[u]^val1[v]^val[i]),fa[fa[u]]=fa[v];
else
{
if(val1[u]^val1[v]^val[i])
{
printf("%d\n",i-1);
goto T;
}
}
}
puts("ORZZGG");
T:return 0;
}