【题目链接】

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;
}
Logo

集算法之大成!助力oier实现梦想!

更多推荐