【考试总结】2022-06-14

区间第 k 小

注意区间第 k 小问题中,相同的元素出现多次时要计算多次

使用可持久化主席树套主席树解决这个问题,外层主席树维护扫描线,查询取出来 r 对应的主席树第 l 片叶子根链上的所有根跑线段树二分

考察区间中出现某种数字个数不超过 w 这条限制,在扫描线的过程中每个叶子维护一个权值线段树,那么得到这个元素之前第 w 次出现位置前 w+1 到前 w 这段区间的线段树单点 w,而后面这些单点加 1

区间修改就是在 log 个子区间的根对应的权值线段树中进行单点权值修改

Code Display
const int N=1e5+10;
int n,w,Q,typ,a[N];
vector<int> app[N],nds;
const int M=5e7;
struct peresistant_SEG{
	int tot,ls[M],rs[M],sum[M];
	inline void ins(int &p,int l,int r,int pos,int val){
		++tot;
		ls[tot]=ls[p]; rs[tot]=rs[p]; sum[tot]=sum[p]+val;
		p=tot;
		if(l==r) return ;
		int mid=(l+r)>>1;
		if(pos<=mid) ins(ls[p],l,mid,pos,val);
		else ins(rs[p],mid+1,r,pos,val);
	}
	inline int query(int l,int r,int K){
		if(l==r) return l;
		int mid=(l+r)>>1,cnt=0;
		for(auto t:nds) cnt+=sum[ls[t]];
		if(K<=cnt){
			for(auto &t:nds) t=ls[t];
			return query(l,mid,K);
		}else{
			for(auto &t:nds) t=rs[t];
			return query(mid+1,r,K-cnt);
		}
	}
}T;
const int SIZ=N*80;
int tot,root[SIZ],ls[SIZ],rs[SIZ];
inline void ins(int &p,int l,int r,int st,int ed,int pos,int val){
	++tot; 
	ls[tot]=ls[p]; rs[tot]=rs[p]; root[tot]=root[p];
	p=tot;
	if(st<=l&&r<=ed) return T.ins(root[p],0,n,pos,val);
	int mid=(l+r)>>1;
	if(st<=mid) ins(ls[p],l,mid,st,ed,pos,val);
	if(ed>mid) ins(rs[p],mid+1,r,st,ed,pos,val);
}
int rt[N],cnt[N];
int main(){
	freopen("kth.in","r",stdin); freopen("kth.out","w",stdout);
	n=read(); w=read(); Q=read(); typ=read();
	for(int i=1;i<=n;++i) app[a[i]=read()].emplace_back(i);
	for(int i=1;i<=n;++i){
		rt[i]=rt[i-1];
		int cur=++cnt[a[i]];
		int las=cur>w?app[a[i]][cur-w-1]:0;	
		ins(rt[i],1,n,las+1,i,a[i],1);
		if(las){
			int pre=0;
			if(cur>w+1) pre=app[a[i]][cur-w-2];
			ins(rt[i],1,n,pre+1,las,a[i],-w);
		}
	}
	int ans=0;
	while(Q--){
		int l=read(),r=read(),K=read();
		l^=typ*ans;
		r^=typ*ans;
		K^=typ*ans;
		nds.clear();
		function<void(int,int,int,int)>get_rt=[&](int p,int l,int r,int pos){
			if(!p) return ; nds.emplace_back(root[p]);
			if(l==r) return ;
			int mid=(l+r)>>1;
			if(pos<=mid) get_rt(ls[p],l,mid,pos);
			else get_rt(rs[p],mid+1,r,pos);
			return ;
		};
		get_rt(rt[r],1,n,l);
		printf("%d\n",ans=T.query(0,n,K));
	}
	return 0;
}

求和

平凡和式变换可以得到原式为 i=1nd=1Kfd(i)(1+2i=1niφ(i))

Fd(n)=i=1nfd(i),设 λ(i)=f+(i) 即每个质因子出现次数不做限制,使用容斥原理继续处理

枚举哪些质因子出现次数超过了限制,而根据 μI=ϵ 的等式可以得到容斥系数就是 μ(i)

Fd(n)=i=1nμ(i)j=1nid+1λ(id+1j)

显然 λ 是完全积性函数,所以现在要做 λ 的前缀和,发现 d|xλ(d)=[x 是完全平方数] ,做杜教筛即可

时间复杂度 Θ(Kn+n23)

Code Display
const int mod=1<<30;
const int N=1e7+10;
int pri[N],pcnt;
int phi[N],sumphi[N];
int lam[N],sumlam[N],mu[N];
bool isc[N];
int n,K;
vector<int> pw[50];
inline int Phi_sum(int n){
	static unordered_map<int,int>mp;
	if(n<=1e7) return sumphi[n];
	if(mp.count(n)) return mp[n];
	int res=0;
	if(n&1) res=(n+1)/2%mod*(n%mod)%mod;
	else res=n/2%mod*((n+1)%mod)%mod;
	for(int l=2,r;l<=n;l=r+1){
		r=n/(n/l);
		res-=(r-l+1)%mod*Phi_sum(n/l)%mod;
	}
	res=(res%mod+mod)%mod;
	return mp[n]=res;
}
inline int Lam_sum(int n){
	static unordered_map<int,int> mp;
	if(n<=1e7) return sumlam[n];
	if(mp.count(n)) return mp[n];
	int res=sqrt(n);
	for(int l=2,r;l<=n;l=r+1){
		r=n/(n/l);
		res-=(r-l+1)%mod*Lam_sum(n/l)%mod;
	}
	res=(res%mod+mod)%mod;
	return mp[n]=res;
}
inline int Sum(int n,int d){
	int sum=0;
	for(int i=1;i<pw[d].size()&&pw[d][i]<=n;++i){
		int coef=mu[i];
		if(!(d&1)) coef*=lam[i];
		sum+=Lam_sum(n/pw[d][i])*coef;
	}
	sum%=mod;
	return sum;
}
signed main(){
	freopen("sum.in","r",stdin); freopen("sum.out","w",stdout);
	n=1e7; 
	phi[1]=sumphi[1]=1;
	lam[1]=sumlam[1]=1;
	mu[1]=1;
	for(int i=2;i<=n;++i){
		if(!isc[i]){
			pri[++pcnt]=i;
			phi[i]=i-1;
			lam[i]=mu[i]=-1;
		}
		for(int j=1;j<=pcnt&&i*pri[j]<=n;++j){
			isc[i*pri[j]]=1;
			lam[i*pri[j]]=-lam[i];
			if(i%pri[j]==0){
				phi[i*pri[j]]=phi[i]*pri[j];
				mu[i*pri[j]]=0;
				break;
			}
			mu[i*pri[j]]=-mu[i];
			phi[i*pri[j]]=phi[i]*phi[pri[j]];
		}
		sumphi[i]=(sumphi[i-1]+phi[i])&(mod-1);
		sumlam[i]=sumlam[i-1]+lam[i];
		sumlam[i]=(sumlam[i]%mod+mod)%mod;
	}
	scanf("%lld%lld",&n,&K);
	for(int i=1;i<=K;++i) pw[i].emplace_back(1);
	for(int i=1;i<=n;++i){
		int val=i;
		for(int d=1;d<=K;++d){
			val*=i;
			if(val>n) break;
			pw[d].emplace_back(val);
		}
		if(i*i>n) break;
	}
	int ans=0;
	for(int l=1,r;l<=n;l=r+1){
		r=n/(n/l);
		int res=2*Phi_sum(n/l)-1;
		int sig=0;
		for(int d=1;d<=K;++d) sig+=Sum(r,d)-Sum(l-1,d);
		sig=(sig%mod+mod)%mod;
		ans+=sig%mod*res%mod;
	}
	ans=(ans%mod+mod)%mod;
	printf("%lld\n",ans);	
	return 0;
}

每个点不会在所有路径中同时作为起点或者终点出现,否则可以进行合并

其次进行路径起始终止点调整不会影响权值贡献,以为有交的部分正反经过抵消掉了

那么如果得到了每个点作为起始点和作为终止点的次数直接按照标号从小到大输出即可

出现次数的求解可以通过一个从叶子到根的递推得到,由于父子边经过时增量是相同的,直接给出递推式:

axaxtsonxatftft+([t<x][x<t])atfxfx+([x<t][t<x])at

Code Display
const int N=1e6+10;
vector<int> G[N];
int n,fa[N],a[N],dp[N];
int main(){
	freopen("tree.in","r",stdin); freopen("tree.out","w",stdout);
	n=read(); 
	for(int i=1;i<=n;++i) a[i]=read();
	for(int i=1;i<n;++i){
		int u=read(),v=read();
		G[u].emplace_back(v);
		G[v].emplace_back(u);
	}
	function<void(int,int)>dfs=[&](int x,int fat){
		for(auto t:G[x]) if(t!=fat){
			dfs(t,x);
			a[x]-=a[t];
			if(t<x) dp[t]+=a[t],dp[x]-=a[t];
			else dp[t]-=a[t],dp[x]+=a[t];
		}
		return ;
	};
	dfs(1,0);
	vector<int> A,B;
	for(int i=1;i<=n;++i){
		if(dp[i]>0){
			while(dp[i]--) A.emplace_back(i);
		}else{
			while(dp[i]++) B.emplace_back(i);
		}
	}
	int m=A.size();
	printf("%d\n",m);
	for(int i=0;i<m;++i) printf("%d %d\n",A[i],B[i]);
	return 0;
}

posted @   没学完四大礼包不改名  阅读(67)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示