BZOJ 2844: albus就是要第一個出場 高斯消元 線性基
2844: albus就是要第一個出場
Time Limit: 6 Sec Memory Limit: 128 MBSubmit: 1416 Solved: 598
[Submit][Status][Discuss]
Description
已知一個長度為n的正整數序列A(下標從1開始), 令 S = { x | 1 <= x <= n }, S 的冪集2^S定義為S 所有子集構成的集合。定義對映 f : 2^S -> Zf(空集) = 0f(T) = XOR A[t] , 對於一切t屬於T現在albus把2^S中每個集合的f值計算出來, 從小到大排成一行, 記為序列B(下標從1開始)。 給定一個數, 那麼這個數在序列B中第1Input
第一行一個數n, 為序列A的長度。接下來一行n個數, 為序列A, 用空格隔開。最後一個數Q, 為給定的數.
Output
共一行, 一個整數, 為Q在序列B中第一次出現時的下標模10086的值.Sample Input
31 2 3
1
Sample Output
3樣例解釋:
N = 3, A = [1 2 3]
S = {1, 2, 3}
2^S = {空, {1}, {2}, {3}, {1, 2}, {1, 3}, {2, 3}, {1, 2, 3}}
f(空) = 0
f({1}) = 1
f({2}) = 2
f({3}) = 3
f({1, 2}) = 1 xor 2 = 3
f({1, 3}) = 1 xor 3 = 2
f({2, 3}) = 2 xor 3 = 1
f({1, 2, 3}) = 0
所以
B = [0, 0, 1, 1, 2, 2, 3, 3]
HINT
資料範圍:
1 <= N <= 10,0000
其他所有輸入均不超過10^9
學習了一下線性基
先說題解
高仿PoPoQQQ一發
題目大意:
給定一個n個數的集合S和一個數x,求x在S的2^n個子集從大到小的異或和序列中最早出現的位置
BJ:理解題意靠樣例
首先我們求出線性基
我們會得到一些從大到小排列的數和一堆0 記錄0的個數
不考慮0,看前面的數
由於線性基的性質
我們直接貪心從大到小列舉 若當前異或和異或這個值小於Q則取這個數
(注意^不要寫成+或者| 本蒟蒻已經因為這個WA了兩道題了)(您還是沒有BJ蒟蒻啊 哈哈)
然後我們通過每個數取不取可以得到一個01序列
這個序列就是通過異或可以得到的小於Q的數的數量的二進位制
比如線性基是8 4 2 Q=13
取完8和4之後無法法取2 那麼得到的01序列就是110 即6
BJ:這個要自己YY一下
然後我們再加上空集的0
然後考慮後面的0
設有m個0
那麼每個數出現的次數為2^m 乘起來後+1就是答案
一個細節就是當Q=0的時候計算小於Q的數的數量時不要算上0
不懂的可以自己測測0
程式碼(之後有線性基學習筆記):
#include<cmath>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<complex>
#include<iostream>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<string>
#include<bitset>
#include<queue>
#include<set>
#include<map>
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch<='9'&&ch>='0'){x=10*x+ch-'0';ch=getchar();}
return x*f;
}
inline void print(int x)
{if(x<0)putchar('-'),x=-x;if(x>=10)print(x/10);putchar('0'+x%10);}
const int N=100100,mod=10086;
int n,m,a[N];
void gauss()
{
int k=0;
for(int i,j=1<<30;j;j>>=1)
{
for(i=1+k;i<=n;++i)if(a[i]&j)break;//cout<<a[i]<<endl;
if(i==n+1)continue;
swap(a[i],a[++k]);
for(int i=1;i<=n;i++)
if(i!=k&&(a[i]&j))a[i]^=a[k];
}
m=n-k;n=k;//cout<<n<<endl;
}
inline int qpow(int x,int y)
{
int res=1;
while(y)
{
if(y&1)res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
int main()
{
n=read();
for(int i=1;i<=n;i++)a[i]=read();
gauss();int now=0,ans=0;int q=read();//cout<<q<<endl;
for(int i=1;i<=n;i++)
if((a[i]^now)<q)now^=a[i],(ans+=qpow(2,n-i))%=mod;//cout<<ans<<endl;
if(q){++ans;ans%=mod;}ans*=qpow(2,m),ans%mod;ans++;ans%=mod;
print(ans);puts("");return 0;
}
/*
3
1 2 3
1
3
*/
再粘篇部落格
話說BJ可真是個轉載狂魔
概述
網上好像沒有什麼關於線性基的資料…
定義
設數集T的值域範圍為[1,2n−1]。
T的線性基是T的一個子集A={a1,a2,a3,...,an}。
A中元素互相xor所形成的異或集合,等價於原數集T的元素互相xor形成的異或集合。
可以理解為將原數集進行了壓縮。
性質
1.設線性基的異或集合中不存在0。
2.線性基的異或集合中每個元素的異或方案唯一,其實這個跟性質1是等價的。
3.線性基二進位制最高位互不相同。
4.如果線性基是滿的,它的異或集合為[1,2n−1]。
5.線性基中元素互相異或,異或集合不變。
維護
插入
如果向線性基中插入數x,從高位到低位掃描它為1的二進位制位。
掃描到第i時,如果ai不存在,就令ai=x,否則x=x⊗ai。
x的結局是,要麼被扔進線性基,要麼經過一系列操作過後,變成了0。
bool insert(long long val)
{
for (int i=60;i>=0;i--)
if (val&(1LL<<i))
{
if (!a[i])
{
a[i]=val;
break;
}
val^=a[i];
}
return val>0;
}
合併
將一個線性基暴力插入另一個線性基即可。
L_B merge(const L_B &n1,const L_B &n2)
{
L_B ret=n1;
for (int i=0;i<=60;i++)
if (n2.d[i])
ret.insert(n2.d[i]);
return ret;
}
查詢
存在性
如果要查詢x是否存於異或集合中。
從高位到低位掃描x的為1的二進位制位。
掃描到第i位的時候x=x⊗ai
如果中途x變為了0,那麼表示x存於線性基的異或集合中。
最大值
從高位到低位掃描線性基。
如果異或後可以使得答案變大,就異或到答案中去。
long long query_max()
{
long long ret=0;
for (int i=60;i>=0;i--)
if ((ret^d[i])>ret)
ret^=d[i];
return ret;
}
最小值
最小值即為最低位上的線性基。
long long query_min()
{
for (int i=0;i<=60;i++)
if (d[i])
return d[i];
return 0;
}
k小值
根據性質3。
我們要將線性基改造成每一位相互獨立。
具體操作就是如果i<j,aj的第i位是1,就將aj異或上ai。
經過一系列操作之後,對於二進位制的某一位i。只有ai的這一位是1,其他都是0。
所以查詢的時候將k二進位制拆分,對於1的位,就異或上對應的線性基。
最終得出的答案就是k小值。
void rebuild()
{
for (int i=60;i>=0;i--)
for (int j=i-1;j>=0;j--)
if (d[i]&(1LL<<j))
d[i]^=d[j];
for (int i=0;i<=60;i++)
if (d[i])
p[cnt++]=d[i];
}
long long kthquery(long long k)
{
int ret=0;
if (k>=(1LL<<cnt))
return -1;
for (int i=60;i>=0;i--)
if (k&(1LL<<i))
ret^=p[i];
return ret;
}
模板
struct L_B{
long long d[61],p[61];
int cnt;
L_B()
{
memset(d,0,sizeof(d));
memset(p,0,sizeof(p));
cnt=0;
}
bool insert(long long val)
{
for (int i=60;i>=0;i--)
if (val&(1LL<<i))
{
if (!d[i])
{
d[i]=val;
break;
}
val^=d[i];
}
return val>0;
}
long long query_max()
{
long long ret=0;
for (int i=60;i>=0;i--)
if ((ret^d[i])>ret)
ret^=d[i];
return ret;
}
long long query_min()
{
for (int i=0;i<=60;i++)
if (d[i])
return d[i];
return 0;
}
void rebuild()
{
for (int i=60;i>=0;i--)
for (int j=i-1;j>=0;j--)
if (d[i]&(1LL<<j))
d[i]^=d[j];
for (int i=0;i<=60;i++)
if (d[i])
p[cnt++]=d[i];
}
long long kthquery(long long k)
{
int ret=0;
if (k>=(1LL<<cnt))
return -1;
for (int i=60;i>=0;i--)
if (k&(1LL<<i))
ret^=p[i];
return ret;
}
}
L_B merge(const L_B &n1,const L_B &n2)
{
L_B ret=n1;
for (int i=60;i>=0;i--)
if (n2.d[i])
ret.insert(n1.d[i]);
return ret;
}