20230905 比赛总结

反思

B

没有想好就开始写,于是重构了

且犯了数组越界的 sb 错误

C

过度沉迷于写正解,而不写暴力

D

d p dp dp 套路见识得太少了!

题解

比赛链接

A

傻子题

B

我是傻子

C

看到子树内距离其不超过 k k k 的点,有一个套路是主席树 +    d f s +\;dfs +dfs 序,即在主席树的第 d e p t h [ x ] + k depth[x]+k depth[x]+k 层查询区间 [ L [ x ] , R [ x ] ] [L[x],R[x]] [L[x],R[x]]

看到数同构,考虑数哈希,当然,用到上面的技巧的话,可以把树哈希变成序列哈希,然后就会简单且精确很多

考虑第一个 t r i c k trick trick 放到线段树上维护哈希,然后就可以二分做了

时间复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N=100100;
typedef unsigned long long ull;
vector<int> T[N];
int n,s[N];
int depth[N],mxd[N],dfnL[N],dfnR[N],dfs_clock;
vector<int> d[N];
struct Node{
	int lc,rc,sum;
	ull h;
}seg[N*4+N*20];
int idx,root[N];
ull pw[N],P=131;
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
void dfs(int u,int fa){
	depth[u]=depth[fa]+1,mxd[u]=depth[u];
	dfnL[u]=++dfs_clock;
	d[depth[u]].push_back(u);
	for(int v:T[u]) if(v!=fa) dfs(v,u),mxd[u]=max(mxd[u],mxd[v]);
	dfnR[u]=dfs_clock;
}
int build(int l,int r){
	int p=++idx;
	if(l==r) return p;
	int mid=(l+r)>>1;
	seg[p].lc=build(l,mid),seg[p].rc=build(mid+1,r);
	return p;
}
void merge(Node &rt,Node lc,Node rc){ rt.sum=lc.sum+rc.sum,rt.h=rc.h+lc.h*pw[rc.sum];}
int insert(int p,int l,int r,int pos,int val){
	int q=++idx;seg[q]=seg[p];
	if(l==r){ seg[q].h=val,seg[q].sum=1;return q;}
	int mid=(l+r)>>1;
	if(mid>=pos) seg[q].lc=insert(seg[p].lc,l,mid,pos,val);
	else seg[q].rc=insert(seg[q].rc,mid+1,r,pos,val);
	merge(seg[q],seg[seg[q].lc],seg[seg[q].rc]);
    // cout<<l<<' '<<r<<" : "<<seg[q].h<<'\n';
    return q;
}
Node query(int p,int l,int r,int L,int R){
	if(L<=l&&r<=R) return seg[p];
	int mid=(l+r)>>1;
	if(mid>=L&&mid<R){
		Node lf=query(seg[p].lc,l,mid,L,R);
		Node ri=query(seg[p].rc,mid+1,r,L,R);
		merge(lf,lf,ri);
		return lf;
	}
	if(mid>=L) return query(seg[p].lc,l,mid,L,R);
	return query(seg[p].rc,mid+1,r,L,R);
}
bool check(int mid){
	set<ull> se;
	int cnt=0;
	for(int i=1;i<=n;i++) if(depth[i]+mid<=mxd[i])
		cnt++,se.insert(query(root[depth[i]+mid-1],1,n,dfnL[i],dfnR[i]).h);
	return cnt!=se.size();
}
int main(){
    freopen("ernd.in","r",stdin);
    freopen("ernd.out","w",stdout);
	n=read();
    pw[0]=1;
    for(int i=1;i<=n;i++) pw[i]=pw[i-1]*P;
	for(int i=1;i<=n;i++){
		s[i]=read();
		for(int j=1;j<=s[i];j++) T[i].push_back(read());
	}
	dfs(1,0);
	for(int i=1;i<=n;i++){
		root[i]=root[i-1];
		for(int j:d[i]) root[i]=insert(root[i],1,n,dfnL[j],s[j]);
	}
	int l=0,r=n;
	while(l<r-1){
		int mid=(l+r)>>1;
		check(mid)?l=mid:r=mid;
	}
	printf("%d",l);
	return 0;
}

D

nb 题!

我一开始以为求本质不同的子序列个数很不可做,后来才发现是我傻逼了

考虑 d p i dp_i dpi 表示到 i i i 的本质不同的子序列个数

考虑如果 a i a_i ai 之前未出现过,那么显然所有前面的子序列都可以在后面接上 a i a_i ai,所以 d p i = 2 d p i − 1 + 1 dp_i=2dp_{i-1}+1 dpi=2dpi−1+1

如果 a i a_i ai 之前出现过,考虑最后一个出现的位置 j j j,那么在 j − 1 j-1 j−1 前面的子序列后面添上 a i a_i ai 就重复了,所以 d p i = 2 d p i − 1 − d p j − 1 dp_i=2dp_{i-1}-dp_{j-1} dpi=2dpi−1−dpj−1

这样可以 O ( n 2 ) O(n^2) O(n2) 做

考虑 k k k 的范围极小,所以整个 d p dp dp 的过程可以用矩乘优化

区间查询用线段树维护矩乘即可

考虑区间加操作,那么 k k k 个数一定会形成一个置换,这个可以通过改变转移矩阵的方式进行修改

考虑区间乘操作

当 k ≠ 4 k\neq4 k=4 时, k k k 个数一定会形成一个置换,那么和区间加的方法一样改矩阵

这里要注意乘 0 0 0 的情况需要特判,预处理出长度为 i i i 的区间全是 0 0 0 的转移矩阵即可

当 k = 4 k=4 k=4 且 ∗ 2 *2 ∗2 时,就比较恶心,因为 0 , 2 0,2 0,2 会变成 0 0 0, 1 , 3 1,3 1,3 会变成 2 2 2,所以考虑再维护两个矩阵分别表示区间中 0 , 2 0,2 0,2 变成 0 0 0, 1 , 3 1,3 1,3 变成 2 2 2 的矩阵 和 区间中 0 , 2 0,2 0,2 变成 2 2 2, 1 , 3 1,3 1,3 变成 0 0 0 的矩阵

为什么要维护后面的矩阵,因为当 + 2 +2 +2 时,需要交换两个矩阵,这个可以自己体会

时间复杂度 O ( q k 3 l o g n ) O(qk^3logn) O(qk3logn)

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int P=998244353;
const int N=30100;
struct Matrix{ int n,m,a[6][6];};
Matrix operator *(const Matrix &A,const Matrix &B){
	Matrix C;C.n=A.n,C.m=B.m;
	memset(C.a,0,sizeof(C.a));
	for(int i=0;i<C.n;i++) for(int j=0;j<C.m;j++)
		for(int k=0;k<A.m;k++) C.a[i][j]=(C.a[i][j]+1ll*A.a[i][k]*B.a[k][j])%P;
	return C;
}
Matrix mul0[N],mul2[N];
int n,K,m,v[N];
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
Matrix ID(){
	Matrix ret;ret.n=K+1,ret.m=K+1;
	memset(ret.a,0,sizeof(ret.a));
	for(int i=0;i<=K;i++) for(int j=0;j<=K;j++) ret.a[i][j]=1;
	return ret;
}
Matrix ONLYMAT(int col){
	Matrix ret;ret.n=K+1,ret.m=K+1;
	memset(ret.a,0,sizeof(ret.a));
	for(int i=0;i<K;i++)
		if(i!=col) ret.a[i][i]=1;
		else ret.a[K][i]=1;
	ret.a[K][K]=2,ret.a[col][K]=P-1;
	return ret;
}
struct SegmentTree{
	Matrix sum[N<<2],g[N<<2][2];
	//g[][0]:0,2全部变成0 且 1,3全部变成2 之后的矩阵
	//g[][1]:0,2全部变成2 且 1,3全部变成0 之后的矩阵
	int tagadd[N<<2],tagmul[N<<2],len[N<<2];
    void build(int l,int r,int x){
        tagadd[x]=0,tagmul[x]=1,len[x]=r-l+1;
        if(l==r){
			sum[x]=ONLYMAT(v[l]);
			if(K==4){
				if(!v[l]||v[l]==2) g[x][0]=ONLYMAT(0),g[x][1]=ONLYMAT(2);
				else g[x][0]=ONLYMAT(2),g[x][1]=ONLYMAT(0);
			}
			return;
		}
        int mid=(l+r)>>1;
        build(l,mid,x<<1),build(mid+1,r,x<<1^1);
        sum[x]=sum[x<<1]*sum[x<<1^1];
		if(K==4) g[x][0]=g[x<<1][0]*g[x<<1^1][0],g[x][1]=g[x<<1][1]*g[x<<1^1][1];
    }
	void downadd(int x,int val){
		if(!val) return;
		tagadd[x]+=val;
		if(tagadd[x]>=K) tagadd[x]-=K;
		if(K==4&&val!=2) swap(g[x][0],g[x][1]);
		int mat[6][6];
		for(int i=0;i<=K;i++) for(int j=0;j<=K;j++) mat[i][j]=sum[x].a[i][j];
		for(int i=0;i<K;i++){
			int ti=(i+val)%K;
			for(int j=0;j<K;j++){
				int tj=(j+val)%K;
				sum[x].a[ti][tj]=mat[i][j];
			}
			sum[x].a[ti][K]=mat[i][K],sum[x].a[K][ti]=mat[K][i];
		}
	}
	void downmul(int x,int val){
		tagmul[x]=tagmul[x]*val%K,tagadd[x]=tagadd[x]*val%K;
		if(K==4&&val==2){//不形成一个置换
			sum[x]=g[x][0],g[x][0]=mul0[len[x]],g[x][1]=mul2[len[x]];
			return;
		}
        if(!val){
			sum[x]=mul0[len[x]];
			if(K==4) g[x][0]=mul0[len[x]],g[x][1]=mul2[len[x]];
			return;
		}
		int mat[6][6];
		for(int i=0;i<=K;i++) for(int j=0;j<=K;j++) mat[i][j]=sum[x].a[i][j];
		for(int i=0;i<K;i++){
			int ti=i*val%K;
			for(int j=0;j<K;j++){
				int tj=j*val%K;
				sum[x].a[ti][tj]=mat[i][j];
			}
			sum[x].a[ti][K]=mat[i][K],sum[x].a[K][ti]=mat[K][i];
		}
	}
	void pushdown(int x){
		downmul(x<<1,tagmul[x]),downmul(x<<1^1,tagmul[x]);
		downadd(x<<1,tagadd[x]),downadd(x<<1^1,tagadd[x]);
		tagadd[x]=0,tagmul[x]=1;
	}
	void modify(int l,int r,int x,int L,int R,int val,int type){
		if(L<=l&&r<=R){
			if(!type) downadd(x,val);
			else downmul(x,val);
			return;
		}
		pushdown(x);
		int mid=(l+r)>>1;
		if(mid>=L) modify(l,mid,x<<1,L,R,val,type);
		if(mid<R) modify(mid+1,r,x<<1^1,L,R,val,type);
        sum[x]=sum[x<<1]*sum[x<<1^1];
		if(K==4) g[x][0]=g[x<<1][0]*g[x<<1^1][0],g[x][1]=g[x<<1][1]*g[x<<1^1][1];
	}
	Matrix query(int l,int r,int x,int L,int R){
		if(L<=l&&r<=R) return sum[x];
		pushdown(x);
		int mid=(l+r)>>1;
		if(mid>=L&&mid<R) return query(l,mid,x<<1,L,R)*query(mid+1,r,x<<1^1,L,R);
		if(mid>=L) return query(l,mid,x<<1,L,R);
		return query(mid+1,r,x<<1^1,L,R);
	}
}sg;
void work(){
	n=read(),K=read(),m=read();
	for(int i=1;i<=n;i++) v[i]=read();
	mul0[0]=ID(),mul0[1]=ONLYMAT(0);
    for(int i=2;i<=n;i++) mul0[i]=mul0[1]*mul0[i-1];
	mul2[0]=ID(),mul2[1]=ONLYMAT(2);
	for(int i=2;i<=n;i++) mul2[i]=mul2[1]*mul2[i-1];
    sg.build(1,n,1);
	for(int i=1;i<=m;i++){
		int op=read(),l=read(),r=read();
		if(op==1) sg.modify(1,n,1,l,r,read(),0);
		else if(op==2) sg.modify(1,n,1,l,r,read(),1);
		else{
			Matrix A;A.n=1,A.m=K+1;A.a[0][K]=0;
			for(int j=0;j<K;j++) A.a[0][j]=P-1;
			A=A*sg.query(1,n,1,l,r);
			printf("%d\n",A.a[0][K]);
		}
	}
}
int main(){
    freopen("data.in","r",stdin);
    freopen("data.out","w",stdout);
	int T=read();
	while(T--) work();
	return 0;
}
相关推荐
passer__jw7671 小时前
【LeetCode】【算法】3. 无重复字符的最长子串
算法·leetcode
passer__jw7671 小时前
【LeetCode】【算法】21. 合并两个有序链表
算法·leetcode·链表
sweetheart7-71 小时前
LeetCode22. 括号生成(2024冬季每日一题 2)
算法·深度优先·力扣·dfs·左右括号匹配
景鹤4 小时前
【算法】递归+回溯+剪枝:78.子集
算法·机器学习·剪枝
_OLi_4 小时前
力扣 LeetCode 704. 二分查找(Day1:数组)
算法·leetcode·职场和发展
丶Darling.4 小时前
Day40 | 动态规划 :完全背包应用 组合总和IV(类比爬楼梯)
c++·算法·动态规划·记忆化搜索·回溯
风影小子5 小时前
IO作业5
算法
奶味少女酱~5 小时前
常用的c++特性-->day02
开发语言·c++·算法
passer__jw7675 小时前
【LeetCode】【算法】11. 盛最多水的容器
算法·leetcode
诸葛悠闲5 小时前
C++ 面试问题集合
算法