带删线性基——2025.4.21 鲜花

带删线性基

メズマライザー feat. 初音ミク&重音テト
実際の感情はNo Think!
気付かないフリ...?
絶対的な虚実と心中
そうやって減っていく安置
傷の切り売り
脆く叫ぶ、醜態
そんなあなたにオススメ!
最高級の逃避行
やがて、甘美な罠に
釣られたものから救われる?
もはや正気の沙汰では
やっていけないこの娑婆じゃ
敢えて素知らぬ顔で
身を任せるのが最適解?
言葉で飾った花束も
心を奪えば、本物か?
全てが染まっていくような
事象にご招待
さらば!
こんな時代に誂えた
見て呉れの脆弱性
本当の芝居で騙される
矢鱈と煩い心臓の鼓動
残機は疾うにないなっている;;
擦り減る耐久性
目の前の事象を躱しつつ
生きるので手一杯!
誰か、助けてね(^^♪
「あなた段々眠くなる」
浅はかな催眠術
頭、身体、煙に巻く
まさか、数多誑かす!?
目の前で揺らぐ硬貨
動かなくなる彼方
「これでいいんだ」
自分さえも騙し騙しShut down
「あなた段々眠くなる」
浅はかな催眠術
頭、身体、煙に巻く
まさか、数多誑かす!?
目の前で揺らぐ硬貨
動かなくなる彼方...
(強制解除)
どんなに今日を生き抜いても
報われぬEveryday
もうBotみたいなサイクルで
惰性の瞬間を続けているのだ
運も希望も無いならば
尚更しょうがねえ
無いもんは無いで、諦めて
余物で勝負するのが運命
こんな時代に誂えた
見て呉れの脆弱性
本当の芝居で騙される
矢鱈と煩い心臓の鼓動
賛美はもう意味ないなっている;;
偽のカリスマ性
現実を直視しすぎると
失明しちゃうんだ!
だから、適度にね(^^♪

考虑删除的贡献如何撤销。

每个值插入线性基以后只会对插入到比他靠后的位置的数贡献。

不妨先考虑离线的问题,离线可以直到每个值最晚是什么时候被删掉的,当插入到某位时若当前这位的删除时间比插入的数早,则交换。这样可以保证所有贡献了的数只会贡献给比他靠前删的。

是非常简洁的单 \(\log\)

Code
/* Local File
in_out/in.in
in_out/out.out
*/
#include <bits/stdc++.h>
using namespace std;
using llt = long long;
using ull = unsigned long long;
using llf = long double;
#define endl '\n'

const int N = 5e5 + 3, LG = 30;
int n, _a[N], del[N]; unordered_map<int, int> id, ct;

class Lb{
private:
	int o[LG + 3], tim[LG + 3];
public:
	void Ins(int v, int t){
		for(int i = LG; ~i; --i) if(v & 1 << i)
			if(o[i]){
				if(tim[i] < t) swap(tim[i], t), swap(o[i], v);
				v ^= o[i];
			}else{ o[i] = v, tim[i] = t; break; }
	}
	int Mx(int t){
		int r = 0;
		for(int i = LG; ~i; --i) if(!(r & 1 << i) && tim[i] > t) r ^= o[i];
		return r;
	}
} lb;
int main(){
	ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
	cin >> n;
	for(int i = 1; i <= n; ++i){
		int v; cin >> v; _a[i] = v;
		if(v < 0 && !--ct[-v]) del[id[-v]] = i;
		else if(v > 0 && !ct[v]++) del[i] = n + 1, id[v] = i;
	}
	for(int i = 1; i <= n; ++i){
		if(_a[i] > 0) lb.Ins(_a[i], del[i]);
		cout << lb.Mx(i) << endl;
	}
}

考虑在线。

先考虑传统的在线删除线性基。

我们延续上面的思路,考虑撤销一位的贡献,不太简单的分讨。

设删除的数是 \(a\)

若删除的数没有插入到线性基中,则直接删除。

若存在一个数没被删除 \(b\) 且其没有插入进线性基中,并且其尝试插入线性基时 \(a\) 异或了 \(b\),则可以用 \(b\) 平替 \(a\),为了方便我们直接将 \(b\) 删掉即可。

注意到直接用 \(b\) 替换 \(a\) 会使得后面的数对前面造成贡献,可以考虑去掉这部分贡献,这样后面的分讨会简单一点,不去也无所谓。

否则考虑其异或过的所有数中没删除的都存在在线性基中,我们将 \(a\) 对其他数的贡献撤掉,若存在一个比 \(a\) 靠后的数 \(b\) 被贡献了则用 \(b\) 替换到 \(a\) 重新贡献。

大概能实现到复杂度是 \(n \log^2 V\) 的。

上面的过程使用性不高,过于难写了,有没有优秀的做法呢?

考虑 P11620 [Ynoi Easy Round 2025] TEST_34 的 zak 做法,即以 \(\frac 12\) 概率取出 \(\log qV + \epsilon\) 个子集,并用这些子集的异或和做线性基。

只需维护一下所有子集的全局异或和即可。

是非常简洁的 \(\log^2\)

Code
/* Local File
in_out/in.in
in_out/out.out
*/
#include <bits/stdc++.h>
using namespace std;
using llt = long long;
using ull = unsigned long long;
using llf = long double;
#define endl '\n'

const int N = 5e5 + 3, LG = 30, M = 60;
int n, xm[M + 3]; unordered_map<int, int> id, ct;
bool pin[M + 3][N];

class Lb{
private:
	int o[LG + 3];
public:
	void Clr(){ memset(o, 0, sizeof o); }
	void Ins(int v){
		for(int i = LG; ~i; --i) if(v & 1 << i)
			if(o[i]) v ^= o[i];
			else{ o[i] = v; break; }
	}
	int Mx(){
		int r = 0;
		for(int i = LG; ~i; --i) if(!(r & 1 << i)) r ^= o[i];
		return r;
	}
} lb;

int main(){
	ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
	srand(0x7f);
	cin >> n;
	for(int tt = 1; tt <= n; ++tt){
		int v; cin >> v;
		if(v < 0 && !--ct[-v]){
			int d = id[v = -v];
			for(int i = 1; i <= M; ++i)
				if(pin[i][d]) pin[i][d] = 0, xm[i] ^= v;
		}else if(v > 0 && !ct[v]++){
			id[v] = tt;
			for(int i = 1; i <= M; ++i)
				if(pin[i][tt] = rand() % 2) xm[i] ^= v;
		}
		lb.Clr();
		for(int i = 1; i <= M; ++i) if(xm[i])
			lb.Ins(xm[i]);
		cout << lb.Mx() << endl;
	}
}
P

posted @ 2025-04-21 16:19  xrlong  阅读(106)  评论(5)    收藏  举报

Loading