洛谷 P2337 【[SCOI2012]喵星人的入侵】

这几天一直在刷插头Dp,写了几道入门题后,觉得还比较水,直到我发现了这一题、、、、

题目大意:给你一个n*m的地图,有些是空地,有些是障碍,还有两个是ST,在给你一个L,代表可以放L个炮台,你要在空地上放炮台或者障碍,来使得S到T存在路径,喵星人会选择伤害最小的一条路径来走,你需要输出喵星人受到的最大伤害 (伤害就是指你被炮台打到的次数,炮台可以打上下左右还有斜的)

首先,很显然最后只会剩一条路径,否则就用障碍堵住就OK了

然后就变出裸裸的插头Dp了,f[k][i][j][s1][s2]代表用了k个炮台,在(i,j)这个位置,插头状态为s1,种类状态为s2,的最大伤害。 显然大多数的s2可以直接推出s1,所以状态数并不多。

因为会MLE,所以第一维要开滚动数组,具体的话看代码。

code:

//开o2
#include<cstdio>
#include<cstring>
#include<cassert>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=10,maxm=25;
int n,m,L,now,last,ans; char map[maxm][maxm]; bool xxx[maxm][maxm];
void init(){
    scanf("%d%d%d",&n,&m,&L);
    for (int i=1;i<=n;++i) scanf("%s",map[i]+1);
    if (n<m){
        for (int i=1;i<=n;++i) for (int j=1;j<=m;++j) if (!xxx[i][j]){
            xxx[i][j]=xxx[j][i]=1;
            swap(map[i][j],map[j][i]);
        }
        swap(n,m);
    }
    for (int i=1;i<=n;++i) for (int j=1;j<=m;++j) if (map[i][j]=='T') map[i][j]='S';
}
struct Tp{int s1,s2,v;};
struct Tsp{
    static const int mod=15271,maxm=10000,Index=(int)(1e9+7);
    int now[mod],pre[maxm],tot; Tp f[maxm];
    void clear(){memset(now,0,sizeof(now)); tot=0;}
    int find(Tp a){
        int tmp=(1LL*a.s1*Index+a.s2)%mod;
        for (int p=now[tmp];p;p=pre[p]) if (f[p].s1==a.s1 && f[p].s2==a.s2){
            if (a.v>f[p].v) f[p].v=a.v; return f[p].v;
        }
        pre[++tot]=now[tmp]; now[tmp]=tot;
        f[tot].s1=a.s1; f[tot].s2=a.s2; return f[tot].v=a.v;
    }
}f[2][21][7];
void addstate(int x,int y,Tp a){ // 插入进f[now][x][y] 
    f[now][x][y].find(a);
}
int cont1[maxn],cont2[maxn]; // cont1代表s1,cont2代表s2
// s1维护插头状态  0,1,2,3分别表示没插头,左插头,右插头,独立插头   s1还要多维护一个插头 
// s2维护种类   0,1,2分别表示障碍,路径,炮台  s2还要多维护一个种类 
void decode(Tp a){
    for (int i=m+1;i>=1;--i) cont1[i]=a.s1&3,a.s1>>=2;
    for (int i=m+1;i>=1;--i) cont2[i]=a.s2&3,a.s2>>=2;
}
Tp encode(int v){
    Tp a; a.v=v; a.s1=0; a.s2=0;
    for (int i=1;i<=m+1;++i) a.s1=(a.s1<<2)+cont1[i];
    for (int i=1;i<=m+1;++i) a.s2=(a.s2<<2)+cont2[i];
    return a;
}
int findr(int res,int x){
    for (;;++x) if (cont1[x]==1 || cont1[x]==2){
        if (cont1[x]==1) ++res; else --res;
        if (!res) return x;
    }
}
int findl(int res,int x){
    for (;;--x) if (cont1[x]==1 || cont1[x]==2){
        if (cont1[x]==1) ++res; else --res;
        if (!res) return x;
    }
}
int find(int a,int x){
    if (a==1) return findr(1,x+1);
    return findl(-1,x-1);
}
void expand(int x,int y,Tp a){ // 不能放炮台的转移 
    decode(a);
    int north=cont1[y],west=cont1[m+1],nw=cont2[m+1];
    if (y==1 && west) return ; if (y==1) nw=0;

    cont2[m+1]=cont2[y]; // 更新左上角的种类 

    int sum=0; for (int i=1;i<=m+1;++i) if (cont1[i]==3) ++sum; // 统计独立插头数量 

    int ad=0; if (y>1 && cont2[y-1]==2) ++ad; if (nw==2) ++ad;
    if (cont2[y]==2) ++ad; if (y<m && cont2[y+1]==2) ++ad; // 统计轮廓线上方八连通的炮台数 

    if (map[x][y]=='.'){ // 作为一个路径,却与起点没有关系
        if ((y==1 || west || cont2[y-1]!=1) && (x==1 || north || cont2[y]!=1)){ // 可以更新 
            cont2[y]=1; // 更新s2 
            if (west && north){ // (1,2) 不可更新,否则成回路了 
                if (west==north){
                    if (west==3){ // (3,3)
                        cont1[y]=0; cont1[m+1]=0; addstate(x,y,encode(a.v+ad));
                    }
                    else{ // (1,1) (2,2)
                        cont1[find(west,y)]=west; cont1[y]=0; cont1[m+1]=0;
                        addstate(x,y,encode(a.v+ad));
                    }
                }
                else if (west==3 || north==3){ // (1,3) (2,3) (3,1) (3,2)
                    cont1[find(min(west,north),y)]=3; cont1[y]=0; cont1[m+1]=0;
                    addstate(x,y,encode(a.v+ad));
                }
                else if (west==2 && north==1){ // (2,1)
                    cont1[y]=0; cont1[m+1]=0; addstate(x,y,encode(a.v+ad));
                }
            }
            else if (west && !north){ // 只能转向,不能升级成独立 
                cont1[y]=0; cont1[m+1]=west; addstate(x,y,encode(a.v+ad));
                cont1[y]=west; cont1[m+1]=0; addstate(x,y,encode(a.v+ad));
            }
            else if (!west && north){ // 同上
                cont1[y]=0; cont1[m+1]=north; addstate(x,y,encode(a.v+ad));
                cont1[y]=north; cont1[m+1]=0; addstate(x,y,encode(a.v+ad));
            }
            else if (!west && !north){ // 可以变出两个左右插头,但是不能变成独立 
                cont1[y]=1; cont1[m+1]=2; addstate(x,y,encode(a.v+ad));
            }
        }
    }

    if (map[x][y]=='S'){ // 作为一个起点,专业生产独立插头
        if ((y==1 || west || cont2[y-1]!=1) && (x==1 || north || cont2[y]!=1)){ // 老规矩
            cont2[y]=1; // 虽然是起点,但也是路径
            if (west && north){ // 可以滚粗了 
            }
            if ((west && !north) || (!west && north)){
                if (max(west,north)==3){ // 是独立插头,合并
                    cont1[y]=0; cont1[m+1]=0; addstate(x,y,encode(a.v+ad));
                }
                else if (sum<=1){ // 不是独立插头,将其升级为独立插头
                    cont1[find(max(west,north),y)]=3;
                    cont1[y]=0; cont1[m+1]=0; addstate(x,y,encode(a.v+ad));
                } 
            }
            if (!west && !north && sum<=1){ // 没有插头,那就直接产生一个
                cont1[y]=0; cont1[m+1]=3; addstate(x,y,encode(a.v+ad));
                cont1[y]=3; cont1[m+1]=0; addstate(x,y,encode(a.v+ad));
            }
        }           
    }

    if (map[x][y]!='S'){  // 不是起点大哥,就可以放障碍啦~
        if (!west && !north){ // 当然要没有插头才可以放障碍
            if (map[x][y]=='.') decode(a),cont2[m+1]=cont2[y];
            cont2[y]=0; cont1[y]=0; cont1[m+1]=0; addstate(x,y,encode(a.v));
        }
    }
}
void expand_pt(int x,int y,Tp a){ // 只能放炮台
    decode(a);
    int north=cont1[y],west=cont1[m+1],nw=cont2[m+1];
    if (north || west || map[x][y]!='.') return; if (y==1) nw=0;

    int ad=0; if (y>0 && cont2[y-1]==1) ++ad; if (nw==1) ++ad;
    if (cont2[y]==1) ++ad; if (y<m && cont2[y+1]==1) ++ad; // 统计路径个数 

    cont2[m+1]=cont2[y]; cont2[y]=2; addstate(x,y,encode(a.v+ad));
}
void clearf(int x){
    for (int i=1;i<=n;++i) for (int j=1;j<=m;++j) f[x][i][j].clear();
}
void work(){
    now=0,last=1; clearf(now); Tsp *x;
    for (int l=0;l<=L;++l){ // now->l  ,  last->l-1
        addstate(0,0,(Tp){0,0,0}); x=&f[now][0][0]; // l更新l   now->now 
        for (int i=1;i<=n;++i) for (int j=1;j<=m;++j){ // x是上一个 
            for (int k=1;k<=x->tot;++k) expand(i,j,x->f[k]);
            x=&f[now][i][j];
        }
        swap(now,last); clearf(now); // now->l+1  , last=l
        if (l!=L){
            x=&f[last][0][0];
            for (int i=1;i<=n;++i) for (int j=1;j<=m;++j){
                for (int k=1;k<=x->tot;++k) expand_pt(i,j,x->f[k]);
                x=&f[last][i][j];
            }
        }
        for (int k=1;k<=x->tot;++k)
            if (x->f[k].s1==0 && x->f[k].v>ans) ans=x->f[k].v;
    }
    printf("%d\n",ans);
}
int main(){
    init();
    work();
    return 0;
}

 

转载于:https://www.cnblogs.com/yihengblog/p/9613338.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值