1. 程式人生 > 實用技巧 >題解 P7096 【[yLOI2020] 瀘沽尋夢】

題解 P7096 【[yLOI2020] 瀘沽尋夢】

題目傳送門

好的閱讀體驗

第一次寫題解寫得稍微詳細點

題意簡述

給定長度為 \(n\) 的非負整數數列 \(a_i\)\(m\) 次操作,每次操作給定 \(p\)\(x\),將相鄰兩個數 \(a_p\)\(a_{p+1}\) 按位異或上 \(x\),要求在每次操作後求出異或和為 \(0\) 的子區間個數。

題目分析

為了方便這裡記 \(a[l...r]\) 的異或和為 \(S(l,r)\)

首先,需要知道異或有結合律與交換律。

帶修有些麻煩,先來考慮沒有修改時如何計算答案。

最暴力的當然是列舉每個子區間並 \(O(n)\) 判斷,總共 \(O(n^3)\),帶修就是 \(O(n^4)\)

,期望得分 \(10 pts\)

\(S(1,l-1) \oplus S(l,r)=S(1,r)\)

\(S(1,l-1) \oplus S(l,r) \oplus S(1,l-1)=S(1,r) \oplus S(1,l-1)\)

\(S(l,r)=S(1,r) \oplus S(1,l-1)\)

用字首和優化一下,判斷就可以 \(O(1)\) 了,總共 \(O(n^2)\),帶修 \(O(n^3)\),期望得分 \(20pts\)

由上面那個式子可以發現 \(S(l,r)=0\) 當且僅當 \(S(1,l-1)=S(1,r)\)

所以如果用 \(cnt_k\) 表示所有 \(S(1,i)\)

中值為 \(k\) 的個數,那麼答案就是

\(ans= \sum\limits_i \frac{1}{2}cnt_i(cnt_i-1)\)

考慮用 map 存,當然可以在統計 \(cnt_i\) 時順便更新答案

\(\Delta ans=\frac{1}{2}[cnt_i(cnt_i+1)-cnt_i(cnt_i-1)]=cnt_i\)

總共 \(O(n)\),帶修 \(O(n^2)\),期望得分 \(40pts\)

單次計算已經很優了,來考慮帶修。

然後會發現每次修改只會改變 \(S(1,p)\),因為對於後面的字首異或和,每個都異或上了兩次 \(x\),也就是不變。

答案維護和上面一樣。

然而由於 map 常數太大了,貌似要用 unordered_map 才能卡過去……

請注意,\(a_i,x \leq Y\) 不能說明 \(a_i \oplus x \leq Y\)

別忘了long long

程式碼

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;

int n,m;
long long s[N];
long long ans;
long long ansxor,ansj,ansmax=0,ansmin=1e18;
unordered_map <long long,int> mp;

int main(){
	scanf("%d%d",&n,&m);
	mp[0]++;
	for(int i=1;i<=n;i++){
		int a;
		scanf("%d",&a);
		s[i]=s[i-1]^a;//字首異或和 
		ans+=mp[s[i]],mp[s[i]]++;
	}
	
	for(int i=1;i<=m;i++){
		int p,x;
		scanf("%d%d",&p,&x);
		ans-=mp[s[p]]-1,mp[s[p]]--;
		ans+=mp[s[p]^x],mp[s[p]^x]++;
		s[p]^=x;//將s[p]變為s[p]^x,同時更新答案 
		
		ansxor^=ans;
		if(ans & 1ll) ansj++;
		ansmax=max(ansmax,ans);
		ansmin=min(ansmin,ans); 
	}
	printf("%lld\n%lld\n%lld\n%lld\n",ansxor,ansj,ansmax,ansmin);
}