目录见右上
站内文章欢迎各位转载,但是请注明出处(毕竟码字很辛苦哈)。

主题

Toosay

原题:(权限限制没有链接)

题目描述

在一个遥远的村庄,生活着淳朴自然的村民们,比如zd、crt、liaoy、pb等等淳朴的村民。

每天早上起来,他们就十分关心别人昨夜与周公做了什么,于是他们开始互相问话。可是作为一名淳朴的村民,每天要干的事情很多,如果每天起床就要记下自己问过谁了,还有谁没问,实在有些费劲哈。于是机智的pb想出了一个有趣的方法:

起床后大家排成一列,每个人都要把这队列中所有的人(包括自己)都问完,这样就不用再用脑子去记了。

可是同样机智的zd发现一个问题:刚刚liaoy跑过来问了我,等下我还要跑过去再问他一遍,这不是浪费体力啊。为什么不一起问呢?

这时机智的crt想到了一个方法:每个人都只问排在他后面的人(包括自己),这样就避免了多跑路。

于是这个问题得到了解决。那么真正的问题来了:

每个村民身上都带有一个数字(可以相同),然后每个村民去问话的路上(沿着队伍往后走)都会想把看到的数字(一开始会先看看自己的数字)记下来,可是村民太多了,想记下来实在费劲。于是机智的liaoy说:可以把每次看到的数和自己已经记下的数xor一下,更新自己记下的数。此时从这个村民到后面一个村民的所有数字xor起来便是这次问话的xor数字价值了。

这是一个好办法,为了提高准确性,于是哈某援例,说不如再记下and和、or和。为了判断某一个人是否和所有人(包括自己)都互相问过了。让你输出这些所有问话的xor数字价值之和,and数字价值之和,or数字价值之和。

特殊的,一个村民问自己时,并不需要xor、and、or俩次。因为他只会看一眼。(ps:为什么要问自己啊?因为他怕不记得自己的梦了啊。)

输入输出格式

输入格式:

第一行一个正整数n。

第二行为n个正整数a,第i个数,即第i个村民身上的数。

输出格式: 

第一行xor 和

第二行and和

第三行or和

输入输出样例

输入样例#1:

3 1 2 3

输出样例#1:

10 8 15

 说明 

比如村民1跑去问村民3,那么此次xor数字价值:1 xor 2 xor 3=0。

村民1问自己,此次xor数字价值 :1。

ansXor=1+2+3+1xor2+2xor3+1xor2xor3=1+2+3+3+1+0=10

ansAnd=1+2+3+1and2+2and3+1and2and3=1+2+3+0+2+0=8

ansOr=1+2+3+1or2+2or3+1or2or3=1+2+3+3+3+3=15

数据范围:n<=10^6 a<2^20


个人解法:

题目是求一个序列的子序列的And、Or、Xor和。

最初没有什么想法,只想到一个O(n^2)的暴力。

枚举端点O(n^2),剩下的就是O(1)计算答案了。

Xor支持区间减法,于是我们可以做一个前缀和。而And与Or仅仅支持区间加法,不支持区间减法,怎么办呢……

突然发现And与Or都是基于位上的0与1的。

想到某锟前几天讲的拆位。因为位运算的位之间相互不影响,于是我们分别对每一位考虑。如果我们知道这一个区间在这一位上有多少个1(也就知道了有多少个0),于是就知道了这一段区间在这一位上的And与Or的值。

而前缀和是支持区间减法的。于是就用数组记录每一位上的前缀和就好了。

这样就可以O(n^2)得到答案了。


代码如下:

const

shl1:array[0..22] of longint=(1,2,4,8,16,32,64,128,256,512,1024,2048,4096,

  8192,16384,32768,65536,131072,262144,524288,1048576,2097152,4194304);//储存2的i次方

//xor can used A[i] (by Xorsum[i])

//And can used [0..20] of 0/1 (by [0..20] Andsum)

//Or can used [0..20] od 0/1 (by [0..20] Orsum)


var

A,Xorsum:array[0..1000010] of longint;

Seat:array[0..1000010,0..22] of longint;//Seat记录前面有某一位上的前缀和

Temp:array[0..22] of longint;

Ans:array[1..3] of int64;//1 is xor,2 is And,3 is Or

n,i,j,len,block,Done,Andtmp,Ortmp:longint;


begin

read(n);


for i:=1 to n do//处理数字

begin

  read(A[i]);

  for j:=1 to 3 do

   inc(Ans[j],A[i]);

  Xorsum[i]:=A[i] xor Xorsum[i-1];

  len:=0;

  fillchar(Temp,sizeof(Temp),0);

  while A[i]<>0 do

  begin

   if A[i] and 1=1 then Temp[len]:=1;

   A[i]:=A[i] shr 1;

   inc(len);

  end;

  for len:=0 to 22 do

   Seat[i,len]:=Seat[i-1,len]+Temp[len];

end;


for len:=2 to n do//枚举端点

  for i:=1 to n do

  begin

   j:=i+len-1;

   if j>n then break;

   inc(Ans[1],Xorsum[j] xor Xorsum[i-1]);

   Andtmp:=0;

   Ortmp:=0;

   for block:=0 to 22 do

   begin

    Done:=Seat[j,block]-Seat[i-1,block];

    if Done>0 then inc(Ortmp,shl1[block]);

    if Done=j-i+1 then inc(Andtmp,shl1[block]);

   end;

   inc(Ans[2],Andtmp);

   inc(Ans[3],Ortmp);

  end;


writeln(Ans[1]);

writeln(Ans[2]);

writeln(Ans[3]);

end.


(tm这个暴力有鬼用啊)

嗯这个暴力虽然不能过全部数据,但是也在考场上启发了我。

(然而考场上想出很好的解法时,只有0.5hour的时间,啥也写不出了)

继续考虑拆位。接下来的讨论对于一个位上的0/1讨论。

思考位运算的法则。

对于And而言,只有一个区间全部是1的时候,这一位的And才会是1。如果我们知道了有多少个区间全部为1,那么也就知道了这一位对And的贡献了。

这是可以O(n)扫一遍出来的。

接下来考虑Or,只要一个区间有一个1,这一位的Or就会是1。如果我们知道了有多少个区间全部为0,那么也就知道了有多少个区间有一位是1,也知道这一位对Or的贡献了。

这个和And一样扫一边就可以了。

接下来考虑Xor。如果一个区间有奇数个1,这一位的Xor就会是1;如果一个区间有偶数个1,这一位的Xor就会是0。

于是我们考虑从后面开始扫描。假设F[i]表示以第i位为左端点的区间中,有奇数个1的区间个数。那么F[i]=n-i+1-F[j],其中j为i往右边数第一个1的位置。

这样就可以O(n)扫出来了。


代码如下:

const

shl1:array[0..22] of longint=(1,2,4,8,16,32,64,128,256,512,1024,2048,4096,

  8192,16384,32768,65536,131072,262144,524288,1048576,2097152,4194304);

All=20;

//xor can used A[i] (by Xorsum[i])

//And can used [0..20] of 0/1 (by [0..20] Andsum)

//Or can used [0..20] od 0/1 (by [0..20] Orsum)


var

A:array[1..1000040,0..All] of Byte;

F:array[1..1000040,0..All] of longint;

n,i,w,l,last,j:longint;

Ans,sum,len:int64;


begin

read(n);//数据处理

for i:=1 to n do

begin

  read(w);

  l:=0;

  while w<>0 do

  begin

   A[i,l]:=w and 1;

   w:=w shr 1;

   inc(l);

  end;

end;


 //Xor运算

Ans:=0;

for i:=0 to All do

begin

  last:=n+1;

  sum:=0;

  for j:=n downto 1 do

  begin

   if A[j,i]=1 then

   begin

    F[j,i]:=n-j+1-F[last,i];

    last:=j;

   end

    else F[j,i]:=F[last,i];

   inc(sum,F[j,i]);

  end;

  inc(Ans,sum*shl1[i]);

end;

writeln(Ans);//输出


 //And运算

Ans:=0;

for i:=0 to All do

begin

  last:=-1;

  sum:=0;

  A[n+1,i]:=0;

  for j:=1 to n+1 do

   if A[j,i]=1 then

   begin

    if last=-1 then last:=j;

   end

    else

   if last<>-1 then

   begin

    len:=j-last;

    inc(sum,len*(len+1) shr 1);

    last:=-1;

   end;

  inc(Ans,sum*shl1[i]);

end;

writeln(Ans);//输出


 //Or运算

Ans:=0;

for i:=0 to All do

begin

  last:=-1;

  sum:=0;

  A[n+1,i]:=1;

  for j:=1 to n+1 do

   if A[j,i]=0 then

   begin

    if last=-1 then last:=j;

   end

    else

   if last<>-1 then

   begin

    len:=j-last;

    inc(sum,len*(len+1) shr 1);

    last:=-1;

   end;

  sum:=int64(n)*int64(n+1) shr 1-sum;

  inc(Ans,sum*shl1[i]);

end;

writeln(Ans);//输出


end.


位运算劲啊。

评论
©主题 —— Powered by LOFTER