【集训】线性代数基础
题单
不是自己写的,总结的
P3812 【模板】线性基
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int D = 64;
ll w[D];
bool f;
bool ins(ll x) {
for (int i = D - 1; i >= 0; i--) {
if ((x >> i) & 1) {
if (w[i])
x ^= w[i];
else {
w[i] = x;
return true;
}
}
}
return false;
}
void simp() {
for (int i = D - 1; i >= 0; i--) {
if (w[i]) {
for (int j = D - 1; j > i; j--)
if ((w[j] >> i) & 1)
w[j] ^= w[i];
}
}
}
ll v[D];
ll mxk(ll k) {
int cnt = 0;
for (int i = 0; i < D; i++)
if (w[i] != 0)
v[cnt++] = w[i];
if (k >= (1ll << cnt))
return -1;
ll ans = 0;
for (int i = 0; i < cnt; i++)
if ((k >> i) & 1)
ans ^= v[i];
return ans;
}
int main() {
int n, m;
cin >> n;
for (int i = 1; i <= n; i++) {
ll x;
cin >> x;
f |= (!ins(x));
}
simp();
cin >> m;
for (int i = 1; i <= m; i++) {
ll k;
cin >> k;
if (f)
k--;
cout << mxk(k) << endl;
}
return 0;
}
LOJ114
题意:\(n\) 个数,\(q\) 次询问,每次给出 \(k\),求这 \(n\) 个数的所有子集的异或和去重后第 \(k\) 小的值
我们可以把线性基进一步简化:对每个 \(i\),用最高位为 \(i\) 的数去异或上其他所有第 \(i\) 位非零的数,这样就可以把线性基进一步简化,它满足:
- 所有数的最高位互不相同。
- 对每个 \(i\),如果存在某个 \(v_i\) 的最高位是第 \(i\) 位,则其它数的第 \(i\) 位必为 \(0\)。
这样的一组线性基 \(B = {v_0, \dots , v_k}\),其中 \(v_i\) 的最高位依次递增,\(span(B)\) 中所有元素从小到大排列后恰好就是:$0, v_0, v_1, v_0 \oplus v_1, v_2, v_0 \oplus v_2, \dots $ ,一直到 \(v_0 \oplus \dots ⊕ v_k\)。那么第 k 小也就容易求出了。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int D=64;
ll w[D];
bool f;
bool ins(ll x){
for(int i=D-1;i>=0;i--){
if((x>>i)&1){
if(w[i]) x^=w[i];
else{
w[i]=x;
return true;
}
}
}
return false;
}
void simp(){
for(int i=D-1;i>=0;i--){
if(w[i]){
for(int j=D-1;j>i;j--)
if((w[j]>>i)&1)
w[j]^=w[i];
}
}
}
ll v[D];
ll mxk(ll k){
int cnt = 0;
for (int i = 0; i < D; i++)
if (w[i] != 0)
v[cnt++] = w[i];
if (k >= (1ll << cnt))
return -1;
ll ans = 0;
for (int i = 0; i < cnt; i++)
if ((k >> i) & 1)
ans ^= v[i];
return ans;
}
int main(){
int n,m;
cin>>n;
for(int i=1;i<=n;i++){
ll x;
cin>>x;
f|=(!ins(x));
}
simp();
cin>>m;
for(int i=1;i<=m;i++){
ll k;
cin>>k;
if(f) k--;
cout<<mxk(k)<<endl;
}
return 0;
}
P4570 [BJWC2011] 元素
给出 \(n\) 个物品,每个物品有标号 \(a_i\) 和价值 \(b_i\),选出一些物品使得它们的标号不存在一个非空子集异或和为零,且价值之和最大。\(1\leq n \leq 1000\),\(1\leq a_i \le 10^{18}\),\(1\leq b_i \le 10^4\)。
只需要按价值从大到小考虑,每次能加入就加入即可。
可以用线性基来判断能否插入一个新的元素。时间复杂度 \(\mathcal{O}(n(log V + log n))\)。
证明用一句话来说就是向量空间和它的线性无关子集构成拟阵;当然也可以使用归纳法。
#include <bits/stdc++.h>
using namespace std;
const int D=64,N=1005;
#define ll long long
ll w[D],n;
struct s{
ll a,b;
}a[N];
bool cmp(s a,s b){
return a.b>b.b;
}
bool ins(ll x){
for(int i=D-1;i>=0;i--){
if((x>>i)&1){
if(w[i]) x^=w[i];
else{
w[i]=x;
return true;
}
}
}
return false;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i].a>>a[i].b;
}
sort(a+1,a+n+1,cmp);
ll ans=0;
for(int i=1;i<=n;i++){
if(ins(a[i].a)) ans+=a[i].b;
}
cout<<ans<<endl;
return 0;
}
P4151 [WC2011] 最大XOR和路径
给定一张 \(n\) 点 \(m\) 边的无向图,边有边权 \(w_i\),求一条 \(1 \to n\) 的路径(不一定是简单路径),使得路径上边权 XOR 和最大。\(1 \le n \le 50000, 1 \le m \le 10^5, 0 ≤ w_i ≤ 10^{18}\)
任取生成树 \(T\) 和点 \(r\) 为根,设 \(f(u)\) 为 \(u \to r\) 路径上边权异或和。
考虑任意一个 \(1 \to n\) 的路径 \(P = (u_1, \cdots , u)k)\),设 \(w(u, v)\) 为 \((u, v)\) 这条边的边权,则这条路径的权值是
注意到当 \((u_i, u_{i+1})\) 是树边的时候,总有 \(f(u_i) \oplus w(u_i, u_{i+1}) \oplus f(u_{i+1}) = 0\),
因此这个式子就等于:
而当 \((u, v) \notin T\) 的时候,\(f(u) ⊕ f(v) \oplus w(u, v)\) 的含义就是 \((u,v)\) 以及它们树上路径构成的这个环。
于是,\(1 \to n\) 的所有路径,它的边权异或和一定等于 \(1\to n\) 在树上的路径的边权异或和,以及某些仅包含一条非树边的环的边权异或和。
另一方面,不管当前在哪个点 \(u\),假设有一个环 \(C\),我们总是可以从当前点走到 \(C\) 中的任意一点 \(v\),绕着 \(C\) 走一圈后回到 \(v\),再回到 \(u\),这期间 \(u\to v\)路径被走了两遍,相当于没有;而我们的边权异或和只多出来了 \(C\) 上的那一部分。
综上,我们证明了所有 \(1 \to n\) 的路径上边权异或和,就是 \(1 \to n\) 在树上的路径的边权异或和,以及任意包含恰好一条非树边的环的边权异或和线性组合后的结果。
那么对后者建出线性基,查询前者在后者中的最大异或值即可,总复杂度 \(\mathcal{O}(n+(m-n)\log V)\)。
#include <bits/stdc++.h>
using namespace std;
const int N=50005,M=200005,D=64;
#define ll long long
ll h[N],e[M],ne[M],W[M],idx;
ll f[N],w[D];
bool vis[N];
bool ins(ll x){
for(int i=D-1;i>=0;i--) {
if((x>>i)&1){
if(w[i]) x^=w[i];
else{
w[i]=x;
return true;
}
}
}
return false;
}
void simp(){
for(int i=D-1;i>=0;i--){
if(w[i]!=0){
for(int j=D-1;j>i;j--)
if((w[j]>>i)&1) w[j]^=w[i];
}
}
}
ll mx(ll x){
ll ans=x;
for (int i=D-1;i>=0;i--)
ans=max(ans,ans^w[i]);
return ans;
}
void add(int u,int v,ll w){
e[idx]=v,W[idx]=w,ne[idx]=h[u],h[u]=idx++;
}
void dfs(int u,ll ans){
f[u]=ans,vis[u]=1;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(!vis[j]) dfs(j,ans^W[i]);
else ins(ans^W[i]^f[j]);
}
}
int main(){
memset(h,-1,sizeof(h));
int n,m;
cin>>n>>m;
for(int i=1;i<=m;i++){
ll u,v,w;
cin>>u>>v>>w;
add(u,v,w);add(v,u,w);
}
dfs(1,0);
simp();
cout<<mx(f[n])<<endl;
return 0;
}
CF724G Xor-matic Number of the Graph
\(n\) 个点的无向图,有边权。定义三元组 \((u,v,w)(1\le u < v\le n)\) 合法当且仅当存在从点 \(u\) 到点 \(v\) 存在一条边权异或和为 \(w\) 的路径,经过多次的边需要算多次。求所有合法三元组的 \(w\) 值之和对 \(10^9+7\) 取模的值。
\(u\) 到 \(v\) 所有路径的异或和等于 \(d_u\oplus d_v\oplus B\),其中 \(d_x\) 表示 \(x\) 到根的路径的异或和。
对于一对 \((u,v)\),尝试统计 \(d_u\oplus d_v\oplus B\) 中所有数的和。
直接做并不是很好做,考虑按位分开做:
- 对于线性基 \(B\) 和二进制位 \(w\),有结论:
- 设 \(B\) 中元素个数为 \(S\),则 $B¥ 可以表示出 \(2^S\) 个不同的数。
- 如果 \(B\) 中存在二进制第 \(w\) 位为 \(1\) 的数,则那 \(2^S\) 个数中恰有 \(2^{S-1}\) 个数的二进制第 \(w\) 位为 \(1\),另外 \(2^{S-1}\) 个数的二进制第 \(w\) 位为 \(0\)。
- 如果 \(B\) 中不存在二进制第 \(w\) 位为 \(1\) 的数,显然不可能表示出二进制第 \(w\) 位为 \(1\) 的数,全部 \(2^S\) 个数的二进制第 \(w\) 位均为 \(0\)。
可以通过组合恒等式 \(\sum_{i=0}^{n}\binom{n}{i}[i\bmod 2=1]=\begin{cases}0&,n=0\\2^{n-1}&,n>0\end{cases}\) 证明。
枚举二进制位 \(w\),考虑线性基 \(B\) 中是否存在二进制第 \(w\) 位为 \(1\) 的数。
如果存在,这意味着无论 \(d_u,d_v\) 的二进制第 \(w\) 位是否为 \(1\),都恰有 \(2^{S-1}\) 条使得异或和的二进制第 \(w\) 位为 \(1\) 的路径。这意味着 \(u,v\) 可以随便选,对答案的贡献为 \(2^w2^{S-1}\binom{n}{2}\)。
如果不存在,这意味着 \(d_u,d_v\) 的二进制第 \(w\) 位必须恰有一个为 \(1\),并且此时存在 \(2^S\) 条使得异或和的二进制第 \(w\) 位为 \(1\) 的路径。
这意味着 \(d_u,d_v\) 的第 \(w\) 位必须恰有一个为 \(1\),记第 \(w\) 位为 \(1\) 的 \(d_x\) 的个数为 \(x\),对答案的贡献为 \(2^w2^Sx(n-x)\)
对于每个联通块分别计算即可。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define ll long long
const int D=64,N=100005,M=N*4,P=1e9+7;
ll h[N],e[M],ne[M],W[M],idx;
ll w[D],f[N],s[N],C,t;
bool vis[N];
bool ins(ll x){
for(int i=60;i>=0;i--){
if((x>>i)&1){
if(w[i]) x^=w[i];
else{
w[i]=x;
++C;
return true;
}
}
}
return false;
}
void add(int u,int v,ll w){
e[idx]=v,W[idx]=w,ne[idx]=h[u],h[u]=idx++;
}
void dfs(int u,ll ans){
f[u]=ans,vis[u]=1,s[++t]=u;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(!vis[j]) dfs(j,ans^W[i]);
else ins(ans^W[i]^f[j]);
}
}
signed main(){
memset(h,-1,sizeof(h));
int n,m;
cin>>n>>m;
for(int i=1;i<=m;i++){
ll u,v,w;
cin>>u>>v>>w;
add(u,v,w),add(v,u,w);
}
ll ans=0;
for(int i=1;i<=n;i++){
if(vis[i]==0){
memset(w,0,sizeof(w));
C=t=0;
dfs(i,0);
for(int j=0;j<60;j++){
ll c=(1ll<<j)%P;
bool ok=0;
for(int k=60;k>=0;k--) if((w[k]>>j)&1) ok=1;
if(ok) ans=(ans+(ll)t*(t-1)/2%P*((1ll<<C-1)%P)%P*c)%P;
else{
int x=0;
for(int i=1;i<=t;i++) if((f[s[i]]>>j)&1) ++x;
ans=(ans+(ll)x*(t-x)%P*((1ll<<C)%P)%P*c)%P;
}
}
}
}
cout<<ans%P<<endl;
return 0;
}
P10682 [COTS 2024] 奇偶南瓜 Tikvani
给定 \(n\) 个点 \(m\) 条边的 DAG,求有多少种给每条边赋 \(0/1\) 权值的方式使得任意两条起终点相同的路径权值和 \(\bmod 2\) 都同余。\(n,m\le 400\)。
对每个起点 \(u\) 分别考虑,根据经典结论,先求出一棵以 \(u\) 为根的外向 dfs 树,设树上 \(u\to v\) 的路径为 \(f_u\),那么只要考虑恰经过一条非树边的环。
即对于一条非树边 \(x\to y\),我们只要求 \(f_x\oplus f_y\oplus w(x\to y)=0\),并且不难证明这是充分的。
对于任意一条路径,找到其中的第一条非树边 \(x\to y\),根据限制,可以把到 \(y\) 的路径等效成 \(f_y\),递归进行此过程即可证明该路径合法。
那么此时总共只有 \(\mathcal O(nm)\) 条限制,暴力插入并用 bitset
优化高斯消元维护。
时间复杂度 \(\mathcal O\left(\dfrac{nm^3}\omega\right)\)。
#include<bits/stdc++.h>
using namespace std;
const int N=405,P=1e9+7;
int n,m;
struct s{int v,id;};
vector<s> G[N];
bitset <N> w,d[N],x[N];
bool vis[N];
void ins() {
for(int i=1;i<=m;++i) if(w[i]) {
if(!x[i].any())
return x[i]=w,void();
else w^=x[i];
}
}
void dfs(int u) {
vis[u]=true;
for(auto e:G[u]) {
if(!vis[e.v]) d[e.v]=d[u],d[e.v].set(e.id),dfs(e.v);
else w=d[u],w^=d[e.v],w.flip(e.id),ins();
}
}
signed main() {
cin>>n>>m;
for(int i=1,u,v;i<=m;++i) cin>>u>>v,G[u].push_back({v,i});
for(int i=1;i<=n;++i) {
for(int u=1;u<=n;++u)
d[u].reset(),vis[u]=false;
dfs(i);
}
int ans=1;
for(int i=1;i<=m;++i) if(!x[i].any()) ans=ans*2%P;
cout<<ans<<endl;
return 0;
}
与数据结构配合的线性基
- 线性基插入的复杂度是 \(\mathcal{O}(log V )\)。
- 线性基合并的复杂度是 \(\mathcal{O}(log^2 V )\),方法是把其中一个的所有元素插入到另一边。
CF1100F Ivan and Burgers
给一个长为 \(n\) 的序列 \(a\),\(q\) 次询问,问从 \(a_l, a_{l+1}, \cdots , a_r\) 中选若干个数,异或和最大为多少。\(1 \le n, q \le 5 × 10^5, 0 \le a_i \le 10\)
在一些数中取若干个数,要求异或和最大,不难想到线性基。
直接线段树维护区间,则复杂度达到 \(O((n+q)\log^3 n)\),用ST表的话,预处理复杂度也达到了 \(O(n\log^3 n)\),难以接受。
考虑离线,然后分治。每次考虑解决经过当前区间中点的询问。
对于一个区间,我们处理出中点mid往左的后缀线性基、往右的前缀线性基,则可以在 \(O(\log^2 n)\)(线性基合并)的复杂度解决一个经过中点的询问。
然后,对于两端点都在左边的区间,往左递归处理;两端点都在右边的区间,往右递归处理。
分治时间复杂度:\(T(n)=2T(\frac n 2)+O(n\log n)=O(n\log^2 n)\)。加上处理所有询问的复杂度 \(O(q\log^2 n)\),总复杂度 \(O((n+q)\log^2 n)\)。
#include <bits/stdc++.h>
using namespace std;
const int N=500005;
struct ss{
int s[20];
void ins(int x){
if(x)
for(int i=19;i>=0;i--)
if(x>>i&1){
if(!s[i]) {s[i]=x;break;}
x^=s[i];
}
}
void clear(){
memset(s,0,sizeof(s));
}
}d[N];
int merge(ss a,ss b){
for(int i=19;i>=0;i--) a.ins(b.s[i]);
int res=0;
for(int i=19;i>=0;i--)
res=max(res,res^a.s[i]);
return res;
}
int a[N],n,q[N],qL[N],qR[N],ans[N],m;
void solve(int l,int r,int L,int R){
if(L>R) return;
if(l==r){
for(int i=L;i<=R;i++) ans[q[i]]=a[l];
return;
}
static int tmpL[N],tmpR[N];
int tL=0,tR=0,mid=l+r>>1;
d[mid].clear();d[mid].ins(a[mid]);
for(int i=mid-1;i>=l;i--) (d[i]=d[i+1]).ins(a[i]);
for(int i=mid+1;i<=r;i++) (d[i]=d[i-1]).ins(a[i]);
for(int i=L;i<=R;i++){
if(qL[q[i]]<=mid){
if(qR[q[i]]>mid) ans[q[i]]=merge(d[qL[q[i]]],d[qR[q[i]]]);
else tmpL[++tL]=q[i];
}
else tmpR[++tR]=q[i];
}
for(int i=1;i<=tL;i++)q[L+i-1]=tmpL[i];
for(int i=1;i<=tR;i++)q[L+tL+i-1]=tmpR[i];
solve(l,mid,L,L+tL-1);
solve(mid+1,r,L+tL,L+tL+tR-1);
}
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
cin>>m;
for(int i=1;i<=m;i++)cin>>qL[i]>>qR[i],q[i]=i;
solve(1,n,1,m);
for(int i=1;i<=m;i++)cout<<ans[i]<<endl;
return 0;
}
P3292 [SCOI2016] 幸运数字
按照倍增的思想,\(g_{i,j}\) 表示i往上跳 \(2^j\) 步的线性基,然后 dp 的时候暴力合并两个 \(g_{i,j-1}\) 与 \(g_{fa_{i,j-1},j-1}\),复杂度 \(O(N\log N\times 64)\)。
#include<bits/stdc++.h>
using namespace std;
const int M=20005,D=64;
typedef long long ll;
ll a[100005],n;
struct s{
ll w[64];
s(){ memset(w,0,sizeof(w)); }
void insert(ll x){
for(int i=D-1;i>=0;i--){
if(!((x>>i)&1)) continue;
if(!w[i]){w[i]=x;return ;}
x^=w[i];
}
}
ll mx(){
ll ans=0;
for(int i=D-1;i>=0;i--)
ans=max(ans,ans^w[i]);
return ans;
}
};
s merge(s u,s v){
for(int i=D-1;~i;i--)
if(v.w[i]) u.insert(v.w[i]);
return u;
}
int idx,ne[2*M],h[2*M],e[2*M];
void add(int x,int y){
e[++idx]=y,ne[idx]=h[x],h[x]=idx;
}
int f[M][21],dep[M];
s g[M][21];
void dfs(int u,int fa){
f[u][0]=fa;
g[u][0].insert(a[fa]);
dep[u]=dep[fa]+1;
for(int i=h[u];i;i=ne[i]){
int to=e[i];
if(to!=fa) dfs(to,u);
}
}
ll query(int x,int y){
if(dep[x]>dep[y])swap(x,y);
s t;
t.insert(a[x]),t.insert(a[y]);
for(int i=20;i>=0;i--){
if(dep[f[y][i]]>=dep[x]) t=merge(t,g[y][i]),y=f[y][i];
}
if(x==y)return t.mx();
for(int i=20;i>=0;i--){
if(f[x][i]!=f[y][i]){
t=merge(t,g[x][i]);
t=merge(t,g[y][i]);
x=f[x][i],y=f[y][i];
}
}
t=merge(t,g[x][0]);
return t.mx();
}
int main(){
int q;
cin>>n>>q;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<n;i++){
int x,y;
cin>>x>>y;
add(x,y),add(y,x);
}
dfs(1,0);
for(int j=1;j<=20;j++){
for(int i=1;i<=n;i++){
f[i][j]=f[f[i][j-1]][j-1];
g[i][j]=merge(g[f[i][j-1]][j-1],g[i][j-1]);
}
}
while(q--){
int x,y;
cin>>x>>y;
cout<<query(x,y)<<endl;
}
return 0;
}
P5607 [Ynoi2013] 无力回天 NOI2017
由于要求维护区间信息,采用线段树维护区间线性基。
考虑差分,将区间修改转化为两个单点修改。接下来考虑如何在差分后的序列上求答案。
序列 \(b\) 满足 \(b_i=a_i \oplus a_{i-1}\),则有:\(a_x=b_1 \oplus b_2 \oplus b_3 \oplus \cdots \oplus b_x\)
不难发现,对于 \(a\) 序列的区间 \([l,r]\),其中任意一种数字选选取方案都可以用 \(a_l,b_{l+1},b_{l+2},b_{l+3}...b_{r}\) 中选取若干数表示出来,即序列 \(a_l,a_{l+1},...a_r\) 的线性基与\(a_l,b_{l+1},b_{l+2},...b_{r}\) 相同。
查询时,求出 \(b_{l+1},b_{l+2},b_{l+3}...b_{r}\) 的线性基和 \(a_l\),将 \(a_l\) 插入线性基,贪心统计答案即可。
线段树上合并两个线性基复杂度\(O(\log^2V)\),因此总复杂度为\(O(n\log + n\log^2V)\)。
#include <bits/stdc++.h>
#define L(u) (u<<1)
#define R(u) (u<<1|1)
#define mid ((l+r)>>1)
using namespace std;
const int N = 50050;
struct XOR {
int d[30], s;
void init() {memset(d, 0, sizeof d);}
void insert(int x) {
for(int i = 29; i >= 0; --i)
if((x >> i) & 1) {
if(d[i]) x ^= d[i];
else {d[i] = x; break;}
}
}
} t[N << 2]; int a[N], n, m;
XOR merge(XOR a, XOR b) {
XOR res; res.init();
for(int i = 29; i >= 0; --i) if(a.d[i]) res.d[i] = a.d[i]; else res.d[i] = b.d[i];
for(int i = 29; i >= 0; --i) if(a.d[i] && b.d[i]) res.insert(b.d[i]);
res.s = a.s ^ b.s; return res;
}
void P(int u) {t[u] = merge(t[L(u)], t[R(u)]);}
void build(int u, int l, int r) {
if(l == r) {t[u].insert(t[u].s = a[l]); return;}
build(L(u), l, mid); build(R(u), mid+1, r); P(u);
}
void modify(int u, int l, int r, int p, int x) {
if(l == r) {t[u].init(), t[u].insert(t[u].s ^= x); return; }
p <= mid ? modify(L(u), l, mid, p, x) : modify(R(u), mid+1, r, p, x); P(u);
}
int finds(int u, int l, int r, int p) {
return l == r ? t[u].s : (p <= mid ? finds(L(u), l, mid, p) : finds(R(u), mid+1, r, p) ^ t[L(u)].s);
}
XOR findi(int u, int l, int r, int tl, int tr) {
if(tl <= l && r <= tr) return t[u];
if(tl > mid) return findi(R(u), mid+1, r, tl, tr);
if(tr <= mid) return findi(L(u), l, mid, tl, tr);
return merge(findi(L(u), l, mid, tl, tr), findi(R(u), mid+1, r, tl, tr));
}
int main() {
cin>>n>>m;
for(int i = 1; i <= n; ++i) cin>>a[i];
for(int i = n-1; i; --i) a[i+1] ^= a[i];
build(1, 1, n);
for(int opt, l, r, x; m--; ) {
cin>>opt>>l>>r>>x;
if(opt == 1) {
modify(1, 1, n, l, x);
if(r < n) modify(1, 1, n, r+1, x);
} else {
int tmp = finds(1, 1, n, l);
if(l == r) cout<<max(x, x^tmp)<<endl;
else {
XOR t = findi(1, 1, n, l+1, r); t.insert(tmp);
for(int i = 29; i >= 0; --i) x = max(x, x ^ t.d[i]);
cout<<x<<endl;
}
}
}
}
P3389 【模板】高斯消元法
#include<bits/stdc++.h>
using namespace std;
int n,pl;
double a[1001][1001];
int main(){
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n+1;j++)
cin>>a[i][j];
for(int i=1;i<=n;i++){
pl=i;
for(int j=i;j<=n;j++)
if(fabs(a[j][i])>fabs(a[pl][i])) pl=j;
if(a[pl][i]==0) {cout<<"No Solution";return 0;}
for(int j=1;j<=n+1;j++)
swap(a[i][j],a[pl][j]);
double k=a[i][i];
for(int j=1;j<=n+1;j++)
a[i][j]=a[i][j]/k;
for(int j=1;j<=n;j++){
if(i!=j){
double ki=a[j][i];
for(int m=1;m<=n+1;m++)
a[j][m]=a[j][m]-ki*a[i][m];
}
}
}
for(int i=1;i<=n;i++)
printf("%.2lf\n",a[i][n+1]);
return 0;
}
P2447 [SDOI2010] 外星千足虫
一种方法是直接使用线性基:方程组有唯一解相当于 \(rk(S) =n\),而一个矩阵的秩就是把所有元素插入线性基之后,线性基中元素的个数。于是从前往后把
所有方程插入线性基即可,时间复杂度 \(O(\frac{n^2m}{ω})\)。
另一方面,我们也可以使用高斯消元,不过在消元的时候尽可能选编号更小的方程来进行操作;这本质上和线性基是相同的,相当于快速找到了下一次线性
基发生改变的位置。
#include <bits/stdc++.h>
using namespace std;
char s[1010];
bitset<1010> a[2010];
int f(int n, int m){
int ans = -1;
for (int i = 1; i <= n; i++){
int cur = i;
while (cur <= m && !a[cur].test(i)) cur++;
if (cur > m) return 0;
ans = max(ans, cur);
if (cur != i)swap(a[cur], a[i]);
for (int j = 1; j <= m; j++)
if (i != j && a[j].test(i))
a[j] ^= a[i];
}
return ans;
}
int main(){
int n, m;
cin>>n>>m;
for (int i = 1, res; i <= m; i++){
cin>>s>>res;
for (int j = 0; j < n; j++)
a[i].set(j+1,s[j]=='1');
a[i].set(0, res);
}
int ret = f(n, m);
if (ret){
cout<<ret<<endl;
for (int i = 1; i <= n; i++)
cout<<(a[i].test(0) ? "?y7M#" : "Earth") <<endl;
}
else cout<<"Cannot Determine\n"<<endl;
return 0;
}
CF24D Broken robot
\(n\) 行 \(m\) 列的矩阵,现在在 \((x,y)\),每次等概率向左,右,下走或原地不动,但不能走出去,问走到最后一行期望的步数。
注意,\((1,1)\) 是木板的左上角,\((n,m)\) 是木板的右下角。\(1\le n,m\le 10^3\),\(1\le x\le n\),\(1\le y\le m\)。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <iomanip>
using namespace std;
const int N = 1010;
int n, m;
int x, y;
double f[N][N];
double a[N][N];
void gauss()
{
for (int i = 1; i <= m; i ++ )
{
double t = a[i + 1][i] / a[i][i];
int d[3] = {i, i + 1, m + 1};
for (int j = 0; j < 3; j ++ )
a[i + 1][d[j]] -= t * a[i][d[j]];
a[i + 1][i] = 0;
}
for (int i = m; i; i -- )
{
a[i - 1][m + 1] -= a[i - 1][i] / a[i][i] * a[i][m + 1];
a[i - 1][i] = 0;
}
}
int main()
{
cin >> n >> m;
cin >> x >> y;
if (m == 1) printf("%.4lf\n", 2.0 * (n - x));
else
{
for (int i = n - 1; i >= x; i -- )
{
a[1][1] = 2.0 / 3, a[1][2] = -1.0 / 3, a[1][m + 1] = f[i + 1][1] / 3 + 1;
a[m][m] = 2.0 / 3, a[m][m - 1] = -1.0 / 3, a[m][m + 1] = f[i + 1][m] / 3 + 1;
for (int j = 2; j < m; j ++ )
{
a[j][j - 1] = -1.0 / 4, a[j][j] = 3.0 / 4, a[j][j + 1] = -1.0 / 4;
a[j][m + 1] = f[i + 1][j] / 4 + 1;
}
gauss();
for (int j = 1; j <= m; j ++ ) f[i][j] = a[j][m + 1] / a[j][j];
}
cout.setf(std::ios::fixed);
cout << setprecision(4) << f[x][y];
//cout << f[x][y];
}
return 0;
}