最近的题目总结(树,电话线铺设,我的天)

12 篇文章 0 订阅
7 篇文章 0 订阅
博客涵盖了关于树的题目,介绍了如何利用prufer编码解决特定问题,并提及电话线铺设问题,涉及到最小生成树与路径查找。还讨论了一种古老村庄的社交问题,提出了动态维护区间最大值的算法挑战。
摘要由CSDN通过智能技术生成


几天没打博客了,现在记录一下一些比较好的题目。

Description
有n个点,它们从1到n进行标号,第i个点的限制为度数不能超过A[i].
现在对于每个s (1 <= s <= n),问从这n个点中选出一些点组成大小为s的有标号无根树的方案数。
n<=100

样例是
3
2 2 1
答案
3 3 2

solution

这一类的题目一般都会用到prufer编码,一个有x节点的数会变成一个有x-2节点的序列,每个点出现的次数是度数减一,然后我们的问题就变成了n个数里选x-2个数,i<=a[i]的方案数
在这里插入图片描述

4重循环爆不了,组合数杨辉三角形预处理即可。

#include<cstdio>
#include<iostream>
#include<cstring>
#define N 107
using namespace std;
const long long mod=1000000007;
int t,n,a[N];
long long c[N][N],f[N][N][N];
int main(){
	c[0][0]=1;
	for(int i=1;i<=101;i++){
		c[i][0]=1;
		for(int j=1;j<=i;j++)
			c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
	}
	scanf("%d",&t);
	while(t--){
		memset(f,0,sizeof(f));
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
			scanf("%d",&a[i]);
		f[0][0][0]=1;
		for(int i=1;i<=n;i++)
			for(int j=0;j<=i;j++)
				for(int k=0;k<=n-2;k++){
					f[i][j][k]=f[i-1][j][k];
					if(j>0){
						for(int l=1;l<=min(a[i]-1,k);l++)
							f[i][j][k]=(f[i][j][k]+f[i-1][j-1][k-l]*c[k][l]%mod)%mod;
					}
				}
		printf("%d ",n);
		for(int i=2;i<=n;i++){
			long long ans=0;
			for(int j=0;j<=i-2;j++)
				ans=(ans+f[n][j][i-2]*c[n-j][i-j])%mod;
			printf("%lld ",ans);
		}
		printf("\n");
	}
}

电话线铺设

Description

在这里插入图片描述

input

在这里插入图片描述

output

在这里插入图片描述

数据

在这里插入图片描述

solution

打比赛时跑去监考了,emm,看着四年级xxs考试真爽
由于李牌只要一个,所以说我们考虑先求王牌的最小生成树,然后枚举李牌(u,v,w),寻找王牌最小生成树上u与v的路径上权值最大的一条边,考虑把它换下来,然后记录最小值以及被换下的边就行了。
然后注意到用王牌不一定能联通,那么我们特判一下,枚举李牌看能不能把没联通的两部分联通,然后同理找最小值。
很多人说这题很烦,我倒是觉得没有啊,反正呢这题就是个最大生成树(显然kruskar适合点,毕竟特判时要判断联通部分)+树上路径(这不就是lca吗),lca打个倍增,没什么难的,注意细节。
另外真的很长,2800bites

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define N 400007
using namespace std;
int n,w,l,res,np,cnt,ans,fa[N],head[N],dis[N];
int f[N][30],mx[N][30],num[N][30];//倍增,分别表示父亲,最大边权,最大边权的编号
bool vis[N];
struct node{int u,v,w,num;}e[N];//求最小生成树
struct tree{int to,w,nxt,num;}tr[N<<1];//求完最小生成树用的
bool cmp(node a,node b){return a.w<b.w;}
int get(int x){return x==fa[x]?x:fa[x]=get(fa[x]);}
int read(){//快读
	char ch=getchar();int a=0;
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9'){
		a=a*10+(ch-'0');
		ch=getchar();
	}
	return a;
}
void add(int u,int v,int w,int num){//链式前向星
	tr[++cnt].to=v;
	tr[cnt].w=w;
	tr[cnt].num=num;
	tr[cnt].nxt=head[u];
	head[u]=cnt;
}
bool kruskal(){//最小生成树
	int tot=0;
	sort(e+1,e+w+1,cmp);//边排序
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=w;i++){
		int u=e[i].u,v=e[i].v,w=e[i].w;
		int fu=get(u),fv=get(v);//找祖宗
		if(fu!=fv){//不连通
			fa[fu]=fv;
			ans+=w;
			vis[e[i].num]=1;//说明这条边用过了
			tot++;
			add(u,v,w,e[i].num);//由于这条边被加入最小生成树中
			add(v,u,w,e[i].num);//所以我们将它建边
		}
		if(tot==n-1) return 1;//联通了
	}
	return 0;//若到了这里说明没有联通
}
void dfs(int x,int fa){//我们找到每个点的深度,以及父亲,边权最大值及编号先处理好
	dis[x]=dis[fa]+1;//深度
	f[x][0]=fa;//父亲找到
	for(int i=head[x];i;i=tr[i].nxt){
		int v=tr[i].to;
		if(v==fa) continue;
		mx[v][0]=tr[i].w;
		num[v][0]=tr[i].num;//由于v与x只有一条边,记录它的值与编号
		dfs(v,x);
	}
}
void RMQ(){
	for(int j=1;j<=20;j++)
		for(int i=1;i<=n;i++){
			f[i][j]=f[f[i][j-1]][j-1];
			if(mx[i][j-1]>mx[f[i][j-1]][j-1])
				mx[i][j]=mx[i][j-1],num[i][j]=num[i][j-1];
			else mx[i][j]=mx[f[i][j-1]][j-1],num[i][j]=num[f[i][j-1]][j-1];
		}
	//以上不想讲,会倍增的自然懂
}
void find(int x,int y){//找lca,res,np记录最大值以及其编号,不讲
	if(dis[x]<dis[y]) swap(x,y);
	if(dis[x]>dis[y]){
		for(int i=20;i>=0;i--)
			if(dis[f[x][i]]>dis[y]){
				if(mx[x][i]>res) res=mx[x][i],np=num[x][i];
				x=f[x][i];
			}
		if(mx[x][0]>res) res=mx[x][0],np=num[x][0];	
		x=f[x][0];
	}
	if(x==y) return;
	for(int i=20;i>=0;i--){
		if(f[x][i]!=f[y][i]){
			if(res<mx[x][i]) res=mx[x][i],np=num[x][i];
			if(res<mx[y][i]) res=mx[y][i],np=num[y][i];
			x=f[x][i],y=f[y][i];
		}
	}
	if(mx[x][0]>res) res=mx[x][0],np=num[x][0];
	if(mx[y][0]>res) res=mx[y][0],np=num[y][0];
	return;	
}
int main(){
	freopen("telephone.in","r",stdin);
	freopen("telephone.out","w",stdout);
	n=read(),w=read(),l=read();
	for(int i=1;i<=w;i++){
		int u=read(),v=read(),val=read();
		e[i].u=u;
		e[i].v=v;
		e[i].w=val;
		e[i].num=i;//边的编号
	}
	if(kruskal()){//联通的话
		dfs(1,0);//深搜一遍
		RMQ();//跑倍增
		int mn=0x3f3f3f3f,np1,np2=0;
		//mn表示换上去的李牌与换下来的王牌差最小,np1表示换下来王牌的编号,np2表示换上去李牌的编号
		for(int i=1;i<=l;i++){
			int u=read(),v=read(),w=read();
			res=0;
			find(u,v);
			if(w-res<mn)
				mn=w-res,np1=np,np2=i;//np,res是本次find中找到的最大王牌边的编号以及权值
		}
		ans+=mn;
		printf("%d\n",ans);
		vis[np1]=false;//替下的王牌标记没有被选
		for(int i=1;i<=w;i++)
			if(vis[i]) printf("%d\n",i);
		printf("%d\n",np2);
	}else{//图不连通
		int mn=0x3f3f3f3f,np2=0;
		for(int i=1;i<=l;i++){
			int u=read(),v=read(),w=read();
			int fu=get(u),fv=get(v);
			if(fu!=fv)//判断李牌的两个点是不是不连通,若不是的话,更新最小值
				if(mn>w) mn=w,np2=i;
		}
		printf("%d\n",ans+mn);
		for(int i=1;i<=w;i++)
			if(vis[i]) printf("%d\n",i);
		printf("%d\n",np2);
	}
}

细节较多,要注意。

我的天

Description
很久很以前,有一个古老的村庄——xiba村,村子里生活着n+1个村民,但由于历届村长恐怖而且黑暗的魔法统治下,村民们各自过着独立的生活,完全没有意识到其他n个人的存在。
但有一天,村民xiba臻无意中也得到了魔法,并发现了这个恐怖的事实。为了反抗村长,他走遍了全世界,找到了其他n个村民,并组织他们发动革命。但让这n个素不相识的村民(xiba臻已跟他们认识)同心协力去抵抗村长是很困难的,所以xiba臻决定先让他们互相认识。
这里,xiba臻用了xiba村特有的xiba思维:先让这n个人排成一列,并依次从1-n标号。然后每次xiba臻会选出一个区间[l, r],在这个区间中的人会去认识其他在这个区间中的人,但已经认识过得不会再去认识。这样,进行m次操作后,xiba臻认为这n个人能认识到许多人。
但是,为了精确地知道当前有多少对人已经认识了,xiba臻想要知道每次操作后会新产生出多少对认识的人,但这已是xiba思维无法解决的事了,你能帮帮他吗?

输入
5 5
2 3
2 4
3 5
1 5
2 4
输出
1
2
2
5
0
数据
对于20%的数据,1≤n,m≤100。
对于50%的数据,1≤n,m≤5000。
对于100%的数据,1≤n,m≤300000,1≤li≤ri≤n。

solution

首先50分做法,对于i他认识的人,一定i+1到一个距离(这里只考虑他右边,因为他左边的答案前面的人会统计),然后这个距离就是每次包含i的询问的最大的r,也就是说设右边最远到d[i],那么一次询问l<=i<=r,d[i]=max(d[i],r) 然后通过这个累计答案即可。
对于一次询问,i的贡献就是r-d[i],若di本来就大于r就不加贡献(这个十分显然)
然后100分的话,我们就是要考虑如果快速维护区间取max了。
然后这个东西可以用吉司机线段树来做。O(nlogn)
但是很显然我不会这个东西。。。。。。
但是也并不是说不可做对吧???
这个也是可以用普通线段树,当然有很多方法,我这里就说我的想法。
首先维护每个区间最小值,最大值,总和。
考试时我没想那么多,只维护了总和。
是因为这样的,假设目前区间(被l,r包含)d[i]总和为sum,答案就是(区间个数*r)-sum对吧?然后我一开始也是这么想的,结果对拍时发现wa了。
为什么呢?因为我们的某个d[i]也许大于r,那么如50分做法我们是不累计答案的,但是在这里我们还是算了进去就会导致答案变少。
然后考试时我也没什么时间导致心态炸了也就不知道怎么优化了。
那么后面想了想维护上述的三个值就能搞定,但是速度会很慢,听别人说是nlog方,我也不太清楚,不过还是可以过的。
具体思想就是,若是原先,当目前区间被l,r包含我们就不用下传直接询问,修改。但是由于d[i]>r的存在会WA。于是我们现在加强标准,不仅要被包含,而且不能有一个d[i]>r,然后其他跟普通线段树一样了。
具体看码:

#include<cstdio>
#include<iostream>
#define ll long long
#define ls num<<1
#define rs num<<1|1
#define lson num<<1,l,mid
#define rson num<<1|1,mid+1,r
using namespace std;
ll lazy[3000007],ans;
struct tree{
	ll min,max,w;
}tr[3000007];
void pushdown(int num,int l,int r){//下传
	if(lazy[num]){//lazy记录的是此区间的修改最大值
	//由于我们知道当区间所有值都不大于r时我们才直
	//接统计答案并且懒标记,所以它的儿子的最大值最小值也是
	//直接被lazy更新的,或者来说,此区间,此区间的儿子的值全被替换成lazy
		int mid=l+r>>1;
		lazy[ls]=max(lazy[num],lazy[ls]);
		tr[ls].min=tr[ls].max=lazy[num];//lazy必然比儿子区间任何数大
		tr[ls].w=(ll)(mid-l+1)*lazy[num];//总和必然是区间个数*lazy
		lazy[rs]=max(lazy[num],lazy[rs]);
		tr[rs].min=tr[rs].max=lazy[num];
		tr[rs].w=(ll)(r-mid)*lazy[num];
		lazy[num]=0;
	}
}
void build(int num,int l,int r){//建树没什么变化
	if(l==r){tr[num].w=tr[num].min=tr[num].max=l;}//
	else{
		int mid=l+r>>1;
		build(lson);build(rson);
		tr[num].w=tr[ls].w+tr[rs].w;
		tr[num].min=min(tr[ls].min,tr[rs].min);
		tr[num].max=max(tr[ls].max,tr[rs].max);
	}
}
void query(int num,int l,int r,ll x,ll y){
	pushdown(num,l,r);//下传先
	if(l>=x&&r<=y&&tr[num].max<=y){ans+=(ll)(r-l+1)*y-tr[num].w;}
	//看到了吗?最大的不能超过y
	else{
		int mid=l+r>>1;
		if(x>l||y<r){//此区间并不被x,y完全包含
			if(x<=mid) query(lson,x,y);
			if(y>mid) query(rson,x,y);//向儿子下传
		}else{//到了这说明此区间被完全包含,但是最大值超过了y
			if(tr[rs].min<=y){//当右儿子最小值<=y时,说明右区间有着被统计答案,同理由于d数组是单调不递减的,所以此时左儿子可以直接统计答案了
				ans+=(ll)(mid-l+1)*y-tr[ls].w;
				query(rson,x,y);
			}else if(tr[ls].min<=y)//右区间没用了,判断左区间有没有用(但是感觉好像没必要吧?)
				query(lson,x,y);
		}
	}
}
void change(int num,int l,int r,ll x,ll y){//下传或者更新的操作不变,就是统计答案换成了取最大值。
	pushdown(num,l,r);
	if(l>=x&&r<=y&&tr[num].max<=y){
		lazy[num]=max(lazy[num],y);
		tr[num].min=tr[num].max=y;
		tr[num].w=(ll)(r-l+1)*y;
	}else{
		int mid=l+r>>1;
		if(x>l||y<r){
			if(x<=mid) change(lson,x,y);
			if(y>mid) change(rson,x,y);
		}else{
			if(tr[rs].min<=y)
				change(rson,x,y),change(lson,x,y);
			else if(tr[ls].min<=y)
				change(lson,x,y);				
		}
		tr[num].w=tr[ls].w+tr[rs].w;
		tr[num].min=min(tr[ls].min,tr[rs].min);
		tr[num].max=max(tr[ls].max,tr[rs].max);
	}
}
int main(){
	freopen("ohmygod.in","r",stdin);
	freopen("ohmygod.out","w",stdout);
	int n,m;
	scanf("%d%d",&n,&m);
	build(1,1,n);//建树
	for(int i=1;i<=m;i++){
		ll l,r;
		scanf("%lld%lld",&l,&r);
		ans=0;
		query(1,1,n,l,r);//查询答案
		change(1,1,n,l,r);//将l到r之间的d[i]=max(d[i],r)
		printf("%lld\n",ans);
	}
}

跑了890ms,吉司机的话应该是100多。

好了最近比较有意思的就这几题了,今天比赛也有一题,但是已经10点了,所以明天再打把。

深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值