题意:
给图地图,每个格子有分数,0分数的格子不能走。求最大分数的一条路径
题解:
这题异常坑爹,在换行操作、有过移位的操作中可能会误将最高位的标记为覆盖掉,因此注释的地方是改写了的,注意区别。
/**
积累一个小技巧,队友路径的插头dp大概分为两种大类
一:只给定起点或终点、起点和终点都给定
对于这类要在总的装填转移之前加特判终点和起点的代码
二:没给起点终点
这类特要在代码中加入起点和终点作为决策(加一个变量标记插头个数,并且加入标志位)
起点决策:一般在没有插头的决策中加入(起点是起始点肯定要没插头开始)
终点决策:一般在只有一个插头的决策中加入(因为是路径不是换所以终点肯定要只有一个插头,环另当别论)
注意一点,第一类问题结果ans是加上最后一行,因为终点已经确定,但是第二类终点未确定所以去去最后一行的最值(相当于枚举终点)
*/
#include<iostream>
#include<math.h>
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<vector>
#include<map>
using namespace std;
typedef long long lld;
#define oo 0x3f3f3f3f
#define OO 0x3f3f3f3f3f3f3f3f
#define HASH 10007
#define STATE 1000010
#define MAXD 15
int N,M,ex,ey;
int code[MAXD];
int maze[MAXD][MAXD];
int ch[MAXD];
int num;///独立插头的个数
struct HASHMAP
{
int head[HASH],next[STATE],sizes;
int dp[STATE];
lld state[STATE];
void init()
{
sizes=0;
memset(head,-1,sizeof head);
}
void push(lld st,int ans)
{
int h=st%HASH;
for(int i=head[h];i!=-1;i=next[i])
{
if(st==state[i])
{
if(dp[i]<ans) dp[i]=ans;
return ;
}
}
dp[sizes]=ans;
state[sizes]=st;
next[sizes]=head[h];
head[h]=sizes++;
}
}hm[2];
void decode(int code[],int m,lld st)
{
num=st&7;
st>>=3;
for(int i=m;i>=0;i--)
{
code[i]=st&7;
st>>=3;
}
}
lld encode(int code[],int m)///最小表示法
{
lld st=0;
int cnt=0;
memset(ch,-1,sizeof ch);
ch[0]=0;
for(int i=0;i<=m;i++)
{
if(ch[code[i]]==-1) ch[code[i]]=++cnt;
code[i]=ch[code[i]];
st<<=3;
st|=code[i];
}
st<<=3;
st|=num;
return st;
}
void shift(int code[],int m)///换行 移位
{
for(int i=m;i>0;i--)
code[i]=code[i-1];
code[0]=0;
}
void dpblank(int i,int j,int cur)
{
int left,up;
for(int k=0;k<hm[cur].sizes;k++)
{
decode(code,M,hm[cur].state[k]);
left=code[j-1];
up=code[j];
if(left&&up)///11 -> 00 有上插头和左插头,这种情况下相当于合并两个连通分量
{
if(left!=up)///不存在环才进行dp (合并)
{
code[j-1]=code[j]=0;
for(int t=0;t<=M;t++)
if(code[t]==up)
code[t]=left;
if(j==M)shift(code,M);
hm[cur^1].push(encode(code,M),hm[cur].dp[k]+maze[i][j]);
}
}
else if(left||up)///01 || 10 上插头和左插头恰好有一个,这种情况相当于延续原来的连通分量
{
int temp;
if(left) temp=left;
else temp=up;
if(maze[i][j+1])
{
code[j-1]=0;
code[j]=temp;
hm[cur^1].push(encode(code,M),hm[cur].dp[k]+maze[i][j]);
}
if(maze[i+1][j])
{
code[j-1]=temp;
code[j]=0;
//if(j==M)shift(code,M);///切记不可忘记,换行要shift
//hm[cur^1].push(encode(code,M),hm[cur].dp[k]+maze[i][j]);
hm[cur^1].push(encode(code,j==M?M-1:M),hm[cur].dp[k]+maze[i][j]);
}
///这个决策把这个点作为这个状态的终点
if(num<2)
{
num++;
code[j-1]=code[j]=0;
//if(j==M)shift(code,M);
//hm[cur^1].push(encode(code,M),hm[cur].dp[k]+maze[i][j]);
hm[cur^1].push(encode(code,j==M?M-1:M),hm[cur].dp[k]+maze[i][j]);
}
}
else///没有上插头和左插头,有下插头和右插头,相当于构成一个新的连通块
{
///这里有三个决策,第一个很正场必须有,建立性的插头,第二决策不建立插头(不走这条路呗),决策三看代码
code[j]=code[j-1]=0;
hm[cur^1].push(encode(code,j==M?M-1:M),hm[cur].dp[k]);
///不是起点直接增加插头
if(maze[i][j+1]&&maze[i+1][j])
{
code[j]=code[j-1]=13;
hm[cur^1].push(encode(code,M),hm[cur].dp[k]+maze[i][j]);
}
///因为没有插头,所以可以从这里作为这个状态起点,于是加入一个插头
if(num<2)
{
num++;
if(maze[i][j+1])
{
code[j-1]=0;
code[j]=13;
hm[cur^1].push(encode(code,M),hm[cur].dp[k]+maze[i][j]);
}
if(maze[i+1][j])
{
code[j]=0;
code[j-1]=13;
//hm[cur^1].push(encode(code,j==M?M-1:M),hm[cur].dp[k]+maze[i][j]);
if(j==M)shift(code,M);
hm[cur^1].push(encode(code,M),hm[cur].dp[k]+maze[i][j]);
}
}
}
}
}
void dpblock(int i,int j,int cur)
{
for(int k=0;k<hm[cur].sizes;k++)
{
decode(code,M,hm[cur].state[k]);
code[j-1]=code[j]=0;
if(j==M)shift(code,M);
hm[cur^1].push(encode(code,M),hm[cur].dp[k]);
}
}
void init()
{
memset(maze,0,sizeof maze);
scanf("%d %d",&N,&M);
for(int i=1;i<=N;i++)
for(int j=1;j<=M;j++)
scanf("%d",&maze[i][j]);
}
void solve()
{
int cur=0;
int ans=0;
hm[cur].init();
hm[cur].push(0,0);
for(int i=1;i<=N;i++)
for(int j=1;j<=M;j++)
{
hm[cur^1].init();
if(maze[i][j])dpblank(i,j,cur);
else dpblock(i,j,cur);
if(maze[i][j]>ans)
ans=maze[i][j];
cur^=1;
}
for(int i=0;i<hm[cur].sizes;i++)
if(hm[cur].dp[i]>ans)
ans=hm[cur].dp[i];///因为起点终点未确定,相遇枚举终点来确定最大值
printf("%d\n",ans);
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
init();
solve();
}
return 0;
}
/**
*/