洛谷P2566[SCOI2009]围豆豆

题目描述:

Input第一行两个整数N和M,为矩阵的边长。 第二行一个整数D,为豆子的总个数。 第三行包含D个整数V1到VD,分别为每颗豆子的分值。 接着N行有一个N×M的字符矩阵来描述游戏矩阵状态,0表示空格,#表示障碍物。而数字1到9分别表示对应编号的豆子。

Output仅包含一个整数,为最高可能获得的分值。

Sample Input

3 8 3

30 -100 30

00000000

010203#0

00000000

Sample Output

38

数据范围:

50%的数据满足1≤D≤3。
100%的数据满足1≤D≤9,1≤N, M≤10,-10000≤Vi≤10000。

思路分析:

  我们一看到这么小的数据应该能想到要用DP,关键是怎么DP;

我们先说一下如何判断一个豆豆被围住,这需要用到“射线法”;

我们可以举几个例子来说,如上图所示(画得这么丑不要介意)我们发现,当从‘豆豆’向右延伸出一条直线时,如果与我们的路径有奇数个交点,那么豆豆将在路径所围成的圈内,否则在圈外,但还有一种特殊情况需要考虑:

 

 

 向右的直线和我们的路径有交时,会出现有偶数个交点(如图中)而在路径内的情况(我们计算交点数的方式是往下走时若过豆豆的纵坐标则记一次,所以中间横在紫线上的点不算,上图只有一左一右两个交点)

解决了判断的问题,我们便可以思考如何DP了

大致思路是,考虑Spfa的做法,从一个点出发,遍历所有与他相连的点,若合法则压入队内,一个点只走一次,知道队列为空,统计答案即可。如何找到与已知点相邻的点的坐标呢,我们在学栈时曾做过一道叫做细胞个数的题,我们可以从那里得到启发,即开两个数组,分别为

f1[4]={1,-1,0,0},f2[4]={0,0,1,-1},之后让已知点的坐标加上对应的值即可修改其位置。若i<2(即已知点上下移动),统计一下是否有点向右的直线与它有奇数个交点,有的话则在原来已知点的基础上加上这个豆豆的权值;我们用dp第一位和第二位表示点的坐标,第三位表示状态,如果一个点的一个状态合法(即vis为true,遍历过)则拿他和当前最大答案比较,最后输出最大答案。其他细节详见注释。

附上代码:

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<queue>
 5 #include<map>
 6 using namespace std;
 7 struct Node2{  //开一个结构体方便用队列存储 
 8     int x,y,dis;
 9     Node2(int a,int b,int c){
10         x=a;y=b;dis=c;
11     }
12 };
13 int vis[11][11][1050],a[11][11],val[11];  
14 int n,m,cnt;
15 int dp[11][11][1050],x0[11],y0[11],ans=0;
16 //vis表示当前点当前状态是否计算过,val记录每个豆豆的权值,
17 //x0,y0分别存储豆豆的横,纵坐标,温馨提示:千万不要定义y1
18 //a数组记录棋盘,dp[i][j][k]记录坐标为(i,j)的点状态为k时权值. 
19 int f1[4]={1,-1,0,0}; //用于已知点的移动 
20 int f2[4]={0,0,1,-1};
21 void Spfa(int x,int y){
22     memset(vis,0,sizeof(vis));  //需要跑多次,记得初始化 
23     memset(dp,0,sizeof(dp));
24     dp[x][y][0]=0;
25     queue<Node2>q;
26     q.push(Node2(x,y,0));
27     int tx,ty,tz,tdp,cross;
28     //tx,ty,tz表示新点的横,纵坐标及状态
29     //tdp表示当前状态权值,cross表示豆豆横坐标 
30     while(!q.empty()){
31         Node2 t=q.front();q.pop();
32         for(int i=0;i<4;++i){
33             tx=t.x+f1[i];ty=t.y+f2[i];
34             if(tx<=0||ty<=0||tx>n||ty>m||a[tx][ty]!=0) //判断合法 
35                 continue;
36             tz=t.dis;tdp=dp[t.x][t.y][t.dis]-1;//由于又走了一步,分数要减一 
37             if(i<2){  //判断是否加分 
38                 cross=min(tx,t.x);
39                 for(int j=1;j<=cnt;++j){
40                     if(x0[j]!=cross||y0[j]>ty) //和豆豆的向右只限无交点 
41                         continue;
42                     if(tz & (1<<(j-1))) tdp-=val[j]; //如果原来加过,再过一次时偶数变奇数,要减去 
43                     else tdp+=val[j];    //反之加上 
44                     tz=tz^(1<<(j-1));   //经过次数加一次 
45                 }
46             }
47             if(!vis[tx][ty][tz]){ //入队,准备下一次循环 
48                 dp[tx][ty][tz]=tdp;
49                 q.push(Node2(tx,ty,tz));
50                 vis[tx][ty][tz]=1;
51             }
52          }    
53     }
54     for(int i=0;i<(1<<cnt);++i)  //统计当前点所有状态的答案 
55         if(vis[x][y][i])
56             ans=max(ans,dp[x][y][i]);
57 }
58 int main(){
59     //freopen("a.txt","r",stdin);
60     scanf("%d%d%d",&n,&m,&cnt);
61     for(int i=1;i<=cnt;++i)
62         scanf("%d",&val[i]);
63     for(int i=1;i<=n;++i)
64     for(int j=1;j<=m;++j){
65         char c=' ';
66         while(c==' '||c=='\n')
67             scanf(" %c",&c);
68         if(c=='#') a[i][j]=-1; //构建棋盘 
69         if(c>='0'&&c<='9'){
70             x0[c-'0']=i;y0[c-'0']=j; //存储豆豆 
71             a[i][j]=c-'0';
72         }
73     }
74     for(int i=1;i<=n;++i)
75     for(int j=1;j<=m;++j){
76         if(a[i][j]==0)
77             Spfa(i,j);//从每个点出发跑一遍 
78     }
79     printf("%d\n",ans);
80     return 0;
81 }
View Code

 

posted @ 2020-04-15 11:49  19502-李嘉豪  阅读(129)  评论(0)    收藏  举报