1. 程式人生 > 實用技巧 >Yoi #321. 永不加班

Yoi #321. 永不加班

題面

現在你得到了一個任務,給你一個長度為n的數列,數列上的每個點都有一個顏色。
你會得到q次詢問,每次問你一段區間裡有多少個子序列滿足子序列的所有點的顏色互不相同(子序列可以為空),答案對998244353取模。
永不加班的你希望可以做到實時回答詢問,所以詢問強制線上,即每次的詢問要異或上次的答案。

分析

經典分塊

這每種顏色有\(c_{i}\)種,則答案顯然為\(\prod_{i}c_{i}\)

預處理出塊與塊之間的答案,塊與塊之間的各種顏色數量(對塊進行顏色字首和)

列舉不在整塊裡的\(2\sqrt{n}\)個點,暴力刪去再加入即可,期望時間\(O(nlgn)\)

#include<bits/stdc++.h>
#define ll long long
const int p=998244353;
using namespace std;

const int N=1e5+5,M=405;
int inv[N],n,a[N],bel[N],m,f[M][M],s[M][N],t[N];
ll ans;

int main() {
	freopen("color.in","r",stdin);
	freopen("color.out","w",stdout);
	inv[1]=1;
	for(int i=2;i<=100001;i++) {
		inv[i]=(ll)(p-p/i)*inv[p%i]%p;
	}	
	scanf("%d",&n);
	for(int i=1;i<=n;i++) {
		scanf("%d",&a[i]),m=max(m,a[i]);
	}
	int S=sqrt(n);
	for(int i=1;i<=n;i++) bel[i]=(i-1)/S+1;
	for(int j=1;j<=bel[n];j++) {
		ans=1;
		for(int i=j;i>=1;i--) {
			for(int k=(i-1)*S+1;k<=min(n,i*S);k++) {
				t[a[k]]++;
				ans=ans*inv[t[a[k]]]%p*(t[a[k]]+1)%p;
			}
			f[i][j]=ans;
		}
		for(int i=1;i<=m;i++) {
			s[j][i]=t[i],t[i]=0;
		}
	}
	int T; scanf("%d",&T); ans=0;
	while(T--) {
		int l,r; scanf("%d%d",&l,&r),l^=ans,r^=ans;
		int t1=bel[l],t2=bel[r];
		if(t2-t1<=1) {
			ans=1;
			for(int i=l;i<=r;i++) t[a[i]]++;	
			for(int i=l;i<=r;i++) {
				ans=ans*(t[a[i]]+1)%p;
				t[a[i]]=0;
			}
			printf("%lld\n",ans);
		}else {
			ans=f[t1+1][t2-1];
			for(int i=l;i<=t1*S;i++) {
				int num=s[t2-1][a[i]]-s[t1][a[i]]+t[a[i]];
				ans=ans*inv[num+1]%p*(num+2)%p;
				t[a[i]]++;
			}
			for(int i=(t2-1)*S+1;i<=r;i++) {
				int num=s[t2-1][a[i]]-s[t1][a[i]]+t[a[i]];
				ans=ans*inv[num+1]%p*(num+2)%p;
				t[a[i]]++;
			}
			for(int i=l;i<=t1*S;i++) t[a[i]]=0;
			for(int i=(t2-1)*S+1;i<=r;i++) t[a[i]]=0;
			printf("%lld\n",ans);
		}
	}
	return 0;
}