题目:https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=13&page=show_contest&contest=350
题意:有N盏灯和M个开关,每一个开关控制多盏灯(比如N=4,"0011"就代表这个开关控制第3和第4盏灯),现在问你有多少种按开关的方法使得所有的灯都熄灭(每个开关只能按一次,并且按的开关的编号是连续的),另外所按的编号连续的开关的长度在[a,b]范围内。初始所有的灯都是开着的。
分析:比赛到最后20分钟的时候想出来了,没写出来,时间不够。。。。。首先可以知道选择的区间的异或和必须为全1,这样就保证了每一盏灯的状态被改变了奇数次。然后维护前缀异或和xor[1,i],找一个在这之前的前缀异或和xor[1,j](j<i),使得xor[1,i]^xor[1,j]为全1状态,那么[j+1,i]就是一个所求区间。怎么找到所有的xor[i,j]?用一个map即可,前缀异或和当key,所有等于key的前缀异或和的尾位置当值。然后用二分找到所有满足条件的区间就行。(另外:由于N<=50,01串可以直接转换成long long类型)
看了下别人的思路,还有一个小技巧:动态更新map里面的值,把不在范围内的值删掉,那就不用二分了。。。还可以用hash优化,不用map。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <vector>
using namespace std;
typedef long long LL;
int L,N,A,B;
char str[80];
map <LL ,vector <int > > mp;
LL toll(char str[])
{
LL ret=0,p=1;
for(int i=L-1;i>=0;i--)
{
if(str[i]=='1')
ret=ret+p;
p<<=1;
}
return ret;
}
int cnt(LL x,int pos)
{
vector <int > &vct=mp[x];
int down=0,mid,up=vct.size()-1,fd=-1;
while(down<=up)
{
mid=(down+up)>>1;
if(pos-vct[mid]<A)
up=mid-1;
else
{
down=mid+1;
if(mid>fd)
fd=mid;
}
}
down=0;
up=vct.size()-1;
int fd1=up+1;
while(down<=up)
{
mid=(down+up)>>1;
if(pos-vct[mid]>B)
down=mid+1;
else
{
up=mid-1;
if(mid<fd1)
fd1=mid;
}
}
if(fd1>fd)
return 0;
return fd-fd1+1;
}
int main()
{
int i,j,ncase=1;
while(scanf("%d%d%d%d",&L,&N,&A,&B)!=EOF)
{
mp.clear();
for(i=0;i<L;i++)
str[i]='1';
str[i]='\0';
LL f=toll(str),ans=0,x,xorsum=0;
for(i=1;i<=N;i++)
{
scanf("%s",str);
x=toll(str);
xorsum^=x;
if(xorsum==f && A<=i && i<=B)
ans++;
ans+=cnt(f^xorsum,i);
mp[xorsum].push_back(i);
}
printf("Case %d: %lld\n",ncase++,ans);
}
return 0;
}