「专题总结」线性基
为什么要把毫无关联的线性基和群论放在一个专题里呢。。?
因为它们都很毒瘤
线性基本身还是比较简单的,用于处理一些数子集异或和的集合有哪些。
然而它的考察方法总是很神奇。。。
题目难度大致升序,但是没有前置知识关系:
元素:
$Description:$
相传,在远古时期,位于西方大陆的 Magic Land 上,人们已经掌握了用魔法矿石炼制法杖的技术。那时人们就认识到,一个法杖的法力取决于使用的矿石。 一般地,矿石越多则法力越强,但物极必反:有时,人们为了获取更强的法力而使用了很多矿石,却在炼制过程中发现魔法矿石全部消失了,从而无法炼制 出法杖,这个现象被称为“魔法抵消” 。特别地,如果在炼制过程中使用超过一块同一种矿石,那么一定会发生“魔法抵消”。
后来,随着人们认知水平的提高,这个现象得到了很好的解释。经过了大量的实验后,著名法师 Dmitri 发现:如果给现在发现的每一种矿石进行合理的编号(编号为正整数,称为该矿石的元素序号),那么,一个矿石组合会产生“魔法抵消”当且仅当存在一个非空子集,那些矿石的元素序号按位异或起来为零。 (如果你不清楚什么是异或,请参见下一页的名词解释。 )例如,使用两个同样的矿石必将发生“魔法抵消”,因为这两种矿石的元素序号相同,异或起来为零。
并且人们有了测定魔力的有效途径,已经知道了:合成出来的法杖的魔力等于每一种矿石的法力之和。人们已经测定了现今发现的所有矿石的法力值,并且通过实验推算出每一种矿石的元素序号。
现在,给定你以上的矿石信息,请你来计算一下当时可以炼制出的法杖最多有多大的魔力$N ≤ 1000,Numberi ≤ 10^{18},Magici ≤ 10^4$
板子题。贪心排序,能加就加。
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 struct ss{ 5 int v;long long num; 6 friend bool operator<(ss a,ss b){return a.v>b.v;} 7 }s[1001]; 8 int n;long long d[63],ans; 9 int main(){ 10 scanf("%d",&n); 11 for(int i=1;i<=n;++i)scanf("%lld%d",&s[i].num,&s[i].v); 12 sort(s+1,s+1+n); 13 for(int i=1;i<=n;++i) 14 for(int j=62;j>=0;--j) 15 if(s[i].num&1ll<<j) 16 if(!d[j]){d[j]=s[i].num;ans+=s[i].v;break;} 17 else s[i].num^=d[j]; 18 printf("%lld",ans); 19 }
albus就是要第一个出场:
$Description:$
已知一个长度为n的正整数序列A(下标从1开始), 令 S = { x | 1 <= x <= n }, S 的幂集2^S定义为S 所有子集构成的集合。定义映射 f : 2^S -> Z
f(空集) = 0
f(T) = XOR A[t]
对于一切t属于T现在albus把2^S中每个集合的f值计算出来, 从小到大排成一行, 记为序列B(下标从1开始)。
给定一个数, 那么这个数在序列B中第1次出现时的下标是多少呢?
$1 <= N <= 10,0000$,其他所有输入均不超过10^9
一个挺好理解的结论。每个线性基中存在的数在任意子集里出现概率都是一半,互不干扰。
毕竟是结论,上自己八百年前的博客。
线性基,从高到底查个排名。
1 #include<cstdio> 2 #define mod 10086 3 int pw(int b,int t,int a=1){for(;t;t>>=1,b=b*b%mod)if(t&1)a=a*b%mod;return a;} 4 int base[33]; 5 int main(){ 6 int n,N,x,cnt=0,ans=0;scanf("%d",&n);N=n; 7 while(n--){ 8 scanf("%d",&x); 9 for(int i=30;~i;--i)if(!base[i]&&x&1<<i){base[i]=x;break;} 10 else if(x&1<<i)x^=base[i]; 11 } 12 scanf("%d",&x); 13 for(int i=30;~i;--i)if(base[i]&&x&1<<i)cnt++,ans=(ans+pw(2,N-cnt))%mod; 14 else if(base[i])cnt++; 15 printf("%d\n",ans+1); 16 17 }
幸运数字:
$Description:$
A 国共有 n 座城市,这些城市由 n-1 条道路相连,使得任意两座城市可以互达,且路径唯一。每座城市都有一个幸运数字,以纪念碑的形式矗立在这座城市的正中心,作为城市的象征。一些旅行者希望游览 A 国。旅行者计划乘飞机降落在 x 号城市,沿着 x 号城市到 y 号城市之间那条唯一的路径游览,最终从 y 城市起飞离开 A 国。
在经过每一座城市时,游览者就会有机会与这座城市的幸运数字拍照,从而将这份幸运保存到自己身上。然而,幸运是不能简单叠加的,这一点游览者也十分清楚。他们迷信着幸运数字是以异或的方式保留在自己身上的。例如,游览者拍了 3 张照片,幸运值分别是 5,7,11,那么最终保留在自己身上的幸运值就是 9(5 xor 7 xor 11)。
有些聪明的游览者发现,只要选择性地进行拍照,便能获得更大的幸运值。例如在上述三个幸运值中,只选择 5 和 11 ,可以保留的幸运值为 14 。现在,一些游览者找到了聪明的你,希望你帮他们计算出在他们的行程安排中可以保留的最大幸运值是多少。$N<=20000,Q<=200000,Gi<=2^60$
树上路径问题,也就是倍增/树剖这一类的了。
这一题是倍增,维护$log$个线性基,线性基也可以简单粗暴的插入合并。
1 #include<cstdio> 2 #define rci const register int 3 int n,q,fir[20002],l[40005],to[40005],cnt,dep[20005],f[20005][16]; 4 long long d[20005][16][61],dd[61],maxx,w[20005]; 5 inline void connect(rci a,rci b){ 6 l[++cnt]=fir[a];fir[a]=cnt;to[cnt]=b; 7 l[++cnt]=fir[b];fir[b]=cnt;to[cnt]=a; 8 } 9 void dfs(rci p,rci ff){ 10 f[p][0]=ff; dep[p]=dep[ff]+1; 11 for(int i=59;i>=0;--i)if(w[p]&1ll<<i){d[p][0][i]=w[p];break;} 12 for(int i=1;i<=15;++i){ 13 f[p][i]=f[f[p][i-1]][i-1]; 14 for(int ii=59;ii>=0;--ii)d[p][i][ii]=d[p][i-1][ii]; 15 for(long long ii=59,x=d[f[p][i-1]][i-1][ii];ii>=0;--ii,x=d[f[p][i-1]][i-1][ii])if(x){ 16 for(int j=59;j>=0;--j) if(x&1ll<<j) 17 if(!d[p][i][j]){d[p][i][j]=x;break;} 18 else x^=d[p][i][j]; 19 }} 20 for(int i=fir[p];i;i=l[i]) if(to[i]!=ff) dfs(to[i],p); 21 } 22 inline void insert(rci p,rci t){ 23 for(long long ii=59,x=d[p][t][ii];ii>=0;--ii,x=d[p][t][ii])if(x) 24 for(int j=59;j>=0;--j) 25 if(x&1ll<<j) 26 if(!dd[j]){dd[j]=x;break;} 27 else x^=dd[j]; 28 } 29 int main(){ 30 scanf("%d%d",&n,&q); 31 for(int i=1;i<=n;++i)scanf("%lld",&w[i]); 32 for(int i=1,a,b;i<n;++i)scanf("%d%d",&a,&b),connect(a,b); 33 dfs(1,0); 34 for(register int t=1,a,b,lca,ds;t<=q;++t){ 35 scanf("%d%d",&a,&b); maxx=0; 36 for(int i=59;i>=0;--i)dd[i]=0; 37 if(dep[a]<dep[b])a^=b,b^=a,a^=b; 38 ds=dep[a]-dep[b]; 39 for(int i=15;i>=0;--i) if(ds&1<<i) insert(a,i),a=f[a][i]; 40 if(a==b)lca=a; 41 else{ 42 for(int i=15;i>=0;--i) if(f[a][i]!=f[b][i]){ 43 insert(a,i); insert(b,i); 44 a=f[a][i]; b=f[b][i]; 45 } 46 insert(a,0); insert(b,0); 47 lca=f[a][0]; 48 } 49 insert(lca,0); 50 for(int i=59;i>=0;--i)if(!(maxx&1ll<<i))maxx^=dd[i]; 51 printf("%lld\n",maxx); 52 } 53 }
shallot:
$Description:$
小苗去市场上买了一捆小葱苗,她突然一时兴起,于是她在每颗小葱苗上写上一个数字,然后把小葱叫过来玩游戏。
每个时刻她会给小葱一颗小葱苗或者是从小葱手里拿走一颗小葱苗,并且
让小葱从自己手中的小葱苗里选出一些小葱苗使得选出的小葱苗上的数字的异或和最大。
这种小问题对于小葱来说当然不在话下,但是他的身边没有电脑,于是他打电话给同为Oi选手的你,你能帮帮他吗?
你只需要输出最大的异或和即可,若小葱手中没有小葱苗则输出0。$N<=500000,Ai<=2^{31}-1$
还是朴素的数据结构配合线性基。
删除元素不好弄,于是每个元素有一个存在区间,线段树分治维护。
1 #include<cstdio> 2 #include<map> 3 #include<cmath> 4 #include<vector> 5 using namespace std; 6 #define cri const register int 7 map<int,int>m; 8 vector<int>v[500005],add[2000005]; 9 int n,ref[500005],cnt,d[31],ll,rr,w,sta[500005],top; 10 void insert(cri p,cri l,cri r){ 11 if(ll<=l&&rr>=r){add[p].push_back(w);return;} 12 if(ll<=l+r>>1)insert(p<<1,l,l+r>>1); 13 if(rr>l+r>>1)insert(p<<1|1,(l+r>>1)+1,r); 14 } 15 void dfs(cri p,cri l,cri r){ 16 for(int j=0;j<add[p].size();++j){ 17 int x=add[p][j]; 18 for(int i=30;i>=0;--i) if(x&1<<i) 19 if(d[i])x^=d[i]; 20 else{d[i]=x;sta[++top]=i;break;} 21 } 22 add[p].clear(); 23 if(l==r){ 24 register int maxx=0; 25 for(register int i=30;i>=0;--i) if(d[i]&&!(maxx&1<<i)) maxx^=d[i]; 26 printf("%d\n",maxx); 27 return; 28 } 29 int ntop=top; 30 dfs(p<<1,l,l+r>>1); 31 while(top>ntop)d[sta[top--]]=0; 32 dfs(p<<1|1,(l+r>>1)+1,r); 33 } 34 int main(){ 35 scanf("%d",&n); 36 for(register int i=1,a;i<=n;++i){ 37 scanf("%d",&a); 38 if(a>0&&m.find(a)==m.end())m[a]=++cnt,ref[cnt]=a; 39 v[m[abs(a)]].push_back((a<0?-1:1)*i); 40 } 41 m.clear(); 42 for(register int i=1,h=0,l=0;i<=cnt;++i,h=0,l=0){ 43 for(register int j=0;j<v[i].size();++j) 44 if(v[i][j]>0)l=l?l:v[i][j],h++; 45 else if((--h)==0)ll=l,rr=-v[i][j]-1,w=ref[i],insert(1,1,n),l=0; 46 if(h)ll=l,rr=n,w=ref[i],insert(1,1,n);v[i].clear(); 47 } 48 dfs(1,1,n); 49 }
梦想封印:
$Description:$
渐渐地,Magic Land上的人们对那座岛屿上的各种现象有了深入的了解。
为了分析一种奇特的称为梦想封印(Fantasy Seal)的特技,需要引入如下的概念:
每一位魔法的使用者都有一个“魔法脉络”,它决定了可以使用的魔法的种类。
一般地,一个“魔法脉络”可以看作一个无向图,有N个结点及M条边,将结点编号为1~N,其中有一个结点是特殊的,称为核心(Kernel),记作1号结点。
图每一条边有一个固有(即生成之后再也不会发生变化的)权值,是一个不超过U的自然数。
每一次魔法驱动,可看作是由核心(Kernel)出发的一条有限长的道路(Walk),可以经过一条边多次,所驱动的魔法类型由以下方式给出:
将经过的每一条边的权值异或(xor)起来,得到s。
如果s是0,则驱动失败,否则将驱动编号为s的魔法(每一个正整数编号对应了唯一一个魔法)。
需要注意的是,如果经过了一条边多次,则每一次都要计入s中。
这样,魔法脉络决定了可使用魔法的类型,当然,由于魔法与其编号之间的关系尚未得到很好的认知,此时人们仅仅关注可使用魔法的种类数。
梦想封印可以看作是对“魔法脉络”的破坏:
该特技作用的结果是,“魔法脉络”中的一些边逐次地消失。
我们记总共消失了Q条边,按顺序依次为Dis1、Dis2、……、DisQ。
给定了以上信息,你要计算的是梦想封印作用过程中的效果,这可以用Q+1个自然数来描述:
Ans0为初始时可以使用魔法的数量。
Ans1为Dis1被破坏(即边被删去)后可以使用魔法的数量。
Ans2为Dis1及Dis2均被破坏后可使用魔法的数量。
……
AnsQ为Dis1、Dis2、……、DisQ全部被破坏后可以使用魔法的数量
$N ≤ 5000,M ≤ 20000,Q ≤20000,U≤10^18$
图肯定不好维护,于是按照习惯维护一棵树。
因为要求从1号节点出发,如果是树上简单路径肯定好说。
其余路径都可以用树上简单路径+若干简单环表示出来。(因为同一条路走两遍相互抵消)
删边不好做,于是时光倒流变成加边。
每次形成一个环,就表明你的任意一条路径可以异或上环上权值和。
所以所有环和所形成的线性基就表示你从起点出发的初值能有哪些,而走到每个节点的简单路径是可以处理出来的。
所以当前的答案就是2的线性基大小次方,再乘上树上所有节点到1号节点的异或距离在不比较线性基含有的位数时有多少种,再减去0这一种。
可以unordered_set维护。每次线性基成功插入时unordered_set的判重依据就会改变,也就是加入每个元素前要放进线性基跑一遍。
而之前unordered_set中不同的元素现在可能相同了,把它们全都倒出来再以相同规则放回去就可以去重了。
因为线性基最多成功插入63次所以上述操作最多也就63次,复杂度可以接受。
每次加边有以下几种可能:
1,两个端点都不在1号节点的生成树上:那么直到两个节点之一被加入之前,这条边的加入暂时没有影响,留到以后再说。
2,其中一个端点在树上:把另一个端点加入树,计算到根的异或距离并放进set。释放与新点相关的上面的那一类边。
3,两个端点都在树上:形成了一个$dis_u \ xor \ dis_v \ xor \ val_i$的环,尝试插入线性基。
比想象中好写不少。顺便温习了一下C++语法如何声明函数(因为加边与加点相互调用。。。)
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 #define S 20005 5 unordered_set<ll>s,r;vector<int>V[S]; 6 int x[S],y[S],al[S],it[S],ec,dt[S],n,m,q,dn[S]; 7 ll v[S],vp[S],ans[S],bs=1,b[66]; 8 ll ask(){return bs*s.size()-1;} 9 void ins(ll x){for(int i=62;~i;--i)if(x&1ll<<i&&b[i])x^=b[i];s.insert(x);} 10 void renew(){bs<<=1;swap(r,s);s.clear();for(auto x:r)ins(x);} 11 void insert(ll x){for(int i=62;~i;--i)if(!b[i]&&x&1ll<<i){b[i]=x;return renew();}else if(x&1ll<<i)x^=b[i];} 12 void extend(int); 13 void add(int o){ 14 if(al[o])return; 15 if(!it[x[o]]&&!it[y[o]]){V[x[o]].push_back(o),V[y[o]].push_back(o);return;} 16 al[o]=1;//cout<<x[o]<<' '<<y[o]<<' '<<v[o]<<endl; 17 if(it[x[o]]&&it[y[o]]){insert(vp[x[o]]^vp[y[o]]^v[o]);return;} 18 if(it[y[o]])x[o]^=y[o]^=x[o]^=y[o]; 19 it[y[o]]=1;vp[y[o]]=vp[x[o]]^v[o];ins(vp[y[o]]);extend(y[o]); 20 } 21 void extend(int p){for(int i=0;i<V[p].size();++i)add(V[p][i]);} 22 int main(){//freopen("1.in","r",stdin); 23 scanf("%d%d%d",&n,&m,&q);it[1]=1;s.insert(0); 24 for(int i=1;i<=m;++i)scanf("%d%d%lld",&x[i],&y[i],&v[i]); 25 for(int i=1;i<=q;++i)scanf("%d",&dn[i]),dt[dn[i]]=i; 26 for(int i=1;i<=m;++i)if(!dt[i])add(i); 27 ans[q+1]=ask(); 28 for(int i=q;i;--i)add(dn[i]),ans[i]=ask(); 29 for(int i=1;i<=q+1;++i)printf("%lld\n",ans[i]); 30 }
玛里苟斯:
$Description:$
魔法之龙玛里苟斯最近在为加基森拍卖师的削弱而感到伤心,于是他想了一道数学题。
S 是一个可重集合,S={a1,a2,…,an}。等概率随机取 S 的一个子集 A={ai1,…,aim}。
计算出 A 中所有元素异或和 x, 求 xk 的期望。
如果结果是整数,直接输出。如果结果是小数(显然这个小数是有限的),输出精确值(末尾不加多余的 0)。
$1≤n≤100000,1≤k≤5,ai≥0$。最终答案小于 $2^63$ 。$k=1,2,3,4,5$ 各自占用 $20%$ 的数据
大恶心毒瘤题。再也不想碰它了。丢个原来的博客的链接就跑路了。
1 #include<cstdio> 2 #define int unsigned long long 3 int n,k,base[65],x[100005],chg[65],A,ansx,ansy,cnt; 4 main(){ 5 scanf("%llu%llu",&n,&k); 6 for(int i=1;i<=n;++i){ 7 scanf("%llu",&x[i]);int X=x[i];A|=x[i]; 8 for(int i=62;~i;--i)if(X&1ll<<i&&!base[i]){base[i]=X;break;} 9 else if(X&1ll<<i)X^=base[i]; 10 } 11 if(k==1)printf("%llu",A>>1),puts(A&1?".5":""); 12 else if(k==2){ 13 for(int j=0;j<32;++j)for(int t=0;t<32;++t)if(A>>j&1&&A>>t&1){ 14 int f=0;for(int i=1;i<=n;++i)if(x[i]>>j&1^x[i]>>t&1){f=1;break;} 15 if(t+j<1+f)ansy++;else ansx+=1ll<<t+j-f-1; 16 } 17 printf("%llu",ansx+(ansy>>1));puts(ansy&1?".5":""); 18 }else{ 19 for(int i=22;~i;--i)if(base[i])chg[++cnt]=base[i]; 20 for(int i=0;i<1<<cnt;++i){ 21 int tot=0; 22 for(int j=0;j<cnt;++j)if(i&1<<j)tot^=chg[j+1]; 23 int x=0,y=1; 24 for(int s=0;s<k;++s)x*=tot,y*=tot,x+=y>>cnt,y&=(1<<cnt)-1; 25 ansx+=x;ansy+=y;ansx+=ansy>>cnt;ansy&=(1<<cnt)-1; 26 } 27 printf("%llu",ansx);puts(ansy?".5":""); 28 } 29 }
DZY Loves Chinese II:
$Description:$
神校XJ之学霸兮,Dzy皇考曰JC。
摄提贞于孟陬兮,惟庚寅Dzy以降。
纷Dzy既有此内美兮,又重之以修能。
遂降临于OI界,欲以神力而凌♂辱众生。今Dzy有一魞歄图,其上有N座祭坛,又有M条膴蠁边。
时而Dzy狂WA而怒发冲冠,神力外溢,遂有K条膴蠁边灰飞烟灭。
而后俟其日A50题则又令其复原。(可视为立即复原)
然若有祭坛无法相互到达,Dzy之神力便会大减,于是欲知其是否连通$N≤100000 M≤500000 Q≤50000 1≤K≤15$。强制在线。
这是真的大神题。。。因为你根本看不出它和线性基有什么关系。
大意是给定无向图,每次询问时暂时去掉若干边问全图是否联通。
同上一题的套路,图不会维护,转而维护一棵树。
但是这题的树还不太一样,因为边没有先后顺序你可以按心情构造任意一棵生成树。
为了方便弄,我们建出dfs树,这样的话所有非树边就都是返祖边了。
考虑:在什么时候会不联通?
一个是某条树边断开了,然后跨越它的返祖边也都全部断开了。
另一个是两条树边它们的返祖边集合相同,这两条树边都被断开了。
所以我们如果认为树边与跨越它的所有返祖边等价,那么我们可以很简单的概括上述关系:
只要有完全等价的边集被断开了,那么图就不联通了。
这里的「等价」需要一个量化的标准,于是我们将非树边随机赋值,而树边因为等价关系所以树边的权就是所有跨越它的返祖边之和。
具体如何处理:就是在dfs建树的时候利用点做一下差分就可以实现区间修改(因为都是返祖边)。
现在的问题就只剩下了:对于k条边看看其中是否有等价边。
暴力的思路肯定就是$2^k$枚举一下子集。
但是到这里我们还没有用上线性基。如果一条边能用另一些边完全表示出来,那么它们的异或和为0。
所以我们将这些边依次插入线性基,如果插入失败就证明有等价边,图不联通。否则就联通。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 1000005 4 int l[S],fir[S],to[S],ec=1,cnt,al[S],n,m,q,T;long long b[65],vp[S],v[S]; 5 void link(int a,int b){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;} 6 long long Rand(long long a=0){for(int i=62;~i;--i)a|=(rand()&1ll)<<i;return a;} 7 void dfs(int p,int fa){al[p]=++T; 8 for(int i=fir[p];i;i=l[i])if(to[i]!=fa) 9 if(!al[to[i]])dfs(to[i],p),v[i>>1]=vp[to[i]],vp[p]^=vp[to[i]]; 10 else if(al[to[i]]<al[p])v[i>>1]=Rand(),vp[p]^=v[i>>1],vp[to[i]]^=v[i>>1]; 11 } 12 int insert(long long x){ 13 for(int i=62;~i;--i)if(x&1ll<<i)if(b[i])x^=b[i];else return b[i]=x,1; 14 return 0; 15 } 16 int main(){ 17 scanf("%d%d",&n,&m); 18 for(int i=1,x,y;i<=m;++i)scanf("%d%d",&x,&y),link(x,y),link(y,x); 19 dfs(1,0);scanf("%d",&q); 20 while(q--){ 21 int ok=1,r,k;scanf("%d",&k); 22 for(int i=62;~i;--i)b[i]=0; 23 while(k--)scanf("%d",&r),ok*=insert(v[r^cnt]); 24 if(ok)puts("Connected"),cnt++;else puts("Disconnected"); 25 } 26 }