IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 2021.牛客暑假多校 第八场 补题 -> 正文阅读

[数据结构与算法]2021.牛客暑假多校 第八场 补题

  1. A题 Ares, Toilet Ares 阅读理解题、签到

大意:题目有点难懂,有个数字 a代表已经写对了,对于一道题有k 次获得代码的机会,每次获得的代码长度为 x,错误的概率为 p (分数形式)。当代码长度为 L 才能写对这道题,题目保证 k 次 的 x 的和为 L。求写对的题目数的期望值,结果对质数取模

思路:因为题目已经确保 x 的和为 L , 所以直接计算概率算出的就是写对这道题的期望值。最后加上 a。

代码如下:

#include <bits/stdc++.h>
using namespace std;
const int M=4933;
typedef long long LL;
int qmi(int a,int b)
{
	int res=1;
	while(b)
	{
		if(b&1)res=(LL)res*a%M;
		a=(LL)a*a%M;
		b>>=1;
	}
	return res;
}
int n,m,k,a,l;
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n>>m>>k>>a>>l;
	LL res=1;
	for(int i=1,x,y,z;i<=k;i++)
	{
		cin>>x>>y>>z;
		if(x==0)continue;// x 为 0 不获得代码,不计算概率	
		y=z-y;
		res=res*y*qmi(z,M-2)%M;
	}
	cout<<(res+a)%M<<"\n";
	return 0;
}
  1. D题 Dohna Dohna 思维,二进制

大意:给定 b数组和 c 数组, b = ( b 2 . . . . . . b n ) b=(b_2......b_n) b=(b2?......bn?) c = ( c 2 . . . . . . c n ) c=(c_2......c_n) c=(c2?......cn?) 并且定义 b i = a i ∣ a i ? 1 b_i=a_i|a_{i-1} bi?=ai?ai?1? c i = a i + a i ? 1 c_i=a_i+a_{i-1} ci?=ai?+ai?1? 给出 b 数组和 c 数组,求满足条件的 a 数组的个数

思路:对于 b 数组,代表的是a数组进行或运算,对于有关位运算的题目,很多都是从二进制的角度思考问题,单独考虑每个位的影响,但是还有个 c 数组涉及到加法,涉及到进位,不能单独考虑每个位了。但是我们有个公式 a + b = ( a ∣ b ) + ( a & b ) a+b=(a|b)+(a\&b) a+b=(ab)+(a&b) 这样我们就将根据题目中的加法和或运算的限制转化成于运算,然后我们就可以单独考虑每个为了。我们对于每个位,要么取0,要么取1.我们可以设置两个变量,0代表不能取,1代表能取,对于每一位的贡献就是乘法原理。

代码如下:

#include <bits/stdc++.h>
using namespace std;
const int N=100010;
typedef long long LL;
int n,b[N],c[N],d[N];
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n;
	for(int i=2;i<=n;i++)cin>>b[i];
	for(int i=2;i<=n;i++)cin>>c[i];
	for(int i=2;i<=n;i++)d[i]=c[i]-b[i];
	LL ans=1;
	for(int i=30;i>=0;i--)
	{
		int bit1=1,bit0=1;
		for(int j=2;j<=n;j++)
		{
			int nw1=0,nw0=0;
			int x=b[j]>>i&1,y=d[j]>>i&1;
			if(x&&y)nw1=bit1;
			if(x&&!y)nw1=bit0,nw0=bit1;
			if(!x&&!y)nw0=bit0; 

			bit1=nw1,bit0=nw0;
		}
		ans=ans*(bit0+bit1);
	}
	cout<<ans<<"\n";
	return 0;
}
  1. E题 Rise of Shadows 签到
  2. F题 Robots 思维,预处理,dp

大意:给定一个 n * m 的格子,n,m<=500。格子中有些点不能走,有三种机器人,第一种只能往下走,第一种只能往右走,第三种既可以往下又可以往右走。q次询问 (q<=5e5),每次询问给出机器人的类型,以及起点和终点的坐标,问是否能到达。

思路:这题暴力写可以过,官方解给的是分治。先来看看暴力怎么写。我们看到n,m 不大,但是询问很多,所以我们想到先进行预处理。我们先预处理出从该点能到的点,可以用bitset,0/1 表示某个点能不能到。但是呢?这样空间复杂度太大了,我们把点坐标映射成数字,那么也得 500*500 个数字,也就是要开个 二维都是500 * 500 的 bitset。但是呢,仔细想想,我们从后往前预处理枚举起点(x,y)的时候只会用到 (x+1,y) 和 (x,y+1) 两个起点去更新,所以对于起点我们可以不用开这么大,我们可以用把所有的询问存下来,用滚动数组,边求点,边更新询问。

代码如下:

#include <bits/stdc++.h>
using namespace std;
const int N=505;
typedef pair<int,int> PII;
int n,m,q;
char g[N][N];
bitset<N*N>f[2][N];
vector<PII> qer[N*N];
bool ans[N*1000];
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n>>m;
	for(int i=0;i<n;i++)cin>>g[i];
	cin>>q;
	for(int i=1;i<=q;i++)
	{
		int op,sx,sy,ex,ey;
		cin>>op>>sx>>sy>>ex>>ey;
		sx--,sy--,ex--,ey--;
		if(sx>ex||sy>ey)continue;
		if(op==1&&sy!=ey)continue;
		if(op==2&&sx!=ex)continue;
		qer[sx*m+sy].push_back({i,ex*m+ey});
	}
	for(int i=n-1;i>=0;i--)
	{
		for(int j=m-1;j>=0;j--)
		{
			if(g[i][j]=='1')f[i&1][j]=0;
			else 
			{
				f[i&1][j]=f[i&1^1][j]|f[i&1][j+1];
				f[i&1][j][i*m+j]=1;
			}
			for(auto &x:qer[i*m+j])ans[x.first]=f[i&1][j][x.second];
		}
	}
	for(int i=1;i<=q;i++)
	{
		if(ans[i])cout<<"yes"<<"\n";
		else cout<<"no"<<"\n";
	}
	return 0;
}

直接暴力写虽然是过了,但是复杂度还是有点高,采用分治优化,快了将近 10 倍。

分治思路如下:首先还是对前两种类型的机器人特殊处理,对于剩下的,如果能从起点走到终点,那么我们可以起点和终点之间的行任意选取一行,从起点在向终点走的过程中必然会经过改行上的某个点。所以我们可以以改行为中转行,处理出从起点能走到该行的哪些点,从该行的哪些点能走到终点,然后求交集就是合法的中转点。并且我们每次可以将mid 为中转行,我们只需要求起点终点的行跨过mid 的,对于其他的可以根据mid 将大区间一分为二进行分治。代码如下:

#include <bits/stdc++.h>
using namespace std;
const int N=505;
struct node{
	int sx,sy,ex,ey;
	int id;
};
bitset <N> f[N][N];
int n,m,q;
char g[N][N];
bool ans[N*1000];
void dfs(vector<node> q,int l,int r)
{
	if(q.size()==0||l==r)return ;
	vector<node> L,R,T;
	int mid=l+r >> 1;
	for(auto &x:q)
	{
		if(x.ex<=mid)L.push_back(x);
		else if(x.sx>mid)R.push_back(x);
		else T.push_back(x);
	}
	dfs(L,l,mid);
	dfs(R,mid+1,r);
	for(int i=mid;i>=l;i--)
		for(int j=m-1;j>=0;j--)
		{
			if(g[i][j]=='1')
			{
				f[i][j].reset();
				continue;
			}
			if(j<m-1)f[i][j]=f[i][j+1];
			else f[i][j].reset();
			if(i<mid)f[i][j]|=f[i+1][j];
			else f[i][j][j]=1;
		}
	for(int i=mid+1;i<=r;i++)
		for(int j=0;j<m;j++)
		{
			if(g[i][j]=='1')
			{
				f[i][j].reset();
				continue;
			}
			if(j>0)f[i][j]=f[i][j-1];
			else f[i][j].reset();
			if(i>mid+1)f[i][j]|=f[i-1][j];
			else f[i][j][j]=1;
		}
	for(auto &x:T)ans[x.id]=((f[x.sx][x.sy]&f[x.ex][x.ey]).count()>0);
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n>>m;
	for(int i=0;i<n;i++)cin>>g[i];
	cin>>q;
	vector<node> qer;
	for(int i=1;i<=q;i++)
	{
		int op,sx,sy,ex,ey;
		cin>>op>>sx>>sy>>ex>>ey;
		sx--;sy--;ex--;ey--;
		if(sx>ex||sy>ey)continue;
		if(op==1&&sy!=ey)continue;
		if(op==2&&sx!=ex)continue;
		// 特殊处理在同一行和同一列的,便于递归结束条件的确定
		if(sx==ex) 
		{
			while(sy<=ey&&g[sx][sy]!='1')sy++;
			ans[i]=sy>ey;
		}
		else if(sy==ey)
		{
			while(sx<=ex&&g[sx][sy]!='1')sx++;
			ans[i]=sx>ex;
		}
		else qer.push_back({sx,sy,ex,ey,i});
	}
	dfs(qer,0,n-1);
	for(int i=1;i<=q;i++)cout<<(ans[i]? "yes":"no")<<"\n";
	return 0;
}

总结:对于某些有共同性质的部分,我们可以进行划分成若干个小部分,分而治之。

  1. J题 Tree 树上博弈

大意:给定一棵树,节点编号1-n。有两个人,给出两个人的初始时所在的节点编号。每个人会把之前走过的点以及和点相连的边删除,当两人都无法移动时比赛结束。两人的都是自己走过的点的个数。两人都想尽可能使得自己的得分尽可能高,对方的得分尽可能低,且都以最优策略走,求最终二者得分的差值(先手得分-后手得分)

思路:假设先手在 u 处,后手在 v 处,则有 u-v 存在一条最短路径,一旦某个人从某点开始离开了这条路径,因为是一棵树,那么之后两人接着走就是互不影响的了,所以我们可以先将最短路径纪录下来,然后预处理出从每个点离开所能走的最大得分。记先手的得分为 s1,后手的得分为 s2。从先手的角度来看 尽可能使得 s1-s2尽可能大,从后手的角度尽可能使s1-s2小。如果只考虑先手走,我们可以遍历从每个点离开最短路径,计算此时s1-s2 的值,维护最大值。对于 此时的 s2 实际上就是 后手在 一段区间上的得分最大值,我们可以提前 st 表预处理。只考虑后手同理,维护个 s1-s2 最小值。同时考虑二者,通过对抗搜索轮流进行。总体时间复杂福 O ( n l o g n ) O(nlogn) O(nlogn)。代码如下:

#include <bits/stdc++.h>
using namespace std;
const int N=500005;
int n,s,t;
int path[N],path1[N],path2[N],len;
int st1[N][20],st2[N][20];
vector<int> G[N];
bool st[N];
int lg[N];
void init()
{
	for(int i=0;i<20;i++)
	{
		int t=1<<i;
		if(t>=N)break;
		lg[t]=i;
	}
	for(int i=1;i<N;i++)if(!lg[i])lg[i]=lg[i-1];

	for(int j=0;j<20;j++)
	{
		for(int i=1;i+(1<<j)-1<=len;i++)
		{
			if(!j)
			{
				st1[i][j]=path1[i];
				st2[i][j]=path2[i];
			}
			else 
			{
				st1[i][j]=max(st1[i][j-1],st1[i+(1<<j-1)][j-1]);
				st2[i][j]=max(st2[i][j-1],st2[i+(1<<j-1)][j-1]);
			}
		}
	}
}
int query1(int l,int r)
{
	int k=r-l+1;
	k=lg[k];
	return max(st1[l][k],st1[r-(1<<k)+1][k]);
}
int query2(int l,int r)
{
	int k=r-l+1;
	k=lg[k];
	return max(st2[l][k],st2[r-(1<<k)+1][k]);
}
bool dfs1(int u,int from,int dep)
{
	if(u==t)
	{
		len=dep;
		path[dep]=u;
		st[u]=1;
		return 1;
	}
	for(auto v:G[u])
	{
		if(v==from)continue;
		if(dfs1(v,u,dep+1))
		{
			path[dep]=u;
			st[u]=1;
			return 1;
		}
	}
	return 0;
}
int dfs2(int u,int from,int dep)
{
	int mx=dep;
	for(auto v:G[u])
	{
		if(st[v]||v==from)continue;
		mx=max(mx,dfs2(v,u,dep+1));
	}
	return mx;
}
int dfs3(int l,int r,int tp)
{
	if(!tp)
	{
		int mx=path1[l]-query2(l+1,r);
		if(l+1<r)mx=max(mx,dfs3(l+1,r,1));
		return mx;
	}
	else 
	{
		int mx=query1(l,r-1)-path2[r];
		if(l+1<r)mx=min(mx,dfs3(l,r-1,0));
		return mx;
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n>>s>>t;
	for(int i=1;i<n;i++)
	{
		int x,y;
		cin>>x>>y;
		G[x].push_back(y);
		G[y].push_back(x);
	}
	dfs1(s,0,1);//纪录最短路径上的点并标记
	for(int i=1;i<=len;i++)path[i]=dfs2(path[i],0,0); //预处理出从某个点离开最短路径所能得到的最多的分
	for(int i=1;i<=len;i++) //预处理出两人若从某点离开已经得到的分数
	{
		path1[i]=path[i]+i;
		path2[i]=path[i]+len-i+1;
	}
	init(); //st 表预处理区间最大值,用于 O(1)计算某段区间的最大分数
	cout<<dfs3(1,len,0)<<"\n";
	return 0;
}
  1. Yet Another Problem About Pi 计算几何,问题抽象转化

大意:一个平面区域被分成无穷多个长为d,宽为 w 的格子,求一条连续不断的长度为 π \pi π 的线能经过的做多的格子数目。对于格子的边界线不属于任何一个格子

思路:先来改一下题目,如果边界的也算在内,那么我们肯定是想办法经过尽可能多的格点,我们起始的时候直接在一个格点,即初始数目为 4,接下来我们每经过一个格点都会使得数目加2,如果我们是沿着方格的边走,那么显然我们沿着 w,d 小的那个走,并且尽量走直线,对于斜着走,显然是映射的方格边上更省长度。但是有个例外,对于方格的对角线虽然耗费长度,但是我们通过走一条对角线到达的格点可以使数目+3,这样可能使得答案更优。记 t=min(w,d)。 t ? x + w 2 + d 2 ? y < = π t*x+\sqrt{w^2+d^2}*y<=\pi t?x+w2+d2 ??y<=π 我们求的是 2 ? x + 3 ? y 2*x+3*y 2?x+3?y 的最大值。

不妨令 w<=d, 原式化为 a ? x + b ? y < = π a*x+b*y<=\pi a?x+b?y<=π a = w , b > = 2 w a=w, b>=\sqrt{2}w a=w,b>=2 ?w 当 b=1.5w 时,则有 3a=2b, 如果 y>=2 , 我们可以通过去掉y-2,x+3 使得结果不变, 当 b>1.5w 时,对于 y>=2 时,必然可以y-2使得 x增加的>3, 也就使得 2x+3y更大。当 b<1.5w 时,同理证得 x>=3 可以通过转化使得结果更优。所以最优解 一定满足 x<3或y<2 枚举就好了。题目说的是边界是不算,但是 π \pi π 无限小数,所以我们总有长度可以在边界位置反复横跳,所以边界是可以的

代码如下:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
double pi=acos(-1); 
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int _;
	cin>>_;
	while(_--)
	{
		double w,d;
		cin>>w>>d;
		if(w>d)swap(w,d);
		double dg=sqrt(w*w+d*d);
		LL ans=0;
		for(int i=0;i<=2;i++)
		{
			if(i*w<=pi)ans=max(ans,(LL)(4+i*2+floor((pi-i*w)/dg)*3));//枚举直线
			if(i*dg<=pi)ans=max(ans,(LL)(4+i*3+floor((pi-i*dg)/w)*2));//枚举对角线
		}
		cout<<ans<<"\n";
	}
	return 0;
}

以下是打了湖南省省赛的重现赛需要补的题,一并加到这里面了

一行盒子 思维,链表

大意:有一行盒子,从左到右依次编号为1, 2, 3,…, n。你可以执行四种指令:

  • 1 X Y表示把盒子X移动到盒子Y左边(如果X已经在Y的左边则忽略此指令)。
  • 2 X Y表示把盒子X移动到盒子Y右边(如果X已经在Y的右边则忽略此指令)。
  • 3 X Y表示交换盒子X和Y的位置。
  • 4 表示反转整条链。

指令保证合法,即X不等于Y。例如,当n=6时在初始状态下执行1 1 4后,盒子序列为2 3 1 4 5 6。接下来执行2 3 5,盒子序列变成2 1 4 5 3 6。再执行3 1 6,得到2 6 4 5 3 1。最终执行4,得到1 3 5 4 6 2。

思路:我们注意到题目中只有交换位置的操作,没有删除插入操作,所以我们可以利用链表的思想,数组模拟链表纪录每个数左右两边相邻的数,这样我们每次交换位置的时候相当于直接交换相关的指针。对于操作 4 我们并不需要实际进行操作,翻转一次也就相当于变了一次遍历的方向,翻转两次又变了回来,我们只需要纪录翻转了多少次就好了。值的注意的时,我们如果翻转了一次,因为我们并没有实际的翻转,这时候的操作 1 实际上相当于操作 2,对于操作 2 同理。另外对于操作 3 ,x,y 相邻和不相邻的时候改变的指针是不一样的,模拟一下就明白了。具体见代码:

#include <bits/stdc++.h>
using namespace std;
const int N=100010;
typedef long long LL;
int n,m;
int L[N],R[N];
void lk(int x,int y)
{
	R[x]=y;
	L[y]=x;
}
int main()
{
	int cs=1;
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		for(int i=1;i<=n;i++) //把 队头和队尾全变成 0
		{
			L[i]=i-1;
			R[i]=(i+1)%(n+1);
		}
		R[0]=1,L[0]=n;
		int op,x,y,to=0;
		while(m--)
		{
			scanf("%d",&op);
			if(op==4)
			{
				to^=1;
				continue;
			}
			scanf("%d%d",&x,&y);
			if(op==3&&R[y]==x)swap(x,y);// 要存下lx等,所以先交换好 x,y 在写 lx,
			int lx=L[x],rx=R[x],ly=L[y],ry=R[y];
			if(op!=3&&to)op=3-op;
			if(op==1)
			{
				if(x==ly)continue;
				lk(lx,rx);lk(ly,x);lk(x,y);
			}
			if(op==2)
			{
				if(x==ry)continue;
				lk(lx,rx);lk(y,x);lk(x,ry);
			}
			if(op==3)
			{
				if(rx==y)
				{
					lk(lx,y);lk(y,x);lk(x,ry);
				}
				else
				{
					lk(lx,y);lk(y,rx);lk(ly,x);lk(x,ry);
				}
			}
		}
		LL ans=0;
		if(to==0)for(int i=R[0],cnt=1;cnt<=(n+1)/2;cnt++,i=R[R[i]])ans+=i;
		else for(int i=L[0],cnt=1;cnt<=(n+1)/2;cnt++,i=L[L[i]])ans+=i;
		printf("Case %d: %lld\n",cs++,ans);
	}
	return 0;
}
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-09-09 12:01:51  更:2021-09-09 12:04:11 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/30 1:26:59-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码