NOIP 模拟赛
Day 1
题目名称 LGTB 玩扫雷 LGTB 学分块 LGTB 打THD
英文代号 mine divide thd
时限 1 秒 1 秒 1 秒
输入文件 mine.in divide.in thd.in
输出文件 mine.out divide.out thd.out
内存限制 64Mb 64Mb 64Mb
测试点个数 10 10 20
总分 100 100 100
时间: 2016 年7 月14 日
LGTB 玩扫雷
在一个n m 的棋盘上,有位置上有雷(用“*” 表示),其他位置是空地(用“.” 表示)。
LGTB 想在每个空地上写下它周围8 个方向相邻的格子中有几个雷。
请帮助他输出写了之后的棋盘
输入
输入第一行包含两个整数n, m 代表棋盘大小
接下来n 行,每行m 个字符,代表棋盘
1<=n,m<=1000
输出
输出包含n 行,每行m 个字符,代表LGTB 写了数字之后的棋盘
样例
样例输入
3 3
*.*
...
*.*
样例输出
*2*
242
*2*
LGTB 学分块
LGTB 最近在学分块,但是他太菜了,分的块数量太多他就混乱了,所以只能分成3 块
今天他得到了一个数组,他突然也想把它分块,他想知道,把这个数组分成3 块,块可以为空。假设3 块各
自的和中的最大值最小
请输出分完之后3 块中的最大值
输入
输入第一行包含一个整数n 代表数组大小
接下来n 个整数a1, a2, ..., an,代表数组
对于40% 的数据,1<= n <=10
对于70% 的数据,1 <=n <= 10^3
对于100% 的数据,1 <=n <= 10^5, 1<= ai <= 10^7
输出
输出包含1 个整数,代表分块完成后3 块中的最大值
样例
样例输入
10
2 5 1 4 7 3 6 2 5 1
样例输出
14
LGTB 玩THD
LGTB 最近在玩一个类似DOTA 的游戏名叫THD
有一天他在守一座塔,对面的N 个小兵排成一列从近到远站在塔前面
每个小兵有一定的血量hi,杀死后有一定的金钱gi
每一秒,他都可以攻击任意一个活着的小兵,对其造成P 点伤害,如果小兵的血量低于1 点,小兵死亡,他
得到金钱。他也可以不攻击任何小兵。
每一秒LGTB 攻击完毕之后,塔会攻击距离塔最近的一个活着的小兵,对其造成Q 点伤害,如果小兵的血
量低于1 点,小兵死亡,LGTB 不会得到金钱
现在LGTB 想知道,在他选择最优策略时,他能得到多少钱。
输入
输入第一行包含3 个整数P, Q, N
接下来N 行,每行包含2 个整数hi, gi
第i 个小兵和塔之间的距离为i
输入的意义如题面所示
对于20% 的数据,1<= N <=4
对于50% 的数据,1<= N <= 20
对于100% 的数据,20<=P,Q<= 200, 1<= N <=100, 1 <= hi <= 200, 0 <= gi<= 106
输出
输出包含一个整数W,代表LGTB 最多能获得的金钱
样例
样例输入
20 60 3
80 100
80 200
120 300
样例输出
500
解题报告
第一题就是简单的增量数组的运用,或者不用增量数组几个判断就搞定
AC代码
#include<cstdio> #include<iostream> using namespace std; int n,m; char x; int side[2][8]={{1,-1,0,0,1,1,-1,-1},{0,0,1,-1,1,-1,-1,1}}; int b2[1002][1002]; char b1[1002][1002]; int main() { freopen("mine.in","r",stdin); freopen("mine.out","w",stdout); scanf("%d %d ",&n,&m); for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { scanf("%c",&b1[i][j]); if(b1[i][j]=='*') for(int k=0;k<=7;k++) b2[i+side[0][k]][j+side[1][k]]++; } getchar(); } for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { if(b1[i][j]=='*') printf("*"); else printf("%d",b2[i][j]); } cout<<endl; } return 0; }
第二题方法很多,刚开始写了一个暴力枚举O(n^2)只过得到70%
就想了办法卡了一下时间反正不超时,多对了一组
结果后来对拍时就用这个卡时间的程序检查第二个程序大数据一直不对,就没敢交,气死我了,该好好反思一下。
然后想了一个O(n(longn)^2)的二分法,先确定一个第一个点然后用类似ST的方法去找第i个点到后面2^j个点的最大值中的最小值
其实不用这么麻烦确定了i点直接找后面的中点开始就行了O(nlongn)
后来两位同学都提到提到了一种超简单的方法只用把总和除以三来从前往后和从后往前来找这样的点就可以了,在考虑两边端点取不取的4种情况就可以了只用O(n)不到,太6了
还要注意的一点就是可能只有1,2个点的时候
暴力+卡时间80%
#include<cstdio> #include<iostream> #define MAX 100005 #define LL long long using namespace std; int n; int lim; LL sum[MAX]; LL _max(LL a,LL b,LL c) { LL d=(a>b?a:b); return d>c?d:c; } int main() { freopen("divide.in","r",stdin); freopen("plain.out","w",stdout); cin>>n; for(int i=1;i<=n;i++) { scanf("%I64d",sum+i); sum[i]+=sum[i-1]; } LL maxx=sum[n]; for(int i=1;sum[i]<maxx;i++) for(int j=i+1;sum[n]-sum[j]<maxx;j++) { if(maxx>_max(sum[i],sum[j]-sum[i],sum[n]-sum[j])) maxx=_max(sum[i],sum[j]-sum[i],sum[n]-sum[j]); lim++; if(limm>500000000);break;//卡时间 } cout<<maxx; return 0; }
AC程序
#include<cstdio> #include<iostream> #define MAX 100005 #define LL long long using namespace std; int n; LL s[MAX],maxx; LL mom[MAX][20]; LL _max(LL a,LL b,LL c) { LL d=(a>b?a:b); return d>c?d:c; } LL _maxl(LL a,LL b) { return a>b?a:b; } LL _minl(LL a,LL b) { return a<b?a:b; } void go(int l,int ll,int r,int rr) { if(ll+1>=rr) return; LL L=s[r]-s[l]; LL R=s[n]-s[r]; if(R==L) { maxx=_minl(maxx,_maxl(R,s[l])); return; } else if(R>L)//右边大 { maxx=_minl(maxx,_maxl(R,s[l])); go(l,r,(r+rr)>>1,rr); } else { maxx=_minl(maxx,_maxl(L,s[l])); go(l,ll,(ll+r)>>1,r); } } int main() { freopen("divide.in","r",stdin); freopen("divide.out","w",stdout); cin>>n; for(int i=1;i<=n;i++) { scanf("%d",s+i); s[i]+=s[i-1]; } if(n<=3){ cout<<_max(s[1],s[2]-s[1],s[3]-s[2]); return 0; } maxx=s[n]; for(int j=1;j<=18;j++) for(int i=1;s[i]<maxx;i++) if(i+(1<<j)<=n)//下面区间是左开右闭 { if(_max(s[i],s[i+(1<<j)-1]-s[i],s[n]-s[i+(1<<j)-1])<maxx) maxx=_max(s[i],s[i+(1<<j)-1]-s[i],s[n]-s[i+(1<<j)-1]); go(i,i,i+(1<<j)-1,n); } cout<<maxx; return 0; }
第三题
一看这道题就知道要使用动态规划,还是宋词对的比较多,她的方法最接近正解
刚开始一直想不出状态转移方程,就写了一个暴力dp,只过了5组数据
想不出状态转移方程的原因主要是只想要把多个状态对应到一个状态上
其实应该把一个状态对应到两种(打或不打)上,就好解决了
题解还把兵的血量转化成时间,因为塔不停攻击
我们就让lgtb补刀就可以了,把其对每个兵补刀的时间算出来就好了,但注意端点
F[i][j]表示人lgtb在攻击第i个小兵时进行了j次攻击时获得的金币数
由F[i-1][j]可有两种推到方式,一是选择让塔解决该小兵,就累计次数
二是考虑这个小兵,如果lgtb可以把它补死,就减少次数,增加金币
最后F[n][i...1001]中最大值就是答案了,虽然总时间不超过1000,但多算无害
其实第三题的数据太弱了,有位很有想法的同学直接把所有的金币求和再输出都对了13组,输出0的也对了5组,随机函数的也对了三组.......我天!什么水数据
AC代码
#include<cstdio> #include<iostream> using namespace std; int gld[205]; int hp[205]; int ta[205]; int pr[205]; int p,Q,n; int f[205][2005];//表示攻击第i个兵时还有j次累计攻击 int main() { freopen("thd.in","r",stdin); freopen("thd.out","w",stdout); cin>>p>>Q>>n; for(int i=1;i<=n;i++) { scanf("%d %d ",&hp[i],&gld[i]); ta[i]=(hp[i]-1)/Q; pr[i]=(hp[i]-ta[i]*Q-1)/p+1;//进1 } for(int i=0;i<=n;i++) for(int j=0;j<=1002;j++) f[i][j]=-100000000; f[0][1]=0; for(int i=1;i<=n;i++) for(int j=0;j<=1001;j++) { if(f[i][j+ta[i]+1]<f[i-1][j]) f[i][j+ta[i]+1]=f[i-1][j]; if(j>=pr[i]-ta[i]) if(f[i][j-pr[i]+ta[i]]<f[i-1][j]+gld[i]) f[i][j-pr[i]+ta[i]]=f[i-1][j]+gld[i]; } int maxx=0; for(int j=0;j<=1001;j++)if(maxx<f[n][j])maxx=f[n][j]; cout<<maxx; return 0; }
这套是暂时考得最好的一套,主要原因是第一题送的分接住了,第二题没丢太多的分,第三题暴搜写对了,但题目的数据不强也是原因