题解目录
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……