神奇游乐园

题目描述

经历了一段艰辛的旅程后,主人公小 P 乘坐飞艇返回。在返回的途中,小 P 发现在漫无边际的沙漠中,有一块狭长的绿地特别显眼。往下仔细一看,才发现这是一个游乐场,专为旅途中疲惫的人设计。

娱乐场可以看成是一块大小为 n\times mn×m 的区域,且这个 n\times mn×m 的区域被分成 n\times mn×m 个小格子,每个小格子中就有一个娱乐项目。

然而,小 P 并不喜欢其中的所有娱乐项目,于是,他给每个项目一个满意度。满意度为正时表示小 P 喜欢这个项目,值越大表示越喜欢。为负时表示他不喜欢,这个负数的绝对值越大表示他越不喜欢。为 00 时表示他对这个项目没有喜恶。

小 P 决定将飞艇停在某个小格中,然后每步他可以移动到相邻的上下左右四个格子的某个格子中。

小 P 希望找一条路径,从飞艇所在格出发,最后又回到这个格子。

小 P 有一个习惯,从不喜欢浪费时间。因此,他希望经过每个格子都是有意义的:他到一个地方后,就一定要感受以下那里的惊险和刺激,不管自己是不是喜欢那里的娱乐项目。而且,除了飞艇所在格,其他的格子他不愿意经过两次。小 P 希望自己至少要经过四个格子。

在满足这些条件的情况下,小 P 希望自己玩过的娱乐项目的满意度之和最高。你能帮他找到这个最高的满意度之和吗?

输入格式

第一行为两个正整数 nn 和 mm,表示游乐场的大小为 n\times mn×m。

接下来的 nn 行,每行有 mm 个整数,第 (i + 1)(i+1) 行的第 jj 个数表示游乐场的第 ii 行第 jj 列的小格子中的娱乐项目的满意度 a_{i,j}ai,j​。同一行的两个整数之间用空格隔开。

输出格式

仅一行为一个整数,表示最高的满意度之和。

输入输出样例

输入 #1

4 4
100 300 -400 400
-100 1000 1000 1000
-100 -100 -100 -100
-100 -100 -100 1000

输出 #1

4000

题解

思路

先理解一下题意:实际上就是要你求这个棋盘总权值最大的一个回路

看到这个数据范围,还有回路处理,就想到使用插头dp来做了

观察一下发现,这道题因为都是回路,所以联通块上方的插头一定两两配对,可以使用括号序列代替最小表示法

分情况讨论一下

情况一:当前格子上方和左方都没有插头

这种时候可以继续,也可以给当前格子加一个下插头一个右插头,相当于一个新的联通分量

情况二:上方有一个下插头,左边没有

这时有两个决策:可以向右转,也可以继续向下,操作就是分别给这个格子一个右插头或者一个下插头

注意此时新插头的括号类型和原来的那个插头相同(画个图可以理解一下)

情况三:左边有一个右插头,上面没有

同情况二,转弯或者直走

情况四:都有插头,而且两个插头是同一括号

这种情况,我们可以将这两个插头合并,在当前格子把这条路径封闭了

但是这里需要考虑一下其他的插头

我们去掉了两个相同的括号,就需要把另外一个括号反过来配对才行

比如当前的括号序列是 ((##()#(##))##),加粗的是我们要合并的两个括号,那么这两个)变成#以后,它们原来匹配的左括号(就失配了,需要其中一个(右边的那个)左括号变成右括号,两个重新配对

也就是((##()#())##)变成((##()#(####)变成((##()#)####)

当然也可以画个图理解一下,两条路径相当于是绕了圈接起来了

这个操作需要扫一遍整个序列,是O\left(n\right)O(n)的,当然也可以预处理变成O\left(1\right)O(1)

情况五:都有插头,且两个是)(

这时候直接合并就好了

情况六:都有插头,而且两个是()

这种时候,只有在整个轮廓线上只剩下这两个插头时才能合并,路径完全封闭,得到了一个答案

状态数略多,可以滚动数组+哈希处理

分类讨论的时候注意可不可以这么做(需要判断下一个格子是否为障碍)

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define hash deep_dark_fantasy
#define inf 1e9
using namespace std;
inline int read(){
    int re=0,flag=1;char ch=getchar();
    while(ch>'9'||ch<'0'){
        if(ch=='-') flag=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9') re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
    return re*flag;
}
int n,m,x[150][150],cur,pre,ex,ey;
int st[2][300010];ll ans[2][300010],re;
int tot[2],bit[20],state[300010],st_tot,hash=300000;
struct edge{
    int to,next;
}a[300010];
void insert(int sta,ll val){//往哈希表里插入一个状态,顺便更新答案
    int p=sta%hash,i;
    for(i=state[p];i;i=a[i].next){
        if(st[cur][a[i].to]==sta){
            ans[cur][a[i].to]=max(ans[cur][a[i].to],val);return;
        }
    }
    tot[cur]++;
    a[++st_tot].to=tot[cur];
    a[st_tot].next=state[p];
    state[p]=st_tot;st[cur][tot[cur]]=sta;ans[cur][tot[cur]]=val;
}
void dp(){
    int i,j,k,l,now,down,right;ll val;re=-inf;
    cur=0;tot[cur]=1;ans[cur][1]=0;st[cur][1]=0;
    for(i=1;i<=n;i++){
        for(j=1;j<=tot[cur];j++) st[cur][j]<<=2;
        for(j=1;j<=m;j++){
            pre=cur;cur^=1;tot[cur]=0;st_tot=0;memset(state,0,sizeof(state));
            for(k=1;k<=tot[pre];k++){
                now=st[pre][k];val=ans[pre][k];
                right=(now>>bit[j-1])%4;down=(now>>bit[j])%4;
                if(!down&&!right){//新建联通分量,加一个下插头一个右插头
                    insert(now,val);
                    if(j!=m) 
                        insert(now+(1<<bit[j-1])+((1<<bit[j])<<1),val+x[i][j]);
                }
                if(down&&!right){//延续下插头
                    insert(now-down*(1<<bit[j])+down*(1<<bit[j-1]),val+x[i][j]);
                    if(j!=m)insert(now,val+x[i][j]);
                }
                if(right&&!down){//延续右插头
                    insert(now,val+x[i][j]);
                    if(j!=m) 
                        insert(now+right*(1<<bit[j])-right*(1<<bit[j-1]),val+x[i][j]);
                }
                if(right==1&&down==1){//合并两个左括号
                    int cnt=1;
                    for(l=j+1;l<=m;l++){
                        if((now>>bit[l])%4==1) cnt++;
                        if((now>>bit[l])%4==2) cnt--;
                        if(!cnt){
                            insert(now-(1<<bit[l])-(1<<bit[j])-(1<<bit[j-1]),val+x[i][j]);
                            break;
                        }
                    }
                }
                if(right==2&&down==2){//合并两个右括号
                    int cnt=1;
                    for(l=j-2;l>=0;l--){
                        if((now>>bit[l])%4==1) cnt--;
                        if((now>>bit[l])%4==2) cnt++;
                        if(!cnt){
                            insert(now+(1<<bit[l])-((1<<bit[j])<<1)-((1<<bit[j-1])<<1),val+x[i][j]);
                            break;
                        }
                    }
                }
                if(right==2&&down==1){//合并)(
                    insert(now-((1<<bit[j-1])<<1)-(1<<bit[j]),val+x[i][j]);
                }
                if(right==1&&down==2){//合并(),统计答案
                    if((now==(1<<bit[j-1])+((1<<bit[j])<<1))&&(val+x[i][j]>re)){
                        re=val+x[i][j];
                    }
                }
            }
        }
    }
}
int main(){
    int i,j;
    n=read();m=read();
    for(i=1;i<=10;i++) bit[i]=(i<<1);
    for(i=1;i<=n;i++) for(j=1;j<=m;j++) x[i][j]=read();
    dp();
    printf("%lld",re);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值