线性代数的学习

高斯消元

HDU 3949 XOR 

给定一个集合, 求仅使用这个集合中的元素进行异或所能得到的第K小的数是多少。

对整个集合进行一遍高斯消元以后得到的就是原来的集合所在的线性空间的一组基了。

注意这里的高斯消元和解线性方程组时的高斯消元略有不同, 这里的高斯消元在消第i位的时候不止要把所有它后面的向量的第i位都消掉, 也要把它前面所有向量的第i为都消掉, 也就是在最后消出来的集合中每一个数位要不然不出现要不然出现且仅出现了一次。求出来了这个基以后只需要判基中的每一位是不是1就可以了(不在基中的每一位是无法控制是不是1的, 一定由更高位决定了)注意这里的是不是1并不代表选不选,是要结合之前选了的东西的, 不过写在程序里就只要按照二进制搞一搞就可以了。注意判有没有0的情况, 可以组成0当且仅当原式线性相关即消元完了以后还有向量=0。

/*其实这样做高斯消元的目的就是让每一位都存在于之多一个数上, 然后每一位的选或不选就是独立的了, 就可以随便搞一搞了。*/

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <algorithm>
 5 #include <cmath>
 6 #define MAXN 10005
 7 #define ll long long
 8 using namespace std;
 9 int T, n, q, tt;
10 ll a[MAXN];
11 int main(){
12     scanf("%d", &T);
13     while(T --){
14         printf("Case #%d:\n", ++ tt);
15         scanf("%d", &n);
16         for(int i = 1; i <= n; i ++) scanf("%I64d", &a[i]);
17         int k = 1, p;
18         for(ll i = 1ll<<59; i; i >>= 1){
19             bool flag = 0; 
20             for(int j = k; j <= n; j ++)if(a[j]&i){
21                 flag = 1; p = j; break;
22             }
23             if(!flag) continue;
24             swap(a[k], a[p]);
25             for(int j = 1; j <= n; j ++) if(j!=k && a[j]&i) a[j] ^= a[k];
26             k ++;
27         }
28         k --;
29         bool flag = 1; if(k == n) flag = 0;
30         scanf("%d", &q);
31         for(int i = 1; i <= q; i ++){
32             ll x; scanf("%I64d", &x); x -= flag;
33             if(x >= (1ll<<k)){puts("-1"); continue;}
34             if(x == 0){puts("0"); continue;}
35             ll ans = 0;
36             for(int j = 0; j < k; j ++){
37                 if(x&1) ans ^= a[k-j];
38                 x >>= 1;
39             }
40             printf("%I64d\n", ans);
41         }
42     }
43     return 0;
44 }
View Code

 

BZOJ 2115 Wc2011 Xor

求一个边权为非负整数的无向图从1到n的所有路径中异或和最大的那个。

任意两条从1到n的路径的(异或)和都是一个环!

所以我们可以任取一条路径, 然后找出和它异或起来最大的那个环就可以啦!原图中所有的环在异或意义下构成了一个线性空间,也就是说只要找到所有环的一个极大线性无关子集就是这个线性空间的一组基了,直接dfs一遍每出现一条非树边就确定一条环就可以直接得到一个极大线性无关子集了, 得到一组基以后高斯消元消一消“让每一位都存在于之多一个数上”, 然后按位贪心就可以了。

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cmath>
 4 #include <algorithm>
 5 #include <cstring>
 6 #define ll long long
 7 #define MAXN 500005
 8 using namespace std;
 9 int n, m, ee, st, vis[MAXN], head[MAXN];
10 ll tc[MAXN], b[MAXN], a[MAXN];
11 struct Edge{
12     int to, next; ll c;
13 }edge[MAXN*2];
14 inline void addedge(int x, int y, ll c){
15     edge[++ ee].to = y; edge[ee].c = c; edge[ee].next = head[x]; head[x] = ee;
16 }
17 void dfs(int x, int fatt){
18     vis[x] = 1;
19     for(int i = head[x]; i != -1; i = edge[i].next) if(edge[i].to != fatt){
20         if(!vis[edge[i].to]){
21             tc[edge[i].to] = tc[x] ^ edge[i].c; dfs(edge[i].to, x);
22         }
23         else a[++ st] = tc[x] ^ tc[edge[i].to] ^ edge[i].c;
24     } 
25 }
26 int main(){
27     memset(head, -1, sizeof(head));
28     scanf("%d%d", &n, &m);
29     for(int i = 1; i <= m; i ++){
30         int x, y; ll c; scanf("%d%d%lld", &x, &y, &c);
31         addedge(x, y, c); addedge(y, x, c);
32     }
33     dfs(1, 0);
34     int k = 1, p;
35     for(ll i = 1ll<<59; i; i >>= 1){
36         bool flag = 0;
37         for(int j = k; j <= n; j ++) if(a[j]&i){
38             p = j; flag = 1; break;
39         }
40         if(!flag) continue;
41         swap(a[p], a[k]); b[k] = i;
42         for(int j = 1; j <= n; j ++) if(j!=k && (a[j]&i))
43             a[j] ^= a[k];
44         k ++;
45     }
46     k --;
47     ll ans = 0;
48     for(int i = 1; i <= k; i ++){
49         if((b[i]&tc[n]) == 0) ans ^= a[i];
50     }
51     printf("%lld\n", ans^tc[n]);
52     return 0;
53 }
View Code

 

BZOJ 2844 albus就是要第一个出场

给你一个集合, 问你把它的所有子集的异或和排序之后Q这个异或和第一次出现在第几个。

如果排序是带去重的话直接高消求一组基算一下就可以了。但是如何统计重复的那些呢?最重要的是要认知到一点, 就是把行A异或上行B之后所有子集的异或和序列是没有任何影响的!对于只含有A的原集合S, 当前的S+B以后就相当于原来的S, 而对于所有既含有A有含有B的元集合P, 当前的P-B以后就相当于原来的集合P, 所以说异或出来的数的集合是没有变化的。

于是就直接高消,对于那些消完了以后为0的行在每一个集合中都是可以选或不选而不影响集合的异或值的, 于是答案就应该是基上的答案再乘上一个2^|0的个数|

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <algorithm>
 5 #include <cmath>
 6 #define MAXN 100005
 7 using namespace std;
 8 int n, Q, a[MAXN], la[MAXN], b[MAXN], ans;
 9 int main(){
10     scanf("%d", &n);
11     for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
12     int k = 1, p;
13     for(int i = (1<<29); i; i >>= 1){
14         bool flag = 0;
15         for(int j = k; j <= n; j ++) if(a[j]&i){
16             p = j; flag = 1; break;
17         }
18         if(!flag) continue;
19         swap(a[p], a[k]); b[k] = i;
20         for(int j = 1; j <= n; j ++) if(j!=k && (a[j]&i)) a[j] ^= a[k];
21         k ++;
22     }
23     k --;
24     scanf("%d", &Q);
25     if(!Q) {puts("1"); return 0;}
26     for(int i = 1; i <= k; i ++) if(Q&b[i]) (ans += (1<<(k-i))) %= 10086;
27     int ha = 1;
28     for(int i = k+1; i <= n; i ++) (ha <<= 1) %= 10086;
29     printf("%d\n", (1+((long long)ans*ha)%10086)%10086);
30     return 0;
31 }
View Code

 

BZOJ 2728 HNOI2012 与非

给定k位二进制下的n个数,求[l,r]区间内有多少个数能通过这几个数与非得到

这明明不是高斯消元啊QAQ 看到别人把它分类到高消就在这里把它做了。可能是因为最后一步的思想经常应用于要进行高消的题中吧。

我们知道只要有两种基本逻辑运算就可以把其他的几个都凑出来了。那么题目名称“与非”是什么意思呢?

大概是因为 A nand A = not A, not ( A nand B ) = A and B 所以与和非都可以通过nand表达出来吧。 

所以问题转化为给你一堆数, 然后随便进行二进制下的逻辑运算, 问你最后能产生的小于x的数有哪些。如果每一位都是相互独立的话, 最后肯定是所有数都可以取到了, 那么为什么有的数不能取到呢?因为第i位和第j位之间可能存在着限定关系。显然如果每一个数的第i位都等于第j位的话无论进行什么操作到最后得出的结果的第i位仍然会等于第j位。所以把相等的这些都压到最大的那一位上, 然后按照从大向小一位一位地取下去就可以了。

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cmath>
 4 #include <algorithm>
 5 #include <cstring>
 6 #define ll long long
 7 #define MAXN 1005
 8 using namespace std;
 9 int n, k;
10 ll L, R, A[MAXN], a[66], tt, b[66];
11 ll ask(ll x){
12     if(x == -1) return -1;
13     ll now = 0, ret = 0;
14     for(int i = 1; i <= tt; i ++)
15         if((now|a[i]) <= x) now |= a[i], ret |= (1ll<<(tt-i));
16     return ret;
17 }
18 int main(){
19     scanf("%d%d%lld%lld", &n, &k, &L, &R);
20     for(int i = 1; i <= n; i ++) scanf("%lld", &A[i]);
21     ll la = (1ll<<k)-1;
22     for(int i = k-1; i >= 0; i --)if(!b[i]){
23         ll ha = la;
24         for(int j = 1; j <= n; j ++){
25             if((A[j]&(1ll<<i))) ha &= A[j];
26             else ha &= ~A[j]&la; 
27         }
28         a[++ tt] = ha;
29         for(int j = 0; j < i; j ++)
30             if(ha&(1ll<<j)) b[j] = 1; 
31     }
32     printf("%lld\n", ask(R)-ask(L-1));
33     return 0;
34 }
View Code

 

BZOJ 3759 Hungergame

有一堆里面分别放有ai个石子的箱子, A,B两人轮流操作, 每次可以1,从任意一个已经开启的箱子里拿走任意多个石子 2,打开任意多个原来没有打开的箱子。不能操作者败。

有结论:当前的所有打开的箱子中的石子数异或和为零,且所有关闭的箱子中的石子数的集合中不存在一个异或和为零的非空子集 时先手必败。

正确性还是很显然的,但自己并不能想到啊QAQ

所以判一下是否线性相关就好啦。

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <cmath>
 5 #include <algorithm>
 6 using namespace std;
 7 int n, a[22];
 8 int main(){
 9     int T; scanf("%d", &T);
10     while(T --){
11         scanf("%d", &n);
12         for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
13         int flag = 0, k = 1, p;
14         for(int i = 1<<29; i; i >>= 1){
15             bool la = 0;
16             for(int j = k; j <= n; j ++) if(a[j]&i){
17                 la = 1; p = j; break;
18             }
19             if(!la) continue;
20             swap(a[k], a[p]);
21             for(int j = k+1; j <= n; j ++) if(a[j]&i) a[j] ^= a[k];
22             k ++;
23         }
24         k --;
25         puts(k<n ? "Yes" : "No");
26     }
27     return  0;
28 }
View Code

 

转载于:https://www.cnblogs.com/lixintong911/p/5083688.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值