uva Internet of Lights and Switches (异或运算+状态压缩)

题目: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;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值