【考试总结】2022-07-11


客蒜计题

本题计算的是三角形个数而不是直角三角形个数

强制 \(a\le b\le c\) 并枚举 \(c\) 再把算式 \(\Theta(1)\) 处理即可

Code Display
inline int S(int l,int r){
	return (r-l+1)%mod*((l+r)%mod)%mod*inv2%mod;
}
signed main(){
	freopen("cilrag.in","r",stdin);
	freopen("cilrag.out","w",stdout);
	int n=read();
	int ans=S((n+2)/3+1,(n-1)/2+1);
	int L=n-(n-1)/2+1,R=n-(n+2)/3+1;
	if(L&1) ans-=L/2%mod,++L;
	if(L<=R&&R%2==0) ans-=R/2%mod,--R;
	if(L/2<=R/2) ans-=2*S(L/2,R/2)%mod;
	cout<<(ans%mod+mod)%mod<

牛奶题

将所有区间中的元素扔到 \(\rm Trie\) 树上做 \(\rm DP\),实现的时候写的是记忆化搜索,那么遍历到的状态必然有用

\(f_x\) 表示 \(x\) 子树中选 \(3\) 个合法的点的方案数。转移分成三个点在两个子树中的分布以及 \(K\) 的二进制表示在这位下的 \(0/1\) 来递归。三个在相同子树且 \(K\) 当前位是 \(1\) 的情况可以使用 \(\binom{siz}3\) 来快速计算。

此时需要另设 \(g_{x,y}\) 表示两个点在 \(x\) 子树中,一个点在 \(y\) 子树中的选择方案数来满足计算 两左一右/两右一左 分布方案的需求。这部分根据点分布的位置继续讨论,其中同子树的两个点一左一右的情况仍然需要特别关照

\(y\) 子树里面的点选到左子树时 \(x\) 中右子树的点和其形成的限制严格一些,那么可以设 \(h_{x,y}\) 表示在 \(x,y\) 子树中各选一个点的方案数,在附上对应的系数 \(siz_{ls/rs}\) 即可

\(h\) 的转移和也是对位置讨论,这次全是平凡 case 了。

Code Display
const int N=1e6+10;
int n,K,siz[N],dep[N],ls[N],rs[N],tot;
unordered_map dp2[N],dp3[N],dp1;
inline int C2(int x){return x*(x-1)/2%mod;}
inline int C3(int x){return x*(x-1)%mod*(x-2)%mod*inv6%mod;}
inline void ins(int &p,int d,int l,int r,int st,int ed){
	if(st<=l&&r<=ed) return p=d+1,void();
	if(!p) dep[p=++tot]=d;
	int mid=(l+r)>>1;
	if(st<=mid) ins(ls[p],d-1,l,mid,st,ed);
	if(ed>mid) ins(rs[p],d-1,mid+1,r,st,ed);
	siz[p]=siz[ls[p]]+siz[rs[p]];
	return ;
}
inline int dfs3(int x,int y){
	if(dp3[x].count(y)) return dp3[x][y];
	if(!x||!y) return 0;
	if(x==1) return 1;
	int res=0;
	if(K>>(dep[x]-1)&1){
		res+=dfs3(ls[x],rs[y]);
		res+=dfs3(rs[x],ls[y]);
		res+=siz[ls[x]]*siz[ls[y]]+siz[rs[x]]*siz[rs[y]];
	}else{
		res+=dfs3(ls[x],ls[y]);
		res+=dfs3(rs[x],rs[y]);
	}
	return dp3[x][y]=res%mod;
}
inline int dfs2(int x,int y){
	if(dp2[x].count(y)) return dp2[x][y];
	if(x<=1||!y) return 0;
	int res=0;
	if(K>>(dep[x]-1)&1){
		res+=C2(siz[ls[x]])*siz[ls[y]]+C2(siz[rs[x]])*siz[rs[y]];
		res+=dfs2(ls[x],rs[y])+dfs2(rs[x],ls[y]);
		res+=siz[ls[x]]*dfs3(rs[x],ls[y]);
		res+=siz[rs[x]]*dfs3(ls[x],rs[y]);
	}else{
		res=dfs2(ls[x],ls[y])+dfs2(rs[x],rs[y]);
	}
	return dp2[x][y]=res%mod;
}
inline int dfs1(int x){
	if(dp1.count(x)) return dp1[x];
	if(x<=1) return 0;
	int res=0;
	if(K>>(dep[x]-1)&1){
		res=C3(siz[ls[x]])+C3(siz[rs[x]]);
		res+=dfs2(ls[x],rs[x]);
		res+=dfs2(rs[x],ls[x]);
	}else{
		res=dfs1(ls[x])+dfs1(rs[x]);
	}
	return dp1[x]=res%mod;
}
signed main(){
	freopen("milk.in","r",stdin);
	freopen("milk.out","w",stdout);
	n=read(); K=read();
	siz[1]=1;
	for(int i=2;i<=31;++i){
		siz[i]=siz[i-1]*2;
		ls[i]=rs[i]=dep[i]=i-1;
	}
	tot=31;
	int rt=0;
	while(n--){
		int l=read(),r=read();
		ins(rt,30,0,(1<<30)-1,l,r);
	}
	print(dfs1(rt));
	return 0;
}


周欣题

先求出来每次操作被 pop 光的时间,剩下可以差分处理

将序列分块,求每个操作在其覆盖的整块以及至多两个散块的被弹出时间的最大值。为了将空间复杂度降至 \(\Theta(n)\) 那么将操作离线,对每个块处理和其相关的操作

将 整块 散块 分别处理。

注意到较靠后的修改操作不会比靠前的操作早删空,所以可以在时间轴上双指针。

对于整块操作而言,维护 “至多再加入多少个元素就会被删空” 的数值 \(\rm Mx\),初始化 \(\max\{a_i\}\),为处理整体操作需要维护一个 \(tag\) ,每次整体操作将标记加一。对于不完整覆盖一个块的操作,将 \(tag\) 下方后重新计算 \(\rm Mx\) 即可。这样的操作不超过 \(2m\) 个,这部分暴力复杂度是 \(\Theta(m\sqrt n)\)

对于不完整覆盖的操作的答案计算,做双指针的变成了每个位置。此时不能扫描完整时间轴,只遍历那些散块操作。对于整块覆盖操作影响的处理可以在时间轴上做一个前缀和,同时被删掉的位置不一定是散块操作,也可能是整块操作。

总复杂度 \(\Theta((n+m)\sqrt n)\)

Code Display
const int N=2e5+10;
int n,m,lim[N],ql[N],qr[N],qx[N],f[N],ans[N];
vector > vec[N];
int siz[N],cov[N],sum[N],id[N];
inline bool in(int id,int x){return ql[id]<=x&&x<=qr[id];}
inline bool countain(int id,int L,int R){return ql[id]<=L&&R<=qr[id];}
int main(){
	freopen("zx.in","r",stdin);
	freopen("zx.out","w",stdout);
	n=read(); m=read();
	for(int i=1;i<=n;++i) lim[i]=read();
	for(int i=1;i<=m;++i) ql[i]=read(),qr[i]=read(),qx[i]=read();
	int block=sqrt(n);
	for(int L=1,R;L<=n;L=R+1){
		R=min(n,L+block-1);
		int icnt=0,ccnt=0;
		int indl=1,indr=0;
		int Mx=0,tag=0;
		for(int i=L;i<=R;++i) ckmax(Mx,siz[i]=lim[i]);
		while(indl<=m&&indr<=m){
			while(indr=0){
				++indr;
				sum[indr]=sum[indr-1];
				if(countain(indr,L,R)){
					tag++;
					cov[++ccnt]=indr;
					sum[indr]++;
					continue;
				}
				if(ql[indr]>R||qr[indr]R||qr[indl]=0){
					++indr;
					if(in(id[indr],i)) --cur;
				}
				if(in(id[indl],i)){
					if(cur-(sum[id[indr]]-sum[id[indl]])>=0){
						f[id[indl]]=m+1;
					}else{
						if(in(id[indr],i)&&cur-(sum[id[indr]]-sum[id[indl]])==-1){
							ckmax(f[id[indl]],id[indr]);
						}else{
							int delt=in(id[indr],i);
							int pos=sum[id[indl]]+cur+1+delt;
							if(pos<=ccnt) ckmax(f[id[indl]],cov[pos]);
							else f[id[indl]]=m+1;
						}
					}
				}
				if(in(id[indl],i)) ++cur;
				++indl;
			}
		}
	}
	for(int i=1;i<=m;++i) vec[qx[i]].emplace_back(i,f[i]);
	for(int i=1;i<=2e5;++i){
		sort(vec[i].begin(),vec[i].end());
		int indic=0;
		for(auto t:vec[i]){
			if(indic>=t.sec) continue;
			ans[max(indic,t.fir)]++;
			ans[indic=t.sec]--;
		}
	}
	for(int i=1;i<=m;++i) ans[i]+=ans[i-1];
	for(int i=1;i<=m;++i) print(ans[i]);
	return 0;
}