Codeforces Round #637 题解(持续更新)

A. Nastya and Rice

国际惯例,A题必水。

只需要把输入数据的范围(即[n*(a-b),n*(a+b)])与给定的范围(即[c-d,c+d])比较,看是否相交即可。

代码如下:

#include <bits/stdc++.h>
using namespace std;
int t,n,a,b,c,d;
int left1,right1;
int main()
{
	cin>>t;
	for(int i=0;i<t;i++)
	{
		cin>>n>>a>>b>>c>>d;
		left1=n*(a-b);
		right1=n*(a+b);
		if(right1<c-d)
		{
			cout<<"No"<<endl;
		}
		else if(left1>c+d)
		{
			cout<<"No"<<endl;
		}
		else
		{
			cout<<"Yes"<<endl;
		}
	}

B. Nastya and Door

题目大意:
给定长度为N的数列,求长度为K的区间中(伪)极大值点的最大值和区间的左端点。

思路:
顺着题目的意思来,不是太难想,B题的思路一般并不复杂,只是写起来不总是那么流畅。
从起点开始依次扫长度为K的区间,计算区间中极大值点的数量(即大于相邻数的点),然后取最大值。

代码如下:

#include <bits/stdc++.h>
using namespace std;
int high[200010];		//高度数组
int perk[200010];		//perk[i]表示i为左端点的区间极大值的个数
int t,n,k;
int max1,max2=1;		//max1取极大值最大值,max2取其左端点
int main()
{
	cin>>t;
	for(int i=0;i<t;i++)
	{
		cin>>n>>k;		//边读边算,加快速度
		for(int j=0;j<k;j++)		//先算从0到k-1的,然后一步步转移
		{
			cin>>high[j];
			if(j>=2)		//判断是否为极大值的式子
			{
				if(high[j-1]>high[j-2]&&high[j-1]>high[j])
				{
					perk[0]++;
				}
			}
		}
		for(int m=k;m<n;m++)		//将区间向后移动
		{
			cin>>high[m];
			perk[m-k+1]=perk[m-k];		//区间倒数第二个数是否为极值的判断
			if(high[m-1]>high[m-2]&&high[m-1]>high[m])
			{
				perk[m-k+1]++;
			}
			if(high[m-k+1]>high[m-k]&&high[m-k+1]>high[m-k+2])		//区间第一个数是否为极值的判断
			{
				perk[m-k+1]--;
			}
		}
		for(int z=0;z<=n-k;z++)		//取最大值
		{
			if(perk[z]>max1)
			{
				max1=perk[z];
				max2=z+1;
			}
		}
		cout<<max1+1<<" "<<max2<<endl;
		max1=0;
		max2=1;
		for(int y=0;y<n;y++)		//归零
		{
			high[y]=perk[y]=0;
		}
	}
}

C. Nastya and Strange Generator

作为div1的第一题,也是比较水的!

题目大意
这道题的理解可能有一定的困难。(意识到学英语的重要性了?!)
r数组:
r[i]为从该位置开始第一个为空的位置序号(i为空为本身,后序无空位为-1)
count数组:
count[i]为r中i的取值数,可以证明,该值为从位置i往前数直到某空位结束中间的空位个数+1.
根据给定的n,依次将1~n插入一个空序中,每次插入的位置只能是所有count的最大值所在的位置之一,判断是否能构成输入的序列。

思路
理解了题意后这题就不难解决了!
推荐拿出纸笔对样例进行模拟,很快可以得出规律。
首先,插入1,可以在表的任何位置。紧接着插入2,我们可以发现,根据规则,2只能在1之后(仔细阅读规则可知),然后是3,4,直至到表尾。假如接下来要插入i,i就又可以在所有空位中选一个。
一步步插入后,得到序列。
最后序列的函数图像应该是像这样的:

如何判断题目给的序列是不是长这个样呢?
只需要排除掉不合规范的序列即可。
观察得到,该序列有很明显的单调性,可以从单调性入手。
单调减?无法判断,极端情况下,序列可能为单调减。
单调增?倘若序列相邻两数相差大于一,该序列不满足,用反证法可以轻易得到。
代码如下

#include <bits/stdc++.h>
using namespace std;
int t;
int n;
int now,pre;
int flag;
int main()
{
	cin>>t;
	for(int i=0;i<t;i++)
	{
		cin>>n;
		for(int j=0;j<n;j++)
		{
			cin>>now;
			if(j>=1)
			{
				if(now-pre>1&&flag==0)
				{
					cout<<"No"<<endl;
					flag=1;
				}
			}
			pre=now;
		}
		if(flag==0)
		{
			cout<<"Yes"<<endl;
		}
		flag=0;
	}
}

D. Nastya and Scoreboard

该题是一道比较基础的dp(搜索)题!难点在于要get到 这题可以用dp(搜索)做。

题目大意
不难理解,根据计分板上显示数的规则,给出n个残缺的数以及k个灯管,要用k个灯管补全这n个数,取所有补全方法中的的最大值,无法补全输出-1。

思路
为了后序操作的方便,我们可以先对输入的数进行预处理,将其补全至0~9所需的灯管数计算出来(无法表示成该数则为-1)。
然后思路就明朗了起来,就是将k恰好地分给n个数,高位用贪心从9开始取。直觉就是硬搜,从最高位开始,从补全成9到补全成0一步步递归搜索,即可得到答案。

代码如下(用的是dp做法,异曲同工):

#include <bits/stdc++.h>
using namespace std;
int n,k;
int pre[2010][10];		//pre[i][j]表示第i个数补全成j所需的灯管数
char temp[8];
char num[10][8]={"1110111", "0010010", "1011101", "1011011", "0111010", "1101011", "1101111", "1010010", "1111111", "1111011"};		//将0~9表示出来,方便补全计算
int poss[2010][2010];		//poss[i][j]表示是否可以用j根灯管补全后i+1个数
int find1(int now1,int now2);		//将poss值计算完毕后,从最高位开始贪心输出表示出的最大数。
int main()
{
	cin>>n>>k;
	for(int i=0;i<n;i++)
	{
		cin>>temp;
		for(int j=0;j<10;j++)		//预处理
		{
			for(int k=0;k<7;k++)
			{
				if(temp[k]=='1'&&num[j][k]=='0')
				{
					pre[i][j]=-1;
					break;
				}
				if(temp[k]=='0'&&num[j][k]=='1')
				{
					pre[i][j]++;
				}
			}
		}
	}
	for(int z=0;z<10;z++)		//给出初始情况,dp递推
	{
		if(pre[n-1][z]!=-1)
		{
			poss[0][pre[n-1][z]]=1;
		}
	}
	for(int m=1;m<n;m++)
	{
		for(int y=0;y<=k;y++)
		{
			for(int x=0;x<10;x++)
			{
				if(pre[n-m-1][x]!=-1&&y>=pre[n-m-1][x])		//转移方程
				{
					poss[m][y]=max(poss[m][y],poss[m-1][y-pre[n-m-1][x]]);
				}
			}
		}
	}
	if(poss[n-1][k]==0)
	{
		cout<<"-1";
		exit(0);
	}
	find1(n-1,k);
}
int find1(int now1,int now2)
{
	if(now1==0)
	{
		for(int i=9;i>=0;i--)
		{
			if(pre[n-1-now1][i]==now2)
			{
				cout<<i;
				return 0;
			}
		}
	}
	for(int j=9;j>=0;j--)		//贪心输出最大数
	{
		if(pre[n-now1-1][j]!=-1&&poss[now1-1][now2-pre[n-now1-1][j]]==1)
		{
			cout<<j;
			find1(now1-1,now2-pre[n-now1-1][j]);
			return 0;
		}
	}
}

E. Nastya and Unexpected Guest

解决该题需要掌握01-BFS算法!

题目大意
给出路径长度和路径上的停留点,在绿灯的时间内在不同的停留点间移动(必须要恰好用完一个绿灯周期的时间),或是直接走到终点。求起点走到终点的最短时间。

思路
比较直观的做法就是把点看作图,如果两点能恰好在一个绿灯周期走到,就连一条边,边权为绿灯加红灯周期,若能走到终点边权就为绿灯周期内走到终点的时间。最后用最短路算法求得最短时间。
但是边的构成并不直观,需要用搜索,因此必定TLE。
因此,我们采用一种改进的搜索算法,结合最短路算法,求得答案。
该改进算法即为01-BFS,用bfs的思想将一个点的相邻点加入队列,若还未用完绿灯周期,加入到队首,恰好用完绿灯周期,加入到队尾。然后取队首点,判断是否能直接到终点,还是要继续将相邻点加入队列。
推荐百度深度理解该算法再写这道题。

代码如下

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll ans,v,tmp,dis,u,r,g,m,n;
ll a[10010];
ll dp[10010][1010];		//dp[i][j]表示在第i个停留点还剩j的时间需要经过多少个绿灯红灯周期
struct node
{
	ll id, val;
	node(ll dd=0, ll vv=0)
	{
		id=dd;
		val=vv;
	}
};
deque <node> q;
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>a[i];
	}
	cin>>g>>r;
	sort(a+1,a+m+1);
	memset(dp,-1,sizeof(dp));
	q.push_front(node(1, 0));
	dp[1][0]=0;
	ans=1000000000;
	while(!q.empty())
	{
		node fr=q.front();
		q.pop_front();
		v=fr.val,u=fr.id;
		if(u==m)		//可到达终点的情况
		{
			if(v==0)
			{
				ans=min(ans,dp[m][v]*(g + r) - r);
			} 
			else 
			{
				ans=min(ans,dp[m][v]*(g + r) + v);
			}
			continue;
		}
		dis=a[u + 1]-a[u];
		if(v+dis<g&&dp[u + 1][v + dis]==-1)
		{
			dp[u + 1][v + dis]=dp[u][v];
			q.push_front(node(u+1,v+dis));
		}
		if(v+dis==g&&dp[u + 1][0]==-1)
		{
			dp[u + 1][0]=dp[u][v]+1;
			q.push_back(node(u+1, 0));
		}
		if(u>=1)
		{
			dis=a[u]-a[u-1];
			if(v+dis<g&&dp[u-1][v+dis]==-1)
			{
				dp[u-1][v+dis]=dp[u][v];
				q.push_front(node(u-1, v+dis));
			}
			if(v+dis==g&&dp[u-1][0]==-1)
			{
				dp[u-1][0]=dp[u][v]+1;
				q.push_back(node(u-1,0));
			}
		}
	}
	if(ans==1000000000)
	{
		cout<<"-1";
		return0;
	}
	cout<<ans<<endl;
}

to be continued……

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值