信息学奥赛一本通 1262:【例9.6】挖地雷 | 洛谷 P2196 [NOIP1996 提高组] 挖地雷
【题目链接】ybt 1262:【例9.6】挖地雷【题目考点】1. 动态规划2. 图论【解题思路】根据题意,每个地窖是一个顶点,每条路径是一条有向边,这是个有向无环图,可以用动态规划的方法来求解。顶点编号从小到大,只存在小编号顶点指向大编号的顶点的边,不存在大编号顶点指向小编号顶点的边,说明该顶点按照序号从小到大符合拓扑排序。1. 状态定义集合:图中的路径限制:路径的终点顶点属性:路径上每个顶点的地
【题目链接】
ybt 1262:【例9.6】挖地雷
洛谷 P2196 [NOIP1996 提高组] 挖地雷
注:以上两题输入格式不同
【题目考点】
1. 图论:拓扑排序,有向无环图动规
【解题思路】
根据题意,每个地窖是一个顶点,每条路径是一条有向边,每个地窖的地雷数是该顶点的权值(简称点权),这是个有向无环图。
该题可抽象为:求有向无环图上,点权加和最大的路径,可以用动态规划的方法来求解。
顶点编号从小到大,只存在小编号顶点指向大编号的顶点的边,不存在大编号顶点指向小编号顶点的边,说明该顶点按照序号从小到大符合拓扑排序。或者也可以直接对该图进行拓扑排序,在拓扑排序的过程中进行状态转移。
1. 状态定义
- 阶段:到达顶点i
- 决策:从顶点i出发接下来走到哪个邻接点
- 策略:路径
- 策略集合:以顶点i为终点的所有路径
- 条件:点权加和最大
- 统计量:点权加和
状态定义 dp[i]
:以顶点i为终点的所有路径中,点权加和最大的路径的点权加和。
记第i顶点的地雷数量,也就是点权为a[i]
。
初始状态 顶点i自己,就是一个以顶点i为终点的路径,权值为a[i]
。因此设dp[i] = a[i]
2. 状态转移方程
- 策略集合:以顶点v为终点的所有路径
- 分割策略集合:根据顶点v的前一个顶点的不同情况对到达顶点i的路径进行分类
对于顶点v,假设存在弧 < u 1 , v > , < u 2 , v > , . . . , < u x , v > <u_1, v>, <u_2, v>, ..., <u_x, v> <u1,v>,<u2,v>,...,<ux,v> - 如果路径经过
u
1
u_1
u1到顶点
v
v
v,那么到顶点
v
v
v时路径点权加和为:以
u
1
u_1
u1为终点的路径的最大点权加和
dp[u1]
,加上顶点v的点权a[v]
,即dp[u1] + a[v]
- 如果路径经过
u
2
u_2
u2到顶点
v
v
v,那么到顶点
v
v
v时路径点权加和为:以
u
2
u_2
u2为终点的路径的最大点权加和
dp[u2]
,加上顶点v的点权a[v]
,即dp[u2] + a[v]
… - 对所有存在弧
<
u
,
v
>
<u,v>
<u,v>的顶点u,即对所有到顶点i有弧的顶点u,以顶点v为终点的最大点权加和
dp[v]
为:以 u u u为终点的所有路径的最大点权加和dp[u]
加上顶点v的点权a[v]
,即dp[u] + a[v]
- 对所有可能的情况求最大值,得到状态转移方程:
d p [ v ] = m a x { d p [ u ] + a [ v ] } , ∀ < u , v > ∈ E dp[v] = max\{dp[u]+a[v]\},\forall <u, v> \in E dp[v]=max{dp[u]+a[v]},∀<u,v>∈E,E是图的边集合。
对于拓扑排序序列,如果u到v有路径,那么在拓扑排序序列中u在v前面,因此只需要遍历拓扑排序序列中所有顶点v前面的顶点u,通过dp[v] = max(dp[v], dp[u]+a[v])
更新dp[v]
,即可得到dp[v]
。
3. 具体编码
可以直接进行拓扑排序,在拓扑排序过程中进行状态转移
顶点u出队时,访问顶点u的邻接点v,顶点u在拓扑排序序列中在v的前面,顶点u出队时dp[u]
的值已经求好了,那么dp[u]+a[v]
就是dp[v]
的一个可能的结果,可以通过dp[v] = max(dp[v], dp[u]+a[v])
更新dp[v]
。
或者先得到拓扑排序的序列,而后遍历拓扑排序序列完成状态转移。如果使用邻接表存图,则需要对原图的边逆向后建图。这样方便取到指向顶点i的所有顶点。
用数组path记录路径,用递归或栈的方法逆序输出。
最后dp数组最大值的下标为mxi,即以顶点mxi为终点的路径的点权加和最大,输出该路径及点权加和。
【题解代码】
ybt 1262:【例9.6】挖地雷
解法1:进行拓扑排序
#include <bits/stdc++.h>
using namespace std;
#define N 205
int n, a[N], dp[N], deg[N], pre[N], mxi = 1;//dp[i]:终点为i的路径的最大点权加和,挖到第i地窖时最多有多少地雷
vector<int> edge[N];
void topoSort()
{
queue<int> que;
for(int i = 1; i <= n; ++i) if(deg[i] == 0)
{
que.push(i);
dp[i] = a[i];
}
while(!que.empty())
{
int u = que.front();
que.pop();
if(dp[u] > dp[mxi]) //求dp数组最大值下标
mxi = u;
for(int v : edge[u])
{
if(dp[v] < dp[u]+a[v])
{
pre[v] = u;
dp[v] = dp[u]+a[v];
}
if(--deg[v] == 0)
que.push(v);
}
}
}
void show(int i)//输出终点为i的路径
{
if(pre[i] == 0)
{
cout << i;
return;
}
show(pre[i]);
cout << '-' << i;
}
int main()
{
int x, y;
cin >> n;
for(int i = 1; i <= n; ++i)
cin >> a[i];
while(cin >> x >> y && !(x == 0 && y == 0))
{
edge[x].push_back(y);
deg[y]++;
}
topoSort();
show(mxi);
cout << endl << dp[mxi];
return 0;
}
解法2:遍历拓扑排序序列
- 写法1:使用邻接矩阵
#include <bits/stdc++.h>
using namespace std;
#define N 205
bool edge[N][N];//edge[i][j]:从i到j有一条弧
int dp[N];//dp[i]:以顶点i为终点的所有路径中,路径上地雷数量加和最大的路径的地雷数量加和。
int a[N], path[N];//a[i]:第i个地窖的地雷个数 path[i]:第i顶点的前一个顶点
void showPath(int i)//输出以i为终点的路径
{
if(path[i] == 0)
{
cout << i;
return;
}
showPath(path[i]);
cout << '-' << i;
}
int main()
{
int n, f, t, mxi = 1;
cin >> n;
for(int i = 1; i <= n; ++i)
cin >> a[i];
while(cin >> f >> t && !(f == 0 && t == 0))
edge[f][t] = true;
for(int i = 1; i <= n; ++i)
{
dp[i] = a[i];
for(int j = 1; j < i; ++j) if(edge[j][i] && dp[i] < dp[j]+a[i])
{
dp[i] = dp[j]+a[i];
path[i] = j;
}
}
for(int i = 1; i <= n; ++i)
if(dp[i] > dp[mxi])
mxi = i;
showPath(mxi);
cout << endl << dp[mxi];
return 0;
}
- 写法2:使用邻接表
#include <bits/stdc++.h>
using namespace std;
#define N 205
vector<int> edge[N];//edge[i]:所有满足存在弧<j,i>的j保存在edge[i]中
int dp[N];//dp[i]:以顶点i为终点的所有路径中,路径上地雷数量加和最大的路径的地雷数量加和。
int a[N], path[N];//a[i]:第i个地窖的地雷个数 path[i]:第i顶点的前一个顶点
void showPath(int i)//输出以i为终点的路径
{
if(path[i] == 0)
{
cout << i;
return;
}
showPath(path[i]);
cout << '-' << i;
}
int main()
{
int n, f, t, mxi = 1;
cin >> n;
for(int i = 1; i <= n; ++i)
cin >> a[i];
while(cin >> f >> t && f && t)
edge[t].push_back(f);//建与原图弧的方向逆向的图,f到t有一条弧,那么把f加入edge[t]这个vector中。
for(int i = 1; i <= n; ++i)
{
dp[i] = a[i];
for(int j : edge[i]) if(dp[i] < dp[j] + a[i])
{
dp[i] = dp[j]+a[i];
path[i] = j;
}
}
for(int i = 1; i <= n; ++i)
if(dp[i] > dp[mxi])
mxi = i;
showPath(mxi);
cout << endl << dp[mxi];
return 0;
}
洛谷 P2196 [NOIP1996 提高组] 挖地雷
解法1:进行拓扑排序
#include <bits/stdc++.h>
using namespace std;
#define N 205
vector<int> edge[N];//edge[i]:所有满足存在弧<j,i>的j保存在edge[i]中
int dp[N];//dp[i]:以顶点i为终点的所有路径中,路径上地雷数量加和最大的路径的地雷数量加和。
int n, deg[N], a[N], pre[N];//a[i]:第i个地窖的地雷个数 path[i]:第i顶点的前一个顶点
void showPath(int i)//输出以i为终点的路径
{
if(pre[i] == 0)
{
cout << i;
return;
}
showPath(pre[i]);
cout << ' ' << i;
}
void topoSort()
{
queue<int> que;
for(int v = 1; v <= n; ++v)
if(deg[v] == 0)
{
dp[v] = a[v];
que.push(v);
}
while(que.empty() == false)
{
int u = que.front();
que.pop();
for(int v : edge[u])
{
if(dp[v] < dp[u]+a[v])
{
dp[v] = dp[u]+a[v];
pre[v] = u;
}
if(--deg[v] == 0)
que.push(v);
}
}
}
int main()
{
int f, t, mxi = 1;
cin >> n;
for(int i = 1; i <= n; ++i)
cin >> a[i];
for(int u = 1; u < n; ++u)
for(int v = u+1; v <= n; ++v)
{
cin >> t;
if(t)
{
edge[u].push_back(v);
deg[v]++;
}
}
topoSort();
for(int i = 1; i <= n; ++i)
if(dp[i] > dp[mxi])
mxi = i;
showPath(mxi);
cout << endl << dp[mxi];
return 0;
}
更多推荐
所有评论(0)