Contest1078 - NOIP模拟赛190128
A:MZOJ1355: 迷宫
思路
1.n在1~25之间----->尽量不用DFS,需要用到DP
2.要是普通的走迷宫那还好,DP方程很好想,但是这道题比较奇葩的是要维护两个值——距离&&条数,而二维DP只能维护其中其一,所以我们选用三维DP——f(t,i,j)表示从起点到i,j这个点的距离为t路径的条数(注意:t不一定是最小的哦)
3.状态设计及转移:
father的t值等于他儿子的t-1;
father的条数等于他儿子的条数之和
即:
4.几个小问题。
起点如何设计? f(0,startx,starty)=1即可
如何设计顺序? t在最外层且从1-n*n,其他顺序没什么要求,因为初值只有起点才为1,要是其他乱走,f值也为0,无影响。
如何保证t值最小? 在最后找答案的时候按t值从小到达的顺序遍历,找到第一个f有值的时候说明此时的t最小,然后输出即可。
code
/* NAME:EPEILONCXL;
LANG:C++;
TIME:8:20
PROB:A
\`,"" ,'7"r-..__/ \
,'\/`, ,',',' _/ \
/ \/ 7 / / ( \ |
J \/ j L______\ / |
L __JF"""/""\"\_, /
L,-"| O| | O | L_ _/
F \_ / \__/ `- /|
.-' `""" ,' | _..====.._
\__/ r"_ A ,' _..---.._`,
`-.______,,-L// / \ ___,' ,'_..:::.. `,`,
/ / / / 7" `-<""=:' '':. \ \
| <,' / F . i , \ `, :C X L
| \,' / >X< | \ :| | X
\ `._/ ' ! ` | C :| | C
\ \ / | :C C |
__>-.__ __,-\ | | X X |
/ /| | \ \ \_ | 'L L |
/ __/ | | \ \ \ X./ / L
/ | | | | | | L/ / ,'
| \ | | | | | // / _,'
_________| | | | | | | //_/-'
\ __ | / | | / | | /="
\\ \_\ \__,' \ / |/ / |
\\\_____________\/ C___X L
\\| EPSILONCXL |_____/ (_____/
\/TEST:19.1.28A/
*/
#include <bits/stdc++.h>
using namespace std;
const int maxn=30;
int mapp[maxn][maxn];
int f[maxn*maxn][maxn][maxn];
int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};
int n,endx,endy,startx,starty;
void init()
{
freopen("A.in","r",stdin);
}
void readdata()
{
char c;
scanf("%d\n",&n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
scanf("%c",&c);
if(c!='X')mapp[i][j]=0;
if(c=='X')mapp[i][j]=1;
if(c=='S'){startx=i;starty=j;}
if(c=='E'){endx=i;endy=j;}
}
scanf("\n");
}
}
void work()
{
f[0][startx][starty]=1;
for(int t=1;t<=n*n;t++)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(!mapp[i][j])
{
for(int k=0;k<4;k++)
{
int nx=i+dx[k],ny=j+dy[k];
if(!mapp[nx][ny] && nx>0 && ny>0 && nx<n+1 && ny<n+1)
{
f[t][i][j]=f[t][i][j]+f[t-1][nx][ny];
}
}
}
}
}
}
for(int t=1;t<=n*n;t++)
{
if(f[t][endx][endy])
{
printf("%d\n%d",t,f[t][endx][endy]);
break;
}
}
}
int main()
{
init();
readdata();
work();
return 0;
}
B:1356: 最大数列
题意
从一段区间中选取两段区间,使区间和最大,但是这两段区间不能有交集。
思路
dp[i]表示以i节点为结尾的最大值,不难得出递推方程式:
难点
如何维护两端不相交的区间的最大值
两遍DP
第一遍DP正向从1~n,维护出dp的值,表示从正方向上以i节点结尾的最大值
第二遍DP反向维护出一个反向的最大区间
下面主要讲一讲如何维护:
以样例为例:
5
-5 9 -5 11 20
1.第一遍DP:正向从1~n,维护出dp的值
void dp_head()
{
1 memset(dp,0,sizeof(dp));
2 for(int i=1;i<=n;i++) dp[i]=max(dp[i-1]+a[i],a[i]);
}
维护出dp[i]的值这个都没有什么好多要说的
eg:
dp 0 1 2 3 4 5
0 -5 9 4 15 35
最主要是反向维护:
2.第二遍DP:反向维护出一个反向的最大区间
几个变量的意思:
ans:最终两端区间的和的最大值
flag:当前i的后缀和。也就是相当与一个dp数组
sum:从n~1反向遍历所寻找的1个区间最大值
inf:一个极小值
void dp_back()
{
1 ans=inf,flag=0,sum=inf;
2 for(int i=n;i>1;i--)
3 {
4 if(flag>0)flag+=a[i];
5 else flag=a[i];
6
7 if(flag>sum)sum=flag;
8 if(sum+dp[i-1]>ans)ans=sum+dp[i-1];
9 }
}
代码解释
2~5:从右往左遍历每一个点,并且维护出当前点i至n的区间最大值(也就是dp的任务,只不过dp是从左往右的顺序)
7:更新sum
8:如果后续区间的最大值比以i-1为结尾的前区间的和大于最后的ans,更新ans
几个小细节
2:遍历顺序应该从n至2:只有这样才能保证将一段序列分为两端:
如果i=1,那么i-1=0,就相当于只有一个区间,且这个区间还是原来的序列数组
8:应该用sum+dp而不应该用flag+dp:flag只是指当前的点最优解而不是指当前i往后的一个区间的最大值
至此,该题我认为的难点和坑已经叙述得差不多了,所以
code
/* NAME:EPEILONCXL;
LANG:C++;
TIME:9:08
PROB:B
\`,"" ,'7"r-..__/ \
,'\/`, ,',',' _/ \
/ \/ 7 / / ( \ |
J \/ j L______\ / |
L __JF"""/""\"\_, /
L,-"| O| | O | L_ _/
F \_ / \__/ `- /|
.-' `""" ,' | _..====.._
\__/ r"_ A ,' _..---.._`,
`-.______,,-L// / \ ___,' ,'_..:::.. `,`,
/ / / / 7" `-<""=:' '':. \ \
| <,' / F . i , \ `, :C X L
| \,' / >X< | \ :| | X
\ `._/ ' ! ` | C :| | C
\ \ / | :C C |
__>-.__ __,-\ | | X X |
/ /| | \ \ \_ | 'L L |
/ __/ | | \ \ \ X./ / L
/ | | | | | | L/ / ,'
| \ | | | | | // / _,'
_________| | | | | | | //_/-'
\ __ | / | | / | | /="
\\ \_\ \__,' \ / |/ / |
\\\_____________\/ C___X L
\\| EPSILONCXL |_____/ (_____/
\/TEST:19.1.28B/
*/
#include <bits/stdc++.h>
using namespace std;
const int inf=-1e9;
const int maxn=100005;
int dp[maxn],a[maxn],ans,flag,sum,n;
int read()
{
char ch=getchar();int x=0,f=1;
while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0' && ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void init()
{
freopen("B.in","r",stdin);
}
void readdata()
{
n=read();
for(int i=1;i<=n;i++)a[i]=read();
}
void dp_head()
{
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)
{
dp[i]=max(dp[i-1]+a[i],a[i]);
}
}
void dp_back()
{
ans=inf,flag=0,sum=inf;
for(int i=n;i>1;i--)
{
if(flag>0)flag+=a[i];
else flag=a[i];
if(flag>sum)sum=flag;
if(sum+dp[i-1]>ans)ans=sum+dp[i-1];
}
}
void work()
{
dp_head();
dp_back();
printf("%d",ans);
}
int main()
{
init();
readdata();
work();
return 0;
}