1. 程式人生 > >uva Internet of Lights and Switches (異或運算+狀態壓縮)

uva Internet of Lights and Switches (異或運算+狀態壓縮)

題意:有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;
}