这是本蒟蒻在CSDN的第一篇题解报告
第一次就写NOIP鸭梨很大
本蒟蒻在PJ中获得了290的成绩,几乎可以说是压线省一(ZJ 分数线280)
所以我也花了一些功夫来研究本次的四道题目
希望明年TG可以获得比较好的成绩(省二什么的)
进入正文——
原题链接
这道题目从难度上说是一道刚学OI的新手的练习题,难度为0
事实上,还是有一点东西需要注意的,而很多OI选手都不知道
那么我个人在学OI之前是先学的python,用的教材是Sande父子的《父与子的编程之旅》
其中在开头就讲到了关于 精度 的问题
比如下面这段伪代码:
cin >> a >> b >> c;
cout << a*0.2+b*0.3+c*0.5 << endl;
如果直接这么写,本机上测试应该不会有任何问题,但是要是放在linux系统下(例如今年的CCF评测机),可能会这样:
输入: 100 100 100
输出: 99.999999997
这也就是为什么今年有那么多人申诉从而迫使CCF改变评测机的设置
本蒟蒻代码如下:
#include
using namespace std;
int a,b,c;
int main()
{
cin >> a >> b >> c;
a/=10;
b/=10;
c/=10;
a*=2;
b*=3;
c*=5;
cout << a+b+c;
return 0;
}
这样写就不会有任何问题,所以本蒟蒻一开始就是100 (省掉了50申诉费)
原题链接
当我从考场里出来时,很多人都说他们用的是string
而用了 取模求最后几位 的只有我和另外一个同学(我们队)
当时就有人有疑问了:形如00005的数怎么办???
我当时也懵了,好在CCF并没有这样的数据
而使用这种算法是建立在一个前提上的:把数的位数告诉你,否则效率不如字符串
而如果把数的位数告诉了你,而数的位数比较大,那么使用这种算法就会比较快(超过longlong的除外)
鉴于 位数 k 也输进来了,所以求最后几位就可以直接 % 10k 取得
举个栗子:
比如 图书编码为 23233
需求码长度为 3 ,值为 233
这时的 10k 为 1000
而 23233 % 1000 正好是 233 ,取到了最后三位
代码如下:
#include
#include
#include
using namespace std;
int n,m,q[15]={0,10,100,1000,10000,100000,1000000,10000000,100000000},a[1005];
//预处理
int main()
{
cin >> n >> m;
for(int i=1;i<=n;i++) cin >> a[i];
sort(a+1,a+n+1);//先排序保证每次取得最优解
for(int i=1;i<=m;i++)
{
int x,y;
bool fg=false;
cin >> x >> y;
for(int j=1;j<=n;j++) if(a[j]%q[x]==y)//由于排了序,所以符合条件的第一个必然是最优解
{
fg=true;//记录已取到答案
cout << a[j] << endl;//输出
break;//跳出本层循环即可
}
if(!fg) cout << -1 << endl;//如果没有找到解输出-1
}
return 0;
}
从这里开始的题目就比较难了,做好准备!
原题链接
我本人在T3打的是dfs暴力
当时获得了60分,如果加一个记搜的话70分
那么这题正解是什么呢?
大致看下来有三种解法:
1. 玄学记搜dfs
2. 靠谱坚实bfs
3. 看不懂的最短路
而 dp 写法是不行的,因为这题有后效性(不明显)
我在一位dalao的帮助下写出了第1种方法,接下来讲讲做法
首先是变量:
核心变量是一个三位数组 f ,那么 f 数组表示什么呢?
f[x][y][0] 表示 x,y 有色时的最小花费,
f[x][y][1] 表示要使用魔法把 x,y 变为 颜色0 的最小花费
f[x][y][2] 则表示变为 颜色1
(这不是dp是记搜)
别的应该都比较好理解
代码如下:
#include
#include
#include
#include
#include
using namespace std;
int m,n,a[105][105],xx,yy,zz,ans=2147400000,f[105][105][3];
//前面解释过的f数组,别的都不多说了
bool fg;
void dfs(int x,int y,int now,int sum,bool mag)
{
//xy为当前坐标,now为当前格子的颜色,sum为当前花费,mag为是否使用魔法
if(x>m || y>m || x<1 || y<1) return;//如果越界则退出
else if(a[x][y]==0)//如果当前格子没有颜色
{
if(mag) return;//使用过魔法就退出
sum+=2;//否则加上改变的花费
if(f[x][y][1]>sum+now-1)//记搜大法 变相表现走到颜色0的花费
{
f[x][y][1]=sum+now-1;
dfs(x+1,y,1,sum+now-1,1);
dfs(x,y+1,1,sum+now-1,1);
dfs(x-1,y,1,sum+now-1,1);
dfs(x,y-1,1,sum+now-1,1);
}
if(f[x][y][2]>sum-now+2)//同上,表现走到颜色1的花费
{
f[x][y][2]=sum-now+2;
dfs(x+1,y,2,sum-now+2,1);
dfs(x,y+1,2,sum-now+2,1);
dfs(x-1,y,2,sum-now+2,1);
dfs(x,y-1,2,sum-now+2,1);
}
}
else//如果有颜色
{
if(a[x][y]!=now) sum+=1;//如果当前格和上一个不一样就加1花费
if(sum>=f[x][y][0]) return;//记搜大法
f[x][y][0]=sum;//赋值记搜数组
dfs(x+1,y,a[x][y],sum,0);
dfs(x,y+1,a[x][y],sum,0);
dfs(x-1,y,a[x][y],sum,0);
dfs(x,y-1,a[x][y],sum,0);//无脑dfs
}
}
int main()
{
memset(f,0x3f3f3f,sizeof(f));
cin >> m >> n;
for(int i=1;i<=n;i++)
{
cin >> xx >> yy >> zz;
a[xx][yy]=zz+1;
//鉴于有颜色0,为了不赋初值故如此
}
dfs(1,1,a[1][1],0,0);//dfs
if(a[m][m]==0) ans=min(f[m][m][1],f[m][m][2]);
else ans=f[m][m][0];//存答案
if(ans>=10000000) cout << -1;
else cout << ans;//输出
return 0;
}
原题链接
这道题目难度极高(怎么说也是蓝题)
做法是 dp+二分+单调队列优化
非常烦人!
考试时本蒟蒻因为时间(脑子)不够 写的是枚举+错误的dp
骗到30分(没这30我就GG了)
那么有人问了:单调队列怎么用?
大家都知道,单调队列会维护队首(这题是最大),那么我们就可以将 可以跳到的范围 内的最大值维护在队首,降低大量复杂度
大家可以自己手打一个双向队列(数组模拟)
本蒟蒻就只好用STL大法的deque了
推荐一篇博客:justmeh大佬的单调队列初步
代码可能比较难理解,但是原理解释得很透彻
顺便给道例题:Luogu P1886 滑动窗口
二分和dp就不多说了,大家都看得出来
代码如下:
#include
#include
#include
#include
using namespace std;
deque <int> q;//STL大法好!!!
long long n,d,k,f[500005];
long long a[500005],w[500005];//a存位置,w存分数
inline int read()
{
int sum=0,fu=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') fu=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
sum=(sum << 3)+(sum << 1)+(ch ^ 48);
ch=getchar();
}
return sum*fu;
}//读入优化
bool DP(int x)//dp本体
{
int t1=d-x > 1 ? d-x : 1 ,t2=d+x;//计算最近最远跳跃距离
while(!q.empty()) q.pop_back();//清空队列
memset(f,0,sizeof(f));//清空数组
int t=0;//现在将要入队的点
for(int i=1;i<=n;i++)
{
while(a[i]>=t1+a[t])//如果足够远
{
while(!q.empty() && f[t]>=f[q.back()]) q.pop_back();
q.push_back(t);//进队,维护单调队列
t++;
}
while(!q.empty() && a[i]>a[q.front()]+t2) q.pop_front();
//把太远跳不到的出队
if(q.empty()) f[i]=-9999999999;//如果队列空了,存入负无限
else f[i]=f[q.front()]+w[i];//否则存入当前最大值
if(f[i]>=k) return 1;//如果得到解返回真
}
return 0;//如果一直都没得到解返回假
}
int main()
{
long long ans=9999999999;//最好设这么大,否则会WA
n=read();
d=read();
k=read();
int S=0;
for(int i=1;i<=n;i++)
{
a[i]=read();
w[i]=read();
}
int L=0,R=a[n];
while(L<=R)//二分开始
{
long long mid=L+R >> 1;//mid代表改造花的金币
bool res=DP(mid);//存下是否能够得到k分
if(res)//如果可以
{
ans=min(ans,mid);//存答案
R=mid-1;
}
else L=mid+1;
}
printf("%lld",ans);//输出即可
return 0;
}
这次比赛题目 T1、T2 送分,T3、T4对思考要求比较高,本蒟蒻也没有透彻理解,而这次拿到1=也是运气居多,可见我们要走的路还有很长
各位,NOIP2018 TG见。
原创 By Venus
写的不好大佬轻喷