10.7离线赛

来源:互联网 发布:单片机呼吸灯 编辑:程序博客网 时间:2024/05/29 18:47

预分240 实分240

一、S数
应得100 实得100

题意:在[l,r]区间内求满足S(x*x)=S(x)^2的数的个数,S(x)=x各位数字和

数据:对于80%,r∈[1,1e5]
对于100%,r∈[1,1e9],l<=r

80%的只需要一个循环打过去就可以了。

然后把表打出来,发现满足的x只由0123四个数字组成,按照这四个数字造数,共4^9=262144个,dfs一下就过了。

当然这不是正解。发现S(x*x)的最大值是18 * 9,那S(x)的最大值约为12,然后按照各位数字和为12去dfs即可。

二、黑客入侵
应得100 实得100

题意:有n个坦克在一条直线,每个坦克有一个坐标和射程,从右往左轮流开炮,若还在就开炮打毁所有射程内的坦克。有一个备用坦克可以摧毁从右往左连续的任意数量的坦克。求最少损失量。

数据:对于70%,n∈[1,2000]
对于100%,n∈[1,200000],射程和位置∈[1,1e9]

对于70%只要枚举一下最右边要打毁几座坦克,然后再循环一遍,这样是O(N^2)。
正解dp

我写的是两维。
dp[i][0] 表示第i个坦克被摧毁时最多存活几个
dp[i][1] 表示第i个坦克打别人时最多存活几个
那么dp[i][0] 就是从dp[i-1][0] 和 dp[i-1][1] 中取最大值
dp[i][1] 就是dp[x-1][1]+1 x是不会被i打到的最靠右的坦克,二分一下就行
为什么是dp[x-1][1] 呢?因为只要活着就必须打,按照题意。

另一种是用一维的。这样就不用管到底打还是没打,转移就是dp[i]=i-x+dp[x-1]。(来自于JRH大佬)
x与我的是一样的。但是注意这里的dp是最少的摧毁个数,而不是存活个数
连个代码均贴上

二维的:

#include<bits/stdc++.h>#define M 200005using namespace std;int A[M],S[M],dp[M][2],n;int main(){    scanf("%d",&n);    for(int i=1;i<=n;i++)scanf("%d%d",&A[i],&S[i]);    for(int i=1;i<=n;i++){        int x=lower_bound(A,A+1+n,max(0,A[i]-S[i]))-A;//二分查找,手打比函数快,这里为了方便,能A就行        dp[i][0]=max(dp[i-1][0],dp[i-1][1]);        dp[i][1]=dp[x-1][1]+1;    }    printf("%d\n",min(n-dp[n][0],n-dp[n][1]));    return 0;}

一维的

#include<cstdio>#include<algorithm>using namespace std;int pos[200005],A[200005],dp[200005],n,ans=2e9;int main() {    scanf("%d",&n);    for(int i=1; i<=n; i++) {        scanf("%d %d",&pos[i],&A[i]);        int x=lower_bound(pos+1,pos+1+i,pos[i]-A[i])-pos;        A[i]=x;    }    for(int i=1; i<=n; i++) {        dp[i]=i-A[i]+dp[A[i]-1];        if(dp[i]+n-i<ans)ans=dp[i]+n-i;    }    printf("%d",ans);    return 0;}

三、炮兵阵地
应得40 实得40

题意:司令部的将军们打算在N*M的网格地图上部署他们的炮兵部队。一个N*M的地图由N行M列组成,地图的每一格可能是山地(用“H” 表示),也可能是平原(用“P”表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:
这里写图片描述
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。

数据:如图所示
这里写图片描述

来源:NOI2001复赛

状压dp的一道入门题(虽然我想了很久)。考试时我想了一个O(N*M)的完全错误代码,还过了测试数据,发现是凑出来的……dfs里加了一个cnt计算运行次数,若cnt>=100000000(1e7)就直接输出。这是dfs水分利器。

正解:dp[i][j][k] 表示第i行用第j种方案,第i-1行用第k种方案时的最大炮兵放置数。这样看肯定是一头雾水。下面向详细分析:

1、方案数:因为列数都小于10,那就可以用位运算把哪些点放炮兵,哪些不放表示出来。2^10是1024,这样数组岂不是开不下?实际上只有60多种情况,因为很多情况都会重复或者不符合,到时程序有注释。

2、放置数:这是在预处理方案数时对于每一个方案的炮兵放置数量。

3、炮兵攻击范围是两格,为什么只要枚举i-1层,而不用i-2层呢?其实是要的,但是没有放在dp下标里。在转移时要多加一条判断。

还要注意因为转移时要用到i-1和i-2层,所以第一层和第二层是需要单独处理的。相邻三层的方案不能一样,否则会互相打到。

#include<bits/stdc++.h>using namespace std;char sca[105];int A[105][15],num[105],dp[105][65][65],s1[105],Gs,G[105];int main(){    int n,m;    scanf("%d%d",&n,&m);    for(int i=0;i<n;i++){        scanf("%s",sca);        for(int j=0;j<m;j++)            if(sca[j]=='H')num[i]+=(1<<j);//把高地放在相应的位置    }    for(int i=0;i<(1<<m);i++){        if(i&i<<1)continue;        if(i&i<<2)continue;        int now=i;        while(now){            s1[Gs]+=now&1;//此状态的炮兵个数             now>>=1;        }        G[Gs++]=i;//状态    }    for(int i=0;i<Gs;i++)//第一行的dp值(这里是0)         if(!(G[i]&num[0]))dp[0][i][0]=s1[i];    for(int i=0;i<Gs;i++)//第二行的dp值(这里是1)         if(!(G[i]&num[1]))            for(int j=0;j<Gs;j++){                if((G[j]&G[i])||G[j]&num[0])continue;第二行与第一行方案数不能相同。第二行这个点不是山                dp[1][i][j]=max(dp[1][i][j],dp[0][j][0]+s1[i]);//第一行的方案数加上第二行的方案数            }    for(int i=2;i<n;i++)        for(int j=0;j<Gs;j++){//第i行的方案            if(G[j]&num[i])continue;//第i行这个点不是山            for(int k=0;k<Gs;k++){//第i-1行的方案                 if((G[j]&G[k])||(G[k]&num[i-1]))continue;//第i行与第i-1行的方案数要不一样                for(int l=0;l<Gs;l++){//第i-2行的方案                    if((G[k]&G[l])||(G[l]&G[j])||(G[l]&num[i-2]))continue;                    //第i-2与第i-1行方案数不一样,第i-2与第i行方案不一样,第i-2行不是山                    dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][l]+s1[j]);                }            }        }    int ans=0;    for(int i=0;i<Gs;i++)        for(int j=0;j<Gs;j++)            if(!(G[i]&G[j]))ans=max(ans,dp[n-1][i][j]);    printf("%d\n",ans);    return 0;}

实际上很简单的一道题,只是想得太浅了。

原创粉丝点击