SCNU-Algorithms-MS-2023-动态规划

1-1 两个字符串的所有最长公共子序列

求两个字符串的所有最长公共子序列

输入格式

输入长度≤100的两个字符串。


输入样例1:

ABCBDAB
BDCABA

输入样例2:

ABACDEF
PGHIK


输出格式

输出两个字符串的所有最长公共子序列,若最长公共子序列多于1个,则将所有子序列按字典序从小到大排序后输出。


输出样例1:

BCAB
BCBA
BDAB

输出样例2:

NO


最长公共子序列并不是最长相同字符串,公共子序列不需要连续相等,只需要有相同的序列字符就好。

基本原理为进行两次for循环,s1序列的每个字符都要去对比s2中的每个字符。对比的同时会继承s1前面字符的序列数。(如图所示)
在这里插入图片描述
当s1的c == s2的c时,会根据左上方的数字加一,因为左上方的数字,表示除自己以外的先前序列的公共子序列数。当数字不相等时,对比上方和左边的数字,继承先前序列的最大数值。故代码如下:

if s1[i]==s2[j],arr[i][j]=arr[i-1][j-1]+1else arr[i][j]=max{ arr[i][j-1], arr[i-1][j] };

获得上面的数组后,需要根据数组反推打印出子序列。因为当字符相等时,是从左上方加一得到;当字符不想同时,是从上方或左边继承得到。可以进行判断,当字符相等时,输出字符,同时往左上方移动;当不相等时,对比上方和左边,往更大值移动。当上方和左边都相同时,表面有多条子序列路线,这时就需要递归处理。故代码如下:
if (s1[i - 1] == s2[j - 1])
		{
			i--;
			j--;
			str = s1[i] + str;
		}
		else
		{
			if (arr[i - 1][j] > arr[i][j - 1])i--;
			else if (arr[i - 1][j] < arr[i][j - 1])j--;
			else
			{
				lsc_print(i - 1, j, str);
				lsc_print(i, j - 1, str);
				return;
			}
		}

完整代码如下:

#include<iostream>
#include<string>
#include<set>


std::string s1, s2;
int arr[101][101] = { 0 };//动态规划表
std::set <std::string> lsc_s;
int lsc_max(int m, int n)//求出最大公共子序列的长度 
{
	for (int i = 1; i <= m; i++)
	{
		for (int j = 1; j <= n; j++)
		{
			if (s1[i - 1] == s2[j - 1])arr[i][j] = arr[i - 1][j - 1] + 1;
			else arr[i][j] = std::max(arr[i - 1][j], arr[i][j - 1]);
		}
	}
	return arr[m][n];
}
void lsc_print(int i, int j, std::string str)
{
	while (i > 0 && j > 0)
	{
		if (s1[i - 1] == s2[j - 1])
		{
			i--;
			j--;
			str = s1[i] + str;
		}
		else
		{
			if (arr[i - 1][j] > arr[i][j - 1])i--;
			else if (arr[i - 1][j] < arr[i][j - 1])j--;
			else
			{
				lsc_print(i - 1, j, str);
				lsc_print(i, j - 1, str);
				return;
			}
		}
	}

	if (str.length())lsc_s.insert(str);
}
int main()
{
	std::cin >> s1 >> s2;
	int m = s1.length();
	int n = s2.length();
	int len = lsc_max(m, n);
	std::string str;
	lsc_print(m, n, str);
	auto it = lsc_s.begin();
	if (lsc_s.empty())
	{
		std::cout << "NO" << std::endl;
		return 0;
	}
	while (it != lsc_s.end())
	{
		std::cout << *it << std::endl;
		it++;
	}
	return 0;
}

1-2KK的传言

股票经纪人KK想要在一群人(n个人的编号为0~n-1)中散布一个传言,传言只通过认识的人进行传递,在两个认识的人之间传递消息所需要的时间各有不同,在一群人中,KK想要找出以哪个人为起点开始传递这个传言,可以在耗时最短的情况下认所有人都收到消息。
请编写程序帮KK解决这个问题。


### 输入格式 先输入n m,其中n是人数,m是可传递消息的人群关系数量,以下m行分别输入a b t ,表示 a 向 b 可传递消息,消耗的时间为t。 当输入0时,表示输入结束。

输入样例1:

4 4
0 1 2
0 2 5
0 3 1
2 3 3
6 5
0 1 2
0 2 5
0 3 1
2 3 3
4 5 2
0


输出格式

输出从第几个人开始传递传言耗时最短。如果无解,就输出-1。

输出样例1:

3
-1


先用floyd算法,求解出任意两个点中的最短距离。其算法步骤为:三次for循环,前两层是循环遍历二维数组所有的点,第三层是对任意 **[i][j]** 把每个其他的点**K**对比一次,看看是从i到j的距离短,还是i到k到j的距离更短,取更短值赋值到数组中。代码如下:
//floyd algorithm
void floyd(int n)
{
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            for (int k = 0; k < n; k++)
            {
                if (j == k) continue;
                dp[j][k] = std::min(dp[j][k], dp[j][i] + dp[i][k]);
            }
}

再计算每个点到所有点的最小值,就每一行的数值相加即可,其中如果有一条边的值没被改变过,可以视为这个点是孤立的无法到达,也就不符合正确答案,视为失败。
对遍历的所有点的距离总和,找出个最小的点,这个点就是我们所找的答案。代码如下:

int get_res(int n)
{
    //Calculate the minimum value from each point to other points
    std::vector<int>res_sum;
    for (int i = 0; i < n; i++)
    {
        int sum_n = 0;
        for (int j = 0; j < n; j++)
        {
            if (i == j) continue;
            sum_n += dp[i][j];
            if (dp[i][j] == MAX_INT)
                return -1;
        }
        res_sum.push_back(sum_n);
    }

    //find the node which less sum path
    int min_sum = res_sum[0];
    int min_index = 0;
    for (int i = 1; i < n; i++)
    {
        if (min_sum >= res_sum[i])
        {
            min_sum = res_sum[i];
            min_index = i;
        }
    }
    return min_index;
}

完整代码如下:

#include<iostream>
#include<math.h>
#include<vector>
#include <cmath>


#define MAX_INT 9999
#define MIN_INT -9999

std::vector<std::vector<int>>dp = { 101, std::vector<int>(101, MAX_INT) }; //Minimum Path Matrix
 
//floyd algorithm
void floyd(int n)
{
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            for (int k = 0; k < n; k++)
            {
                if (j == k) continue;
                dp[j][k] = std::min(dp[j][k], dp[j][i] + dp[i][k]);
            }
}

int get_res(int n)
{
    //Calculate the minimum value from each point to other points
    std::vector<int>res_sum;
    for (int i = 0; i < n; i++)
    {
        int sum_n = 0;
        for (int j = 0; j < n; j++)
        {
            if (i == j) continue;
            sum_n += dp[i][j];
            if (dp[i][j] == MAX_INT)
                return -1;
        }
        res_sum.push_back(sum_n);
    }

    //find the node which less sum path
    int min_sum = res_sum[0];
    int min_index = 0;
    for (int i = 1; i < n; i++)
    {
        if (min_sum >= res_sum[i])
        {
            min_sum = res_sum[i];
            min_index = i;
        }
    }
    return min_index;
}

int main()
{
    int n, m;
    while (1)
    {
        //input n,m
        std::cin >> n;
        if (n == 0) return 0;
        std::cin >> m;
        //input a,b,t
        while (m--)
        {
            int a, b, t;
            std::cin >> a >> b >> t;
            dp[a][b] = t;
            dp[b][a] = t;
        }

        //处理数据获取结果
        floyd(n);
        int result = get_res(n);

        //result
        std::cout << result << std::endl;
        std::vector<std::vector<int>>dp = { 101,std::vector<int>(101,MAX_INT) };
    }
    return 0;
}

0-1背包问题

给定一个承重量为C的背包,n个重量分别为w1,w2,w3,wn的物品,物品i放入背包能产生 p_i (>0)的价值(i=1,2,…,n)。
每个物品要么整个放入背包,要么不放。要求找出最大价值的装包方案。

输入格式

输入的第一行包含两个正整数n和C(1≤n≤20),第二行含n个正整数分别表示n个物品的重量,第三行含n个正整数分别表示n个物品放入背包能产生的价值。
输入样例:

4 9
2 3 4 5
3 4 5 7


输出格式

在一行内输出结果,包括最大价值装包方案的价值、具体装包方案,用空格隔开。具体装包方案是n个物品的一个子集,用长度为n的0、1串表示(1表示对应物品被选中,0表示没有被选中)。如果这样的0、1串不唯一,取字典序最大的那个串。
输出样例:

12 1110


具体步骤由两层for循环构成,其二维数组构成如下:

在这里插入图片描述
背包可释放容量不断增加,每个物体一个个重量单位都要尝试,在当前容量下,是否应该塞进去。如果是容量塞不进去,那就继承上方,也就是上一个物体的此时的总体价值。如果容量允许塞进去,例如数组[2][3]=4,第二个物体的容量就是3,可以塞进去。但需要注意,我们要对比,放入物体后,价值是否当前最大化。
我们是判断不放入的时候价值f[1][5]=3和放入的时候我们先找到f[1][5-3]的最大价值然后加上现在物品的价值f[i-1][j-w[i]]+v[i]=7,这种情况我们当然选择放入价值更大所以f[2][5]=7。

for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=bag_weigh;j++)
        {
            //max(选i物品,不选i物品)
            if(j>=weigh[i])
            {
                dp[i][j]=max(value[i]+dp[i-1][j-weigh[i]],dp[i-1][j]);
            }
            else dp[i][j]=dp[i-1][j];
        }
    }


在这里插入图片描述

按上述步骤得到数组后:采用回溯法,当前数值如果和上方数值相同,则说明这个物体塞不进来,或是塞不塞都一样。如果和上方数值不一样,说明该物体被放进背包,并且重要,那么回至上方数值,循环反复。

完整代码如下:

#include<iostream>
#include <vector>
using namespace std;

vector<int>weigh;
vector<int>value;
vector<vector<int>>dp(100,vector<int>(100,0));
string choice="000000000000000000000000000";

void dp_opt(int n,int bag_weigh)
{
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=bag_weigh;j++)
        {
            //max(选i物品,不选i物品)
            if(j>=weigh[i])
            {
                dp[i][j]=max(value[i]+dp[i-1][j-weigh[i]],dp[i-1][j]);
            }
            else dp[i][j]=dp[i-1][j];
        }
    }
    return;
}

void get_choice(int i,int j)
{
    while(i>=1)
    {
        if(dp[i][j]==dp[i-1][j]) 
        //第i个物品不重要
            choice[i]='0';
        else
        //第i个物品很重要
        {
            choice[i]='1';
            j-=weigh[i];
        }
        i--;
    }
    return;
}

int main()
{
    //输入数据物品数量、背包最大承重
    int n,bag_weigh;
    cin>>n>>bag_weigh;

    //因为dp数组的第0行和第0列是没有数据的,为0,所以这里统一
    weigh.push_back(0);
    value.push_back(0);

    //输入物品重量以及价值
    for(int i=0;i<n;i++)
    {
        int w;
        cin>>w;
        weigh.push_back(w);
    }
    for(int i=0;i<n;i++)
    {
        int v;
        cin>>v;
        value.push_back(v);
    }

    //获得最大收益
    dp_opt(n,bag_weigh);

    //获取选择
    get_choice(n,bag_weigh);

    //输出结果
    cout<<dp[n][bag_weigh]<<" ";
    for(int i=1;i<=n;i++)
        cout<<choice[i];
        
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值