新版方格取数
【问题描述】
T博士的小儿子小T最近在玩一个游戏。
在一个m行n列的方格中有m*n个数,游戏规则如下:
先在方格边缘取走一个数,以此格为起点,下一步可向该格四个方向中未取数的方格前进,取走该方格的数并继续按如上规则取数。
如果某次取数恰好取到方格的边缘,则下一步可选择离开方格另取入口进入方格,当然也可以选择按上述规则取数。
游戏在小T取完方格内所有数或无法继续取方格内剩下的任何一个数的时候结束。
游戏有这样的得分规则:若方格内的某数j是方格内所有数中第i个取走的数,此次取数的得分为i*j。
小T最后的得分为游戏结束时他各次取数的得分之和。
小T想知道他所能取得的最大得分。
注意:已经取走数的方格不能再次取数或经过。
【输入格式】
输入文件newfgqs.in的第一行是两个正整数m和n,表示方格为m行n列。
第2到m+1行,每行为n个非负整数(注意可能为0),是方格里的数,保证这些数都小于100000(10万)。
【输出格式】
输出文件为newfgqs.out,只要输出一行,为小T能取得的最大得分。
【样例输入1】
3 3
1 2 3
8 9 4
7 6 5
【样例输出1】
285 (依次取1,2,3,4,5,6,7,8,9)
【样例输入2】
3 3
0 0 0
0 1 0
0 0 0
【样例输出2】
9 (周围绕一圈,第9次取1)
【数据范围】
对于50%的数据,满足m*n<=9。
对于100%的数据,满足m*n<=16。
这题如果用普通的搜索可以拿部分分,正解虽然也是搜索,但它是记忆化搜索。
先从最朴素的方法讲起,
深搜dfs,从边缘开始向周围四个方向取,取到边缘再做出选择,四个方向继续深搜,或重复开始的过程到边缘进行取数。
由贪心的思想,先取小的再取大的分数更大,因为越往后来取倍数越多,我们当然希望让大数乘以大倍数;所以,当行数或列数小于2时,所有数都可随时取,直接由贪心可得应该从小到大取,于是60分的代码就出来了:
#include
using namespace std;
int n,m,ans;
int map[17][17];
bool b[17][17]; //判断是否取过
void dfs(int x,int y,int now,int turn) //x表示列,y表示行,now表示当前分数,turn表示取的个数
{
bool flag=false;
b[x][y]=true;
if (x+1<=n&&!b[x+1][y]) //向下一列
{
dfs(x+1,y,now+map[x][y]*turn,turn+1);
flag=true;
}
if (y+1<=m&&!b[x][y+1]) //向下一行
{
dfs(x,y+1,now+map[x][y]*turn,turn+1);
flag=true;
}
if (x-1>0&&!b[x-1][y]) //向上一列
{
dfs(x-1,y,now+map[x][y]*turn,turn+1);
flag=true;
}
if (y-1>0&&!b[x][y-1]) //向上一行
{
dfs(x,y-1,now+map[x][y]*turn,turn+1);
flag=true;
}
if (x==1||x==m||y==1||y==n) //边缘的时候可以重新选择入口
{
for (int i=1;i<=n;i++)
{
if (!b[i][1])
{
dfs(i,1,now+map[x][y]*turn,turn+1);
flag=true;
}
if (!b[i][m])
{
dfs(i,m,now+map[x][y]*turn,turn+1);
flag=true;
}
}
for (int j=1;j<=m;j++)
{
if (!b[1][j])
{
dfs(1,j,now+map[x][y]*turn,turn+1);
flag=true;
}
if (!b[n][j])
{
dfs(n,j,now+map[x][y]*turn,turn+1);
flag=true;
}
}
}
b[x][y]=false;
if (!flag) //已经取完的情况
if (now+map[x][y]*turn>ans)
ans=now+map[x][y]*turn;
}
int main()
{
freopen("newfgqs.in","r",stdin);
freopen("newfgqs.out","w",stdout);
scanf("%d%d",&m,&n);
for (int i=1;i<=m;i++)
for (int j=1;j<=n;j++)
scanf("%d",&map[i][j]);
if (m<=2||n<=2) //能贪心的时候特判
{
int x=0;
int y=0;
for (int i=1;i<=m*n;i++)
{
int min=2147483647;
for (int j=1;j<=m;j++)
for (int k=1;k<=n;k++)
if (map[j][k]<min&&!b[j][k])
{
min=map[j][k];
x=j;
y=k;
}
b[x][y]=true;
ans+=min*i;
}
printf("%d",ans);
}
else
{
for (int i=1;i<=n;i++)
{
dfs(i,1,0,1); //第一行开始取
dfs(i,m,0,1); //最后一行开始取
}
for (int j=1;j<=m;j++)
{
dfs(1,j,0,1); //第一列开始取
dfs(n,j,0,1); //最后一列开始取
}
printf("%d",ans);
}
return 0;
}
下面讲讲AC的方法,
如果学过状压dp,就非常容易理解了;首先,每个数只有取与不取两种状态,n*m<=16,这里其实给了暗示:如果用二进制表示方格的状态,最大是每种状态16位二进制数!!用0表示未取,1表示已取,那么方格的状态就用二进制数表示出来了,16位二进制数最多可以有2^16-1个二进制数,即2^16-1种状态,记忆化搜索,就是通过更新每种状态的最优值,并且把当前未达最优值的同种状态排除以提高效率排除以提高效率。
举个例子,对于一个3*3的方阵,初始时全部未取,我们把它转化成一个二进制数:000 000 000;至于怎么把二维坐标对应成一维的位置,可以这样:label=(x-1)*m+y,其中x是当前坐标的行数,y是当前坐标的列数,m是总列数,这个比较好理解,可以自己拿几个坐标转换一下。于是,当(2,1)被取时,我们就把第(2-1)*3+1即第4位改为1就可以了,这样就变成了000 100 000(当然这里是从低位到高位的顺序)。至于判断当前位置是否已取,就判断这一位是否为1。修改和判断,分别用到或运算和与运算,具体方法也比较容易想,详见代码。
当然,我们不可能用一个二进制数作为状态,应转为十进制;我们用一个数组作为状态最优值,这时又出现了一个问题:同一个状态,有可能当前取数的位置不一样;亦即是说,同样取了四个数,因为顺序不同,可能一个当前取到了(1,1),一个却在(3,2),故这个状态不应该是一维,而应该是二维的,第一维是取数状况,第二维则对应当前坐标(坐标是一维的,前面讲过了二维转一维的方法)。于是,f[i][j]表示在第i取数情况第j位置状态的当前最优值,在dfs时若当前dfs同一状态得分大于f则更新f,否则退出当前dfs。
代码:
#include
#include
using namespace std;
int n,m,ans;
int map[17][17];
bool b[17][17];
int f[65536][17];
void edge(int now,int sum,int turn);
void dfs(int now,int x,int y,int sum,int turn) //x行y列,now取数情况,sum得分,turn取数个数
{
int label=(x-1)*n+y;
if (sum>f[now][label]||f[now][label]==0) f[now][label]=sum; else return; //注意后面||f[now][label]==0不可省略!因为有可能方格中有0使sum==0,但这时0仍然要取
if (sum>ans) ans=sum;
if (x+1<=m&&(now&(1<<x*n+y-1))==0) dfs(now+(1<<x*n+y-1),x+1,y,sum+map[x+1][y]*turn,turn+1);
if (y+1<=n&&(now&(1<<(x-1)*n+y))==0) dfs(now+(1<<(x-1)*n+y),x,y+1,sum+map[x][y+1]*turn,turn+1);
if (x-1>=1&&(now&(1<<(x-2)*n+y-1))==0) dfs(now+(1<<(x-2)*n+y-1),x-1,y,sum+map[x-1][y]*turn,turn+1);
if (y-1>=1&&(now&(1<<(x-1)*n+y-2))==0) dfs(now+(1<<(x-1)*n+y-2),x,y-1,sum+map[x][y-1]*turn,turn+1);
if (x==1||y==1||x==m||y==n) edge(now,sum,turn);
}
void edge(int now,int sum,int turn) //取到边缘
{
for (int i=1;i<=m;i++)
{
if ((now&(1<<(i-1)*n))==0) dfs(now+(1<<(i-1)*n),i,1,sum+map[i][1]*turn,turn+1);
if ((now&(1<<i*n-1))==0) dfs(now+(1<<i*n-1),i,n,sum+map[i][n]*turn,turn+1);
}
for (int j=1;j<=n;j++)
{
if ((now&(1<<j-1))==0) dfs(now+(1<<j-1),1,j,sum+map[1][j]*turn,turn+1);
if ((now&(1<<(m-1)*n+j-1))==0) dfs(now+(1<<(m-1)*n+j-1),m,j,sum+map[m][j]*turn,turn+1);
}
}
int main()
{
freopen("newfgqs.in","r",stdin);
freopen("newfgqs.out","w",stdout);
scanf("%d%d",&m,&n);
for (int i=1;i<=m;i++)
for (int j=1;j<=n;j++)
scanf("%d",&map[i][j]);
if (m<=2||n<=2)
{
int x=0;
int y=0;
for (int i=1;i<=m*n;i++)
{
int min=2147483647;
for (int j=1;j<=m;j++)
for (int k=1;k<=n;k++)
if (map[j][k]<min&&!b[j][k])
{
min=map[j][k];
x=j;
y=k;
}
b[x][y]=true;
ans+=min*i;
}
printf("%d",ans);
}
else
{
edge(0,0,1);
printf("%d",ans);
}
return 0;
}