考试时的思路:
第一题先循环水一个80分出来
第二题先水70分,再用倍增枚举每一个坦克对应的下一个坦克。
第三题直接上DFS,能拿多少拿多少。
题解:
第一题 S数
这道题,我打了个表,然后用二分法来做,记录每个答案的位置,即可得解。但是最后时间不够了,我发现lower_bound用错了的时候只剩下4分钟了,匆忙修改,但还是没对,不过好在暴力分拿到了,看来先打暴力这个方法肯定没错。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#define M 100086
#define FOR(i,a,b) for(int i=(a),i##_end_=(b);i<=i##_end_;++i)
#define DOR(i,a,b) for(int i=(a),i##_end_=(b);i>=i##_end_;--i)
using namespace std;
int sum[M];
void Init() {
FOR(i,1,M-1) {
int x=i;
while(x) {
sum[i]+=x%10;
x/=10;
}
}
}
int n,m;
int A[]= {}//打表
void solve1() {
Init();
int ans=0;
FOR(i,n,m) {
long long x=i*i;
int cnt=0;
while(x) {
cnt+=x%10;
x/=10;
}
if(cnt==sum[i]*sum[i])ans++;
}
cout<<ans<<endl;
}
void solve2() {
int x,y;
y=lower_bound(A+1,A+sizeof(A)/4,m)-A-1;
x=lower_bound(A+1,A+sizeof(A)/4,n)-A-1;
cout<<y-x<<endl;
}
int main() {
cin>>n>>m;
if(m<=1e5)solve1();
else solve2();
solve2();
return 0;
}
这道题有没有更好的算法呢?
显然有。
首先我们分析一下最大的数也就是18个9,那么加起来也就是9*18,开个方也就在12左右,只要根据这个来DFS就可以了,还是非常简单的,就看这一点有没有想到。
第二题 黑客入侵
这道题,首先暴力就70分,少了点儿,因此我就考虑到了看电视这道题,用同样的思路,每一个坦克所对应的下一辆坦克是固定的,因此只要用倍增法求出有多少辆坦克没被打到,就可以顺势求出有多少辆坦克被打了。这种算法,还真没人这么写过,虽然麻烦了点,但正确性显然啊,特别是熟练使用倍增之后,如同行云流水一般,刷刷刷代码就打完了。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#define M 200050
#define FOR(i,a,b) for(int i=(a),i##_end_=(b);i<=i##_end_;++i)
#define DOR(i,a,b) for(int i=(a),i##_end_=(b);i>=i##_end_;--i)
using namespace std;
struct node {
int pos,attack;
} A[M],B[M];
int ans=1e9;
bool mark[M];
int n;
int Fa[20][M];
void solve1() {
DOR(i,n+1,1) {
int cnt=n+1-i;
memset(mark,0,sizeof(mark));
DOR(j,i-1,1) {
if(mark[j])continue;
int x=A[j].pos-A[j].attack;
DOR(k,j-1,1) {
if(A[k].pos>=x) {
mark[k]=true;
cnt++;
} else break;
}
}
if(cnt<ans)ans=cnt;
}
cout<<ans<<endl;
}
int cnt[20][M];
void Init() {
FOR(i,1,n)B[i]=A[n-i+1];
FOR(i,1,n) {
int x=B[i].pos-B[i].attack;
FOR(j,i+1,n) {
if(B[j].pos>=x)continue;
Fa[0][i]=j;
break;
}
}
FOR(i,1,n)cnt[0][i]=1;
FOR(j,1,19)FOR(i,1,n) {
Fa[j][i]=Fa[j-1][Fa[j-1][i]];
cnt[j][i]=cnt[j-1][i]+cnt[j-1][Fa[j-1][i]];
}
}
void solve2() {
Init();
FOR(i,0,n-1) {
int tmp=0;
int t=i+1;
FOR(j,0,19) {
tmp+=cnt[j][t];
t=Fa[j][t];
}
if(n-tmp<ans)ans=n-tmp;
}
cout<<ans<<endl;
}
int main() {
cin>>n;
FOR(i,1,n)scanf("%d%d",&A[i].pos,&A[i].attack);
if(n<=2000)solve1();
else solve2();
return 0;
}
其实其他的大佬的代码比我更精简,想法也不错,所以可以看一看zhowie大佬的博客以及YZK大佬的博客。
第三题:炮兵
这道题我DFS写错了,但是迷之水了40分,我也很无奈啊。
错误示范:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
using namespace std;
#define FOR(i,a,b) for(int i=(a),i##_end_=(b);i<=i##_end_;++i)
#define DOR(i,a,b) for(int i=(a),i##_end_=(b);i>=i##_end_;--i)
int n,m;
bool pic[120][20];
char s[20];
bool mark[120][20];
int ans=0;
bool tmp[120][20];
void dfs(int x,int y,int cnt) {
if(cnt>ans)ans=cnt;
FOR(i,1,n)FOR(j,1,m)tmp[i][j]=mark[i][j];
FOR(i,1,n)FOR(j,1,m)if(!mark[i][j]) {
int top=max(1,i-2);
int tail=min(i+2,n);
FOR(k,top,tail)mark[k][j]=true;
int top1=max(1,j-2);
int tail1=min(j+2,m);
FOR(k,top1,tail1)mark[i][k]=true;
dfs(i,j,cnt+1);
FOR(k,top,tail)mark[k][j]=tmp[k][j];
FOR(k,top1,tail1)mark[i][k]=tmp[i][k];
}
}
int main() {
cin>>n>>m;
FOR(i,1,n) {
scanf("%s",s+1);
FOR(j,1,m)pic[i][j]=(s[j]=='H');
}
FOR(i,1,n)FOR(j,1,m)mark[i][j]=pic[i][j];
dfs(0,0,0);
cout<<ans<<endl;
return 0;
}
可以清楚地看到,在还原现场的时候,看上去好像没什么错,用tmp数组来维护原来的数组,但是,在往下dfs的时候,tmp数组又重新赋值了,导致tmp数组用了跟没用一样,这就解释了为什么我接下来造了一组100×10的数据,每个都是P,却3毫秒跑出来。但是,水了40分过来,这次可以说是侥幸,下次如果数据比较强的话,就没那么好的运气了,这点要注意,千万不能让暴力的分数也丢了。
这道题的正解是状态压缩dp,每一个点只与前一个点,以及上面第二个点有关系,因此可以用dp[i][j][k]来表示前i行放完,第i-1行放坦克情况为j,第i行状态为k的情况,具体正解,请见YZK大佬的代码。
#include<iostream>
#include<cstdio>
#include"cstring"
#define M 105
#define FOR(i,a,b) for(register int i=(a),i##_end_=(b);i<=i##_end_;++i)
#define DOR(i,a,b) for(register int i=(a),i##_end_=(b);i>=i##_end_;--i)
using namespace std;
int pic[M][15];
int dp[M][65][65];
int flag[65];
char s[15];
void checkmax(int &a,int k) {
if(a==-1 or a<k)a=k;
}
int cnt[M*10];
int n,m;
int t=0;
void create() {
FOR(i,0,(1<<m)-1) {
if(i&(i<<1))continue;
if(i&(i<<2))continue;
if(i&(i>>1))continue;
if(i&(i>>2))continue;
flag[++t]=i;
FOR(j,0,m-1)if(i&(1<<j))cnt[i]++;
}
}
int ShimaKZ[M];
int ans=0;
int main() {
cin>>n>>m;
create();
memset(dp,-1,sizeof(dp));
FOR(i,1,n) {
scanf("%s",s+1);
FOR(j,1,m)if(s[j]=='H')ShimaKZ[i]|=(1<<(m-j));
}
dp[0][1][1]=0;
FOR(i,1,n)FOR(j,1,t)FOR(k,1,t) {
if(flag[j]&flag[k])continue;
if(not(~dp[i-1][j][k]))continue;
FOR(l,1,t) {
if(flag[j]&flag[l])continue;
if(flag[k]&flag[l])continue;
if(flag[l]&ShimaKZ[i])continue;
checkmax(dp[i][k][l],dp[i-1][j][k]+cnt[flag[l]]);
}
}
FOR(i,1,t)FOR(j,1,t)checkmax(ans,dp[n][i][j]);
cout<<ans<<endl;
return 0;
}
总结:
这次考得一般吧,该水的分都水过来了,有些小问题还是要注意,还有一些解法要总结一下。