2023.9.5 Online test

GloriousCc / 2023-09-05 / 原文

A

有一个长 \(n+1\) 的数组 \(a\),长 \(n\) 的数组 \(b\)
每次询问删除 \(a\) 中某个位置,求剩下的数跟 \(b\) 组成一种匹配 \(p\),使得 \(\max(a[i]-b[p[i]])\) 最小。

显然有一个贪心,就是排序后位置相同的匹配。
\(a,b\) 排序,求出 \(a\) 的前后缀匹配 \(b\) 的值,这样就可以处理删除某个数的贡献了。

code
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+10;
int n;
int b[N],c[N],ans1[N],ans2[N],ret[N];
struct node {
	int x,id;
} a[N];
bool cmp(node u,node v) {
	return u.x<v.x;
}
int main() {
	freopen("tie.in","r",stdin);
	freopen("tie.out","w",stdout);
	ios::sync_with_stdio(0);
	cin>>n;
	for(int i=1; i<=n+1; i++) cin>>a[i].x,a[i].id=i;
	sort(a+1,a+1+n+1,cmp);
	for(int i=1; i<=n; i++) cin>>b[i];
	sort(b+1,b+1+n);
	for(int i=1; i<=n+1; i++) {
		ans1[i]=max(ans1[i-1],a[i].x-b[i]);
	}
	for(int i=n+1; i>=1; i--) {
		ans2[i]=max(ans2[i+1],a[i].x-b[i-1]);
	}
	for(int i=1; i<=n+1; i++) {
		ret[a[i].id]=max(ans1[i-1],ans2[i+1]);
	}
	for(int i=1; i<=n+1; i++) printf("%d ",ret[i]);
	return 0;
}

B

给定 \(n\) 根木棍,要从中选出 \(6\) 根木棍,满足这 \(6\) 根木棍拼出一个正方形,注意木棍不能弯折。
问方案数(选出木棍相同但正方形不同算一种)。

只有两种方式凑成长方形,一种是 \(a=b=c+d=e+f\),另一种是 \(a=b=c=d+e+f\)
先处理第一种:\(a=b=c+d=e+f\)
防止算重,考虑钦定 \(c\le e\le f\le d\).
我们从小到大先枚举一个 \(d\),再枚举一个 \(c\),那么 \(a=b=c+d\) 已知。
在枚举 \(d\) 的时候,再处理出一个 \(cnt2_i\) 数组,表示两个数和为 \(i\) 的方案数。每次把 \(d\) 的贡献加进去即可。
考虑若 \(c>d\),贡献是 \(C(cnt_{c+d},2)\cdot (cnt_c\cdot cnt_d\cdot cnt2_{c+d}+C(cnt_c,2)\cdot C(cnt_d,2))\)
由于 \(cnt_2\) 中已有的贡献都是 \(e\le d\)\(e\) 贡献的,所以一定不会算重。
\(c=d\) 呢?贡献是 \(C(cnt_{c+d},2)\cdot (C(cnt_c,2)\cdot cnt2_{c+d}+C(cnt_c,4))\)

第二种:\(a=b=c=d+e+f\),钦定 \(d\le e\le f\).
从大到小枚举 \(d\),再次处理出 \(cnt2_i\) 数组。
再枚举一个 \(a\),贡献是 \(C(cnt_a,3)\cdot cnt_d\cdot cnt2_{a-d}\).
考虑 \(d=e\),那么如果 \(a-2d>d\) 的话,那么贡献是 \(C(cnt_a,3)\cdot C(cnt_a,2)\cdot cnt_{a-2d}\).
我们其实还没有结束,我们漏了 \(d=e=f\) 的情况,最后贡献加上 \(C(cnt_{a/3},3)\) 即可。

code
#include<bits/stdc++.h>
using namespace std;
const int N=5010,M=1e7+10;
typedef long long LL;
int n,a[N];
LL cnt[M],cnt2[M],C[N][5],Ans,ret;
vector<int> vec;
int main() {
	freopen("yist.in","r",stdin);
	freopen("yist.out","w",stdout);
	scanf("%d",&n);
	for(int i=1; i<=n; i++) {
		scanf("%d",&a[i]);
		if(!cnt[a[i]]) vec.push_back(a[i]);
		cnt[a[i]]++;
	}
	C[0][0]=1;
	for(int i=1; i<=n; i++) {
		C[i][0]=1;
		for(int j=1; j<=4; j++) C[i][j]=C[i-1][j]+C[i-1][j-1];
	}
	sort(vec.begin(),vec.end());
	for(int v:vec) {
		for(int w:vec) {
			if(v+w>=M) continue;
			LL tmp=C[cnt[v+w]][2];
			if(w>v) {
				Ans=Ans+tmp*cnt[v]*cnt[w]*cnt2[v+w];
				Ans=Ans+tmp*C[cnt[v]][2]*C[cnt[w]][2];
				cnt2[v+w]+=cnt[v]*cnt[w];
			}
			if(w==v) {
				Ans=Ans+tmp*C[cnt[w]][2]*cnt2[2*w];
				Ans=Ans+tmp*C[cnt[w]][4];
				cnt2[2*w]+=C[cnt[w]][2];
			}
		}
	}
	memset(cnt2,0,sizeof cnt2);
	reverse(vec.begin(),vec.end());
	for(int w:vec) {
		for(int v:vec) {
			LL tmp=C[cnt[v]][3];
			if(v>w) {
				Ans=Ans+tmp*cnt[w]*cnt2[v-w];
				if(v-w-w>w) Ans=Ans+tmp*C[cnt[w]][2]*cnt[v-w-w];
			}
		}
		for(int y:vec) {
			if(w+y>=M) continue;
			if(w<y)
				cnt2[w+y]+=cnt[w]*cnt[y];
			if(w==y)
				cnt2[2*w]+=C[cnt[w]][2];
		}
	}
	for(int v:vec) {
		LL tmp=C[cnt[v]][3];
		if(v%3==0) Ans=Ans+tmp*C[cnt[v/3]][3];
	}
	printf("%lld\n",Ans);
	return 0;
}

C

定义 \(k\) 子树为节点子树内距离不超过 \(k\) 的部分。如果一个子树不存在距离为 \(k\) 的部分,那么其不存在 \(k\) 子树。
你需要找到最大的 \(k\),满足存在两个节点的 \(k\) 子树是同构的。注意儿子顺序也是要考虑的。

树哈希处理同构。
显然答案可二分。我们如果用倍增代替二分呢?
我们处理 \(h[i][u]\),表示 \(u\)\(2^i\) 子树的哈希值。
我们现在想合并 \(h[i],h[j]\),使其成为 \(h[i+j]\)
\(p(u,k)\) 表示 \(u\)\(k\) 级儿子集合。那么 \(h[i+j][u]\) 分为两部分,\(h[i][u]\) 以及 \(h[j][v]\space (v\in p(u,i))\).
我们递归一次树,对于 \(v\),就向其 \(i\) 级祖先做贡献。合并是 \(O(n)\) 的。

我们可以先处理出所有的 \(h[2^i]\),然后倍增判断即可。

code
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const int N=1e5+10;
int n,mx,ans,len,now;
const ull bas=7988993;
ull hs[20][N],f[N],nf[N],pv[N];
vector<int> e[N];
int dep[N],p[N],st[N],mxd[N],vis[N];
void dfs(int x,ull *a,ull *b,ull *c) {
	if(x==1) p[0]=st[0]=0,dep[1]=1;
	st[dep[x]]=x;
	mxd[x]=dep[x];
	a[x]=b[x];
	if(dep[x]>len) 
		a[st[dep[x]-len]]=a[st[dep[x]-len]]*bas+c[x];
	for(int y:e[x]) {
		dep[y]=dep[x]+1;
		dfs(y,a,b,c);
		mxd[x]=max(mxd[x],mxd[y]);
	}
	st[0]--;
}
bool cmp(int a,int b) {
	return pv[a]<pv[b];
}
void merge(ull *a,ull *b,ull *c,int k) {
	len=k;
	dfs(1,a,b,c);
	for(int i=1; i<=n; i++) p[i]=i,pv[i]=a[i];
	sort(p+1,p+n+1,cmp);
	for(int i=1,j=0; i<=n; i++) {
		if(pv[p[i]]>pv[p[i-1]]) j++;
		a[p[i]]=j;
	}
}
int check(ull *a,ull *b,ull *c,int k) {
	merge(a,b,c,ans);
	now++;
	for(int i=1; i<=n; i++) {
		if(mxd[i]-dep[i]>=k) {
			if(vis[a[i]]==now) return 1;
			vis[a[i]]=now;
		}
	}
	return 0;
}
int main() {
	freopen("ernd.in","r",stdin);
	freopen("ernd.out","w",stdout);
	scanf("%d",&n);
	for(int i=1; i<=n; i++) {
		int s,x;
		scanf("%d",&s),hs[0][i]=s;
		for(int j=1; j<=s; j++) {
			scanf("%d",&x); e[i].push_back(x);
		}
	}
	for(int i=1; i<=16; i++)
		merge(hs[i],hs[i-1],hs[i-1],1<<(i-1));
	for(int i=16; i>=0; i--) {
		if(check(nf,f,hs[i],ans+(1<<i))) {
			ans+=(1<<i);
			memcpy(f,nf,sizeof(nf));
		}
	}
	printf("%d\n",ans);
	return 0;
}

D

定义一个数列的权值为它本质不同的子序列个数,
给出一个长度为 \(n(n\le 30000)\) 的数列 \(A\) 和一个整数 \(K(K\le 5)\),保证 \(Ai ∈ [0, K)\)\(m(m\le 30000)\) 次操作:

  1. 给出 \(l, r, x\),对所有的 \(i ∈ [l, r]\),将 \(A_i\) 变成 \((A_i + x) \bmod K\)
  2. 给出 \(l, r, x\),对所有的 \(i ∈ [l, r]\),将 \(A_i\) 变成 \(A_i \times x \bmod K\)
  3. 给出 \(l, r\),询问区间 \([l, r]\) 的权值对 \(998244353\) 取模后的值。

本质不同的子序列个数,我们考虑用最小表示法,即我们把一个子序列第一次出现的位置做贡献。
\(f_{i,j}\) 表示 \(i\) 结尾的,结尾是 \(j\) 的子序列个数。
那么 \(f_{i,j}\) 可以被 \(f_{pre[x],x}\) 做贡献,其中 \(pre[x]\)\(x\) 上一次出现的位置。
这种子序列的题一般是动态 dp,状态是一行所有的 \(f_{pre[x],x}\),我们直接把转移写成矩阵,显然是单位矩阵再把第 \(a_i\) 行设为 \(1\)
直接把矩阵上线段树即可。

在给区间加上一个数时,区间中任意两个数的相等关系并不会变,因此只需要给矩阵的行列置换一下就好了,内容本质上并没有变化。

在给区间乘上一个数 \(x\) 时,如果 \(\gcd(x,K)=1\),相等关系同样不会变化,处理方法和区间加一样。对于其他情况,区间中的数以模 \(K/\gcd(K,x)\) 的余数划分成了 \(K/\gcd(K,x)\) 个等价类,这样就不能直接用原有的值更新了。处理方法是我们对 \(K\) 的所有约数 \(d\),都用一棵线段树维护模 \(d\) 时的答案,在区间乘法时相当于用模 \(K/\gcd(K,x)\) 的信息去更新模 \(d\) 的信息。

code
#include<bits/stdc++.h>
#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
typedef long long LL;
inline int read(){
	char ch=getchar();
	while(!isdigit(ch) && ch!='-') ch=getchar();
	int x=0,ff=1; if(ch=='-') ff=-1,ch=getchar();
	while(isdigit(ch)) x=(x<<3) + (x<<1) + (ch^48),ch=getchar();
	return x*ff;
}
const int N=3e4+5,P=998244353;
int n,K,Q,a[N],l[4],m;
inline int gcd(int x,int y){
	if(!y) return x;
	else return gcd(y,x%y);
}
struct matrix{
	int mx[6][6],c;
	void set(int nc,int op){
		c=nc;
		for(int i=0;i<=c;i++)
			for(int j=0;j<=c;j++)
				mx[i][j]=0;
		if(op){
			for(int i=0;i<=c;i++)
				mx[i][i]=1;
		}
	}
	matrix operator * (const matrix A) const {
		matrix B; B.set(A.c,0);
		for(int i=0;i<=c;i++)
			for(int j=0;j<=c;j++)
				for(int k=0;k<=c;k++)
					(B.mx[i][k]+=1ll*mx[i][j]*A.mx[j][k]%P)%=P;
		return B;
	}
} bf;
struct node{
	int adt,mlt;
	matrix b[3];
	void addt(int x,int op){
		if(!op){
			(adt+=x)%=K;
			for(int k=1;k<=m;k++){
				bf.c=l[k];
				for(int i=0;i<=l[k];i++)
					for(int j=0;j<=l[k];j++){
						int vi=(i==l[k])?l[k]:(i+x)%l[k],vj=(j==l[k])?l[k]:(j+x)%l[k];
						bf.mx[vi][vj]=b[k].mx[i][j];
					}
				b[k]=bf;
			}
		}
		else {
			(adt*=x)%=K; (mlt*=x)%=K;
			for(int k=m;k>=1;k--){
				int tx=gcd(l[k],x);
				if(tx==1) {
					bf.c=l[k];
					for(int i=0;i<=l[k];i++){
						int vi=(i==l[k])?l[k]:(i*x)%l[k];
						for(int j=0;j<=l[k];j++){
							int vj=(j==l[k])?l[k]:(j*x)%l[k];
							bf.mx[vi][vj]=b[k].mx[i][j];
						}
					}
					b[k]=bf;
				}
				else {
					int ty=0; if(k==2 && l[k]/tx==l[1]) ty=1;
					bf.set(l[k],1);
					for(int i=0;i<=l[ty];i++){
						int vi=i*tx; 
						bf.mx[vi][vi]=0;
						for(int j=0;j<=l[k];j++) 
							if(j%tx>0 &&i<l[ty]) bf.mx[vi][j]=b[ty].mx[i][l[ty]];
							else bf.mx[vi][j]=b[ty].mx[i][j/tx];
					}
					b[k]=bf;
				}
			}
		}
	}
} tr[N<<2];
void pushdown(int p){
	if(tr[p].mlt!=1){
		tr[p<<1].addt(tr[p].mlt,1);
		tr[p<<1|1].addt(tr[p].mlt,1);
		tr[p].mlt=1;
	}
	if(tr[p].adt){
		tr[p<<1].addt(tr[p].adt,0);
		tr[p<<1|1].addt(tr[p].adt,0);
		tr[p].adt=0;
	}
}
void pushup(int p){
	for(int i=0;i<=m;i++)
		tr[p].b[i]=tr[p<<1].b[i]*tr[p<<1|1].b[i];
}
void build(int p,int L,int R){
	tr[p].adt=0; tr[p].mlt=1;
	if(L==R){
		for(int k=0;k<=m;k++){
			tr[p].b[k].set(l[k],1);
			for(int j=0;j<=l[k];j++) tr[p].b[k].mx[a[L]%l[k]][j]=1;
		}
		return ;
	}
	int mid=(L+R)/2;
	build(p<<1,L,mid); build(p<<1|1,mid+1,R);
	pushup(p);
}
void Mdf(int p,int L,int R,int lt,int rt,int x,int op){
	if(L>=lt && R<=rt) { tr[p].addt(x,op);  return ;}
	int mid=(L+R)/2; pushdown(p);
	if(lt<=mid) Mdf(p<<1,L,mid,lt,rt,x,op);
	if(rt>mid) Mdf(p<<1|1,mid+1,R,lt,rt,x,op);
	pushup(p);
}
matrix Qry(int p,int L,int R,int lt,int rt){
	if(L>=lt && R<=rt) return tr[p].b[m];
	int mid=(L+R)/2; pushdown(p);
	if(lt<=mid && rt>mid) return Qry(p<<1,L,mid,lt,rt)*Qry(p<<1|1,mid+1,R,lt,rt);
	else if(lt<=mid) return Qry(p<<1,L,mid,lt,rt);
	else return Qry(p<<1|1,mid+1,R,lt,rt);
}
void vmain(){
	n=read(); K=read(); Q=read();
	l[m=0]=1; if(K==4) l[++m]=2;
	l[++m]=K;
	for(int i=1;i<=n;i++) a[i]=read();
	build(1,1,n);
	while(Q--){
		int op=read(),l=read(),r=read(),x;
		if(op==3){
			matrix res=Qry(1,1,n,l,r); int ans=0;
			for(int i=0;i<K;i++) (ans+=res.mx[i][K])%=P;
			printf("%d\n",ans);
		}
		else {
			x=read(); --op;
			Mdf(1,1,n,l,r,x,op);
		}
	}
}
int main(){
	freopen("data.in","r",stdin);
	freopen("data.out","w",stdout);
	int T=read(); while(T--) vmain();
	return 0;
}