noip15年普及组-T4-推销员

这篇博客介绍了推销员问题的两种解法。第一种是60分解法,通过枚举和模拟,利用数学推理找到最大开销的节点,并逐步累加。第二种是100分解法,通过快排选择最优节点,减少枚举次数,提高效率。时间复杂度分别为O(N^2)和最坏情况下的O(N*NlogN)或最优情况下的O(NlogN)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:推销员

1. 60分解法 枚举+模拟

提出一个命题:
如果 X = 1 X=1 X=1, 走过的结点集合为 { i 1 } \{i_1\} {i1}
如果 X = 2 X=2 X=2, 走过的结点集合为 { i 1 , i 2 } \{i_1, i_2\} {i1,i2}
就是说: X = 2 X=2 X=2的集合必然包含 X = 1 X=1 X=1的集合

证明:
v[i] : i结点的疲劳值
dis[i]: i结点到起点的距离

x=1maxNode为最大的结点, 即是V[maxNode] + dis[maxNode]*2 > v[i] + dis[j](i=j=1,2,3...)

x=2假设最大的疲劳值是q = v[k1] + v[k2] + dis[k2] * 2其中dis[k2]>dis[k1] k1!=maxNode, k2!=maxNode
显然可以看出存在 v[k1] +V[maxNode] + dis[maxNode]*2 > q
所 以 假 设 不 成 立 , k 1 或 k 2 中 必 然 有 一 个 等 于 m a x N o d e . 所 以 命 题 成 立 所以假设不成立,k1或k2中必然有一个等于maxNode. 所以命题成立 k1k2maxNode.
基于上述思路枚举即可。
计算推销员到每一个点的最大开销,找到最大的一个然后累加然后输出。然后再找到下一个最大的开销的点,如果这个点的距离小于之前的点,那么累加A[i]即可,否则累加A[i] + (dis - max)*2

时间复杂度 O ( N 2 ) O(N^{2}) O(N2)

#include<bits/stdc++.h>
#define UP(i,x,y) for(int i=x; i<=y; i++)
#define LEN 100001
using namespace std;

int N, S[LEN]={0}, A[LEN]={0}, X;
int book[LEN]={0};

/* return哪个节点能获得最大的开销 */
int findMaxValue(int maxIndex)
{
	int currValue = 0;
	int currIndex = 0;
	 
	UP(i, 1, N)
	{
		if(book[i] == 0)
		{
			/* 新的结点超过前面的结点的距离 */
			if(S[i] > S[maxIndex])
			{
				if(A[i] + (S[i] - S[maxIndex]) * 2 > currValue)
				{
					currValue = max(A[i] + (S[i] - S[maxIndex]) * 2, currValue);
					currIndex = i;
				}
			}
			/* 新的结点小于前面的结点的距离 */
			else
			{
				if(A[i] > currValue)
				{
					currValue = max(A[i], currValue);
					currIndex = i;
				}
				
			}
		}
	}
	return currIndex;
}

int main()
{
	cin>>N;
	UP(i, 1, N)
	{
		cin>>S[i];
	}
	UP(i, 1, N)
	{
		cin>>A[i];
	}
	
	int maxIndex = 0;
	int k = 0;
	int ans = 0;
	
	UP(i, 1, N)
	{
		k = findMaxValue(maxIndex); 
		/* 新结点超过了原来最大的结点的距离 */ 
		if(S[k] > S[maxIndex])
		{		
			ans += A[k] + (S[k] - S[maxIndex])*2;
			maxIndex = k; // 更新距离最大的结点 
		}
		/* 新结点小于原来最大的结点 */ 
		else
		{
			ans += A[k];
		}
		cout<<ans<<endl;
		book[k] = 1; // 标记下当前结点走过了 
	}
	return 0;
}

/* 总结:
	1.Debug调试十分重要比脑海调试快很多倍
	2.设计复杂的代码一定要用函数抽离 
 */

2. 100分解法 快排序选择最优点

之前的算法有个问题,就是每次的最优点我们需要枚举 N N N次才能找到最优点,那么可不可以都直接获得最优的点?

我们对每一个点按照开销(距离+疲劳)来从大到小排序,那么第一个元素 i i i就是我们最优的点,将 i i i累加并输出。如果 i i i改变了我们的maxA(已经选择的最远距离),那么我们需要重新排序,又因为 i i i已经选择了,所以排序的范围是 [ i + 1 , N ] [i+1, N] [i+1,N]

时间复杂度。最坏情况下 O ( N ∗ N l o g N ) O(N*NlogN) O(NNlogN),最优情况 O ( N l o g N ) O(NlogN) O(NlogN)(也就是第一次选择的是最远的点,sort函数只被调用了一次)

#include<bits/stdc++.h>
#define UP(i,x,y) for(int i=x; i<=y; i++)
#define LEN 100001 
using namespace std;

int maxA = 0; // 最远的距离 
int N;
struct node{
	int dis;
	int val; 
};
node arr[LEN];

bool cmp(node &a, node &b)
{
	int t1, t2;
	/* 计算增量 */ 
	if(a.dis > maxA)
	{
		t1 = a.val + (a.dis - maxA) * 2;
	}
	else
	{
		t1 = a.val;
	}
	
	/* 计算增量 */
	if(b.dis > maxA)
	{
		t2 = b.val + (b.dis - maxA) * 2;
	}
	else
	{
		t2 = b.val;
	}
	
	/* 比较 */ 
	if(t1 > t2)
	{
		return 1;		
	}
	else
	{
		return 0;
	}
}

int main()
{
	cin>>N;
	UP(i, 1, N)
	{
		cin>>arr[i].dis;
	}
	UP(i, 1, N)
	{
		cin>>arr[i].val;
	}
	
	/* i结点的加入能带来多大的开销(距离+疲劳)
	   按照开销排序 
	 */ 
	sort(arr+1, arr+N+1, cmp);
	
	int ans = 0;
	
	UP(i, 1, N)
	{
		/* 新加入的结点距离比maxA远 */ 
		if(arr[i].dis > maxA)
		{
			ans += (arr[i].dis - maxA) * 2 + arr[i].val; // (arr[i].dis - maxA) 容易错 
			maxA = arr[i].dis; // 更新最大距离 
			
			/* 
				i结点的加入能带来多大的开销(距离+疲劳)
	   			按照能来多大的开销排序 
	 		*/ 
			sort(arr+i+1, arr+N+1, cmp);
		}
		/* 新加入的结点距离比maxA近 */ 
		else
		{
			ans += arr[i].val;
		}
		cout<<ans<<endl;
	} 
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值