首师大附中集训第十七天综合模测

本文精选四道算法竞赛题目,包括区间和问题、安全路径、小朋友的笑话及一个待研究的期望理论问题,通过深入解析提供高效解决方案。文章涵盖了差分、二分查找、线段树、动态规划等算法技巧,适合算法爱好者和竞赛选手学习。

正题

      第一题:区间和2

      给出一个序列A,把所有子区间的和提出来后排序,询问L_i\to R_i的和。

      首先考虑差分,然后二分那个值是多少,check用Two_Pointers,直接用一个二分前缀和计算答案即可。

      

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;

int T;
int n,q;
long long a[200010],f[200010],g[200010];

long long get_tot(int x){
	int tot=0;
	long long ans=0;
	for(int l=1,r=0;l<=n;tot-=a[l],l++){
		while(r<n && tot+a[r+1]<=x) tot+=a[++r];
		if(r>=l) ans+=g[r]-g[l-1]-f[l-1]*(r-l+1);
	}
	return ans;
}

long long check(int x){
	int tot=0;
	long long ans=0;
	for(int l=1,r=0;l<=n;tot-=a[l],l++){
		while(r<n && tot+a[r+1]<=x) tot+=a[++r];
		if(r>=l) ans+=r-l+1;
	}
	return ans;
}

long long get_sum(long long x){
	int l=0,r=f[n],ans=0;
	long long temp=0,a=0;
	while(l<=r){
		int mid=(l+r)/2;
		if((temp=check(mid))>=x) r=(ans=mid)-1,a=temp;
		else l=mid+1;
	}
	return get_tot(ans)-(a-x)*ans;
}

int main(){
	freopen("sum.in","r",stdin);
	freopen("sum.out","w",stdout);
	scanf("%d",&T);
	long long l,r;
	while(T--){
		scanf("%d %d",&n,&q);
		for(int i=1;i<=n;i++) scanf("%d",&a[i]),f[i]=f[i-1]+a[i],g[i]=g[i-1]+f[i];
		while(q--){
			scanf("%lld %lld",&l,&r);
			printf("%lld\n",get_sum(r)-get_sum(l-1));
		}
	}
}

      第二题:安全路径

      我也不知道为什么一堆大神卡在这题上了。给一棵 个点的树,边的颜色非蓝即红。对于树上的一条路径,如果该路径上的所有边均为蓝色,则这 条路径是危险的,否则是安全的。 你想从树上挑出三个点,使得它们任意两点之间的路径都是安全的。

      问有多少种选择方案。

       我们把蓝边看成0边,红边看成1边,那么一个合法的方案两两路径的或值都为1。

       考虑补集转化。一个不合法的方案一定存在一条路径的或值为0,那么说明这两个点在一个0连通块里面,对于每一个0连通块计算答案即可。因为没加ll丢掉许多分。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;

int n;
struct edge{
	int y,next;
}s[100010];
int first[50010],len=0;
bool vis[50010];
long long ans=0;
const long long mod=1e9+7;

void ins(int x,int y){
	s[++len]=(edge){y,first[x]};first[x]=len;
	s[++len]=(edge){x,first[y]};first[y]=len;
}

int dfs(int x,int fa){
	int size=1;vis[x]=true;
	for(int i=first[x];i!=0;i=s[i].next) if(s[i].y!=fa) size+=dfs(s[i].y,x);
	return size;
}

int main(){
	freopen("path.in","r",stdin);
	freopen("path.out","w",stdout);
	scanf("%d",&n);
	int x,y;
	char c[2];
	for(int i=1;i<n;i++){
		scanf("%d %d",&x,&y);
		scanf("%s",c);
		if(c[0]=='b') ins(x,y);
	}
	for(int i=1;i<=n;i++) if(!vis[i]){
		int size=dfs(i,0);
		if(size==1) continue;
		(ans+=1ll*size*(size-1)/2*(n-size)+1ll*size*(size-1)/2*(size-2)/3)%=mod;
	}
	printf("%lld\n",(1ll*n*(n-1)/2*(n-2)/3-ans)%mod);
}

      第三题:小朋友的笑话

      小O是一个很萌很萌的女孩子。 有一天小O叫了很多很多萌萌哒小朋友到家里来玩。 由于太无聊了,她们开始讲笑话。 总共有N个小朋友排成一排,编号1-N。 在某个时刻,会有编号为x_i的小朋友看到了笑话 ,然后她会把这个笑话讲出来,与她距离不超过 的 小朋友都会听到这个笑话。 当一个小朋友听到一个笑话时,如果她是第一次听得到这个笑话,那么她会觉得这个笑话非常好笑,笑 的停不下来。如果她听到之前就是在笑的她会继续笑。 如果她之前听过这个笑话,那么她会觉得这个笑话非常无聊,她会立即停止笑。 现在小O想知道,在某些时刻一段区间内有多少小朋友在笑。

      想到每一次只会有听过的会停止笑,没有听过的就会开始笑。

      我们维护一个线段树,存的是节点是否在笑。

      每次讲笑话的时候,我们先强制让这个区间内全部的人笑,然后对于每一个笑话维护一个set,找到与当前区间有交的区间,让那些听过笑话的停下来就好了。同时合并区间插回去。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<set>
#define ls now<<1
#define rs now<<1|1
using namespace std;

int n,m;
struct node{
	int l,r;
	bool operator<(const node q)const{
		return r!=q.r?r<q.r:l<q.l;
	}
};
set<node> S[100010];
set<node>::iterator it;
int lazy[300010],tot[300010];

void pushdown(int now,int l,int r){
	if(!lazy[now]) return ;
	int mid=(l+r)/2;
	if(lazy[now]==1) tot[ls]=mid-l+1,tot[rs]=r-mid;
	else tot[ls]=tot[rs]=0;
	lazy[ls]=lazy[rs]=lazy[now];lazy[now]=0;
}

void change(int now,int x,int y,int l,int r,int t){
	if(x==l && y==r){
		if(t==0) lazy[now]=2,tot[now]=0;
		if(t==1) tot[now]=r-l+1,lazy[now]=1;
		return ;
	}
	pushdown(now,l,r);
	int mid=(l+r)/2;
	if(y<=mid) change(ls,x,y,l,mid,t);
	else if(mid<x) change(rs,x,y,mid+1,r,t);
	else change(ls,x,mid,l,mid,t),change(rs,mid+1,y,mid+1,r,t);
	tot[now]=tot[ls]+tot[rs];
}

int get_sum(int now,int x,int y,int l,int r){
	if(x==l && y==r) return tot[now];
	pushdown(now,l,r);
	int mid=(l+r)/2;
	if(y<=mid) return get_sum(ls,x,y,l,mid);
	else if(mid<x) return get_sum(rs,x,y,mid+1,r);
	else return get_sum(ls,x,mid,l,mid)+get_sum(rs,mid+1,y,mid+1,r);
}

int main(){
	scanf("%d %d",&n,&m);
	int op,x,l,k,r,ll,rr;
	while(m--){
		scanf("%d %d %d",&op,&x,&k);
		if(op==1){
			scanf("%d",&l);
			r=min(n,x+l);l=max(1,x-l);ll=l,rr=r;
			change(1,l,r,1,n,1);
			while(1){
				it=S[k].lower_bound((node){0,ll});
				if(it==S[k].end() || (*it).l>rr) break;
				node X=*it;
				change(1,max(ll,X.l),min(rr,X.r),1,n,0);
				l=min(l,X.l),r=max(r,X.r);S[k].erase(it);
			}
			S[k].insert((node){l,r});
		}
		else printf("%d\n",get_sum(1,x,k,1,n));
	}
}

       第四题:我还是不太懂期望那一套理论,学懂了再回来补。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值