題解 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)\)
\(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)\)
\(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);
}