1. 程式人生 > 實用技巧 >【題解】[SCOI 2019] 函式【線性基】

【題解】[SCOI 2019] 函式【線性基】

題目連結

題意

給定 \(a_{1\cdots n},w\)。有 \(n\) 個 01 向量,第 \(i\) 個為 \(a_i,a_i+1,a_i+2,\cdots a_i+w\) 轉化為一定長度 \(L\) 的二進位制後拼接起來。求這些向量組成矩陣的軼(異或意義下)。\(n,w,a_i\leq 2^{17}\)

題解

嘗試通過做列變換簡化矩陣。

以每 \(L\) 列為一組,先把後一組異或上前一組,得到每行形如 xxxxxx|000111|000001|000011|000001|001111...

將組內每一列異或上前一列,得到每行形如 xxxxxx|000100|000001|000010|000001|001000...

找到 1 的位置最靠前的一組,這個 1(下面將其稱為“關鍵 1”)的位置確定後,其他 1 的位置也都能確定。故以組內位置為第一關鍵字,組間順序為第二關鍵字,挨個假設每個位置是關鍵 1,用這一列去異或在此假設下其他為 1 的列。在遇到真正的關鍵 1 之前,我們都在用 0 異或其他數,故沒有影響;關鍵 1 會把其他所有位置變成 0,所以每行後面部分只剩一個關鍵 1。

開始線性基:先以關鍵 1 為主元,在後面消元;若有關鍵 1 相同的行,選定一行放入線性基,其他行異或它後用前 17 位進行標準的線性基。

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10,L=19,mod=998244353;
int n,w,a[N],b[L];
map<pair<int,int>,int>mp;
void ins(int x){
	for(int i=L-1;i>=0;--i){
		if((x>>i)&1){
			swap(b[i],x);
			if((x>>i)&1)x^=b[i];
		}
	}
}
int main(){
	int n,w;
	scanf("%d%d",&n,&w);
	for(int i=0;i<n;i++)scanf("%d",a+i);
	for(int i=0;i<n;i++){
		int k=a[i]^(a[i]+w),q=0;
		while(k>>q)++q;
		pair<int,int>p(q,(((a[i]>>(q-1))+1)<<(q-1))-a[i]);
		if(!w)ins(a[i]);
		else if(mp.count(p))ins(mp[p]^a[i]);
		else mp[p]=a[i];
	}
	int r=mp.size();
	for(int i=0;i<L;i++)if(b[i])++r;
	int t=1;while(r--)t<<=1,t>=mod&&(t-=mod);
	cout<<t;
}