2019河北省大学生程序设计大赛-C题

题目内容

链接:https://ac.nowcoder.com/acm/contest/903/C
来源:牛客网

 你是DEEP国的大军师,辅佐一个非常有野心的国王,这位国王非常有野心,他计划攻占 n 个国家。在地图上,这些国家排成一行。

 探子已经查明,当攻打一个国家 i 时,为了防止国家间的联合对抗,需要给该国家周围,所有未被攻占的国家支付cost_icost
i个金币,即对于国家 i,它左侧第一个已被攻打的国家为 l,右侧第一个已被攻打的国家为 r,则他需要给[l+1,i-1] 和 [i+1,r-1] 之间的国家支付金币。如果 l 不存在,则需要给 [1, i-1] 之间的所有国家支付金币;若 r 不存在,则需要给 [i+1,n] 之间的所有国家支付金币。

 现在,你的下属已经给你提供了每个国家需要支付金币的数量。为了满足国王的野心,你需要计算出攻占完所有国家需要的最小花费。
输入描述:
第一行是一个整数 T,代表接下来有T组数据。
接下来每组数据
第一行有一个整数 n,代表要攻占的国家数目。
第二行共 n 个整数,代表攻占每个国家前,需要支付给其周围未被攻占国家 cost_icost
i

个金币。
1 \leq T \leq 501≤T≤50,1 \leq n \leq 1001≤n≤100,1 \leq cost_i \leq 100001≤cost
i

≤10000
输出描述:
对于每组数据输出一行,代表需要支付的最小花费。
示例1
输入
复制
2
1
1
3
1 1 2
输出
复制
0
2
说明
3
1 1 2
先打中间这个1 给左右两个各1块钱
再打左右两个 不用花钱

题目内容讲解

 输入T组测试样例,每一组都有一个N代表你要攻打多少个国家(一统天下 )。你每次只能攻打下一个国家,而其他国家不会坐以待毙,他们会问你要钱!当你攻打i国家时,从i往两边延申,遇到了没有攻打下来的国家你就需要给他们钱,直到遇到攻打下来的国家或者延申完了为止。
 比如要攻打3个国家,国际友好协会已经评定好了你打任何一个国家需要给别的邻接的国家的代价:1 1 3,1代表攻打第一个国家时,如果第二个国家没有被攻打你需要给他1块钱,如果第三个国家没有被攻打你也需要给他1亿。但如果在攻打第一个国家时,第二个国家已经被攻打了,那么也不用给第三个国家钱了(因为有所阻隔,不怕了 )。所以攻打的最好方案是攻打第二个国家,则给第一个第三个国家各自两块,然后第二个国家被攻占,再攻打第一或者第三个国家因为隔着第二个国家(已经被攻打)就不需要给钱了。

解题思路

 目标:对于原问题,就是实现攻打n个国家的目标代价最小。
 思考过程:先考虑过程-即称霸天下,对于n先取一个k1攻打,再取一个k2攻打,再取k3,直到kn。而每一步若选中k之后呢?还需要攻打第1到k-1和第k+1到n的国家,这不就分解出子问题了嘛!而且还是原问题类型的!
思考解题:原问题分解成与原问题同类型的子问题则考虑使用分治。对于原问题可以转换为攻打1到n的国家属于攻打x到y的国家这一类,并且对于原问题的下一步也是涉及i到k-1和k+1到j的情况,所以将攻打i到j的国家视为状态。而状态转移树中,后一状态取决于之前的多个状态,考虑使用动态规划。考虑状态转移方程,攻打i到j的国家的最优解=等于最优解(攻打i到k-1的国家的最优解+攻打k+1到j的国家的最优解+第k个国家的代价乘以(j-i) ),这里的最优解其实就是代价最小。(注意d(i,j)表示攻打i到j国家,而其中所有国家都未被攻打)
状态:d(i,j)为攻打i到j的国家的最小代价。
状态转移方程:d(i,j)=min{d(i,j),d(i,k-1)+d(k+1,j)+a(k)*(j-i)}。

解题代码(AC含注释)

#include<iostream>
using namespace std;
#define Min(a,b) a<b?a:b//简单计算最小值
#define N 100			//定义国家最大值
#define INF 1e9			//定义一个最大值一遍赋值给min(让其在第一次比较时能被更新)
int a[N];				//存储的是n个国家的代价
int d[N][N];//存储记忆数据即状态d(i,j)表示攻打从i到j的国家的最优解
int solve(int i,int j)//dp记忆化搜索求解
{															
	if (i>=j)return 0;//注意边界。因为状态转移要么变左边界要么变右边界,剩下一个是规范的,所以当一者超出0或者超出n-1时都可以用此式检测到
	if (d[i][j] != -1)return d[i][j];//如果计算过了(为0或者正整数)		还有一种不规范是当取k = i(左边界)时欲图计算d(i, k - 1)即d(i, i - 1), 因为计算d(i, j)本就不赢考虑小于i时
	int & min = d[i][j];//定义一个引用变量等于它(名字短容易修改)
	min = INF;			//令他等于无穷大(因为现在等于-1,求出最优解即使为0都更新不了)
	for (int k = i; k <=j ; k++)//试探从i到j攻击一个国家
	{
		int sum = solve(i,k-1) + solve(k+1,j) + a[k] * (j-i);//计算状态转移方程。最内层时i==j 0+0+0=0。
		//cout <<i<<" "<<k<<" "<<j <<" min:" << min << " sum:" << sum << " solve(i,k-1):" << solve(i, k - 1) << " solve(k+1,j):" << solve(k + 1, j) << " a[k] * (j-i):" << a[k] * (j - i) << endl;
		if (min > sum)min = sum;//记录最优解(选某一个国家时)
	}
	return min;//返回最优解
}

int main()
{
	int T;
	cin >> T;//输入T个测试用例
	while(T--)
	{
		int n;
		cin >> n;//输入n个国家
		memset(d, -1, sizeof(d));//初始化二维数组为-1(使用此函数初始化整数数组只能为-1或者0)
								 //而0应该表示攻打唯一国家时的代价d(i,i)=0,所以未求解的设为-1
		for (int i = 0; i < n; i++)
		{
			cin >> a[i];//输入n个国家各自自己的代价
		}
		cout << solve(0,n-1)<<endl;//计算状态0到n-1的最优解并且输出
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

仰望—星空

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值