NEUQ-acm 预备队训练Week6—贪心问题

贪心

贪心,即选取当前最优解,注意当前最优解未必是所有解(全局)中最好的。

P1199 [NOIP2010 普及组] 三国游戏

题目描述

小涵很喜欢电脑游戏,这些天他正在玩一个叫做《三国》的游戏。

在游戏中,小涵和计算机各执一方,组建各自的军队进行对战。游戏中共有 N N N 位武将( N N N为偶数且不小于 4 4 4),任意两个武将之间有一个“默契值”,表示若此两位武将作为一对组合作战时,该组合的威力有多大。游戏开始前,所有武将都是自由的(称为自由武将,一旦某个自由武将被选中作为某方军队的一员,那么他就不再是自由武将了),换句话说,所谓的自由武将不属于任何一方。

游戏开始,小涵和计算机要从自由武将中挑选武将组成自己的军队,规则如下:小涵先从自由武将中选出一个加入自己的军队,然后计算机也从自由武将中选出一个加入计算机方的军队。接下来一直按照“小涵→计算机→小涵→……”的顺序选择武将,直到所有的武将被双方均分完。然后,程序自动从双方军队中各挑出一对默契值最高的武将组合代表自己的军队进行二对二比武,拥有更高默契值的一对武将组合获胜,表示两军交战,拥有获胜武将组合的一方获胜。

已知计算机一方选择武将的原则是尽量破坏对手下一步将形成的最强组合,它采取的具体策略如下:任何时刻,轮到计算机挑选时,它会尝试将对手军队中的每个武将与当前每个自由武将进行一一配对,找出所有配对中默契值最高的那对武将组合,并将该组合中的自由武将选入自己的军队。 下面举例说明计算机的选将策略,例如,游戏中一共有 6 6 6个武将,他们相互之间的默契值如下表所示:
NEUQ-acm 预备队训练Week6—贪心问题_第1张图片
双方选将过程如下所示:
NEUQ-acm 预备队训练Week6—贪心问题_第2张图片
小涵想知道,如果计算机在一局游戏中始终坚持上面这个策略,那么自己有没有可能必胜?如果有,在所有可能的胜利结局中,自己那对用于比武的武将组合的默契值最大是多少?

假设整个游戏过程中,对战双方任何时候均能看到自由武将队中的武将和对方军队的武将。为了简化问题,保证对于不同的武将组合,其默契值均不相同。

输入格式

共 N 行。

第一行为一个偶数 N N N,表示武将的个数。

第 $2 $行到第 $N 行 里 , 第 行里,第 i+1 行 有 行有 N_i 个 非 负 整 数 , 每 两 个 数 之 间 用 一 个 空 格 隔 开 , 表 示 个非负整数,每两个数之间用一个空格隔开,表示 i 号 武 将 和 号武将和 i+1,i+2,…,N $号武将之间的默契值( 0 ≤ 0≤ 0默契值 ≤ 1 , 000 , 000 , 000 ≤1,000,000,000 1,000,000,000)。

输出格式

1 1 1 或 $2 $行。

若对于给定的游戏输入,存在可以让小涵获胜的选将顺序,则输出$ 1$,并另起一行输出所有获胜的情况中,小涵最终选出的武将组合的最大默契值。如果不存在可以让小涵获胜的选将顺序,则输出 0 0 0

样例输入 #1

6 
5 28 16 29 27 
23 3 20 1 
8 32 26 
33 11 
12

样例输出 #1

1
32

样例输入 #2

8 
42 24 10 29 27 12 58 
31 8 16 26 80 6 
25 3 36 11 5 
33 20 17 13 
15 77 9 
4 50 
19

样例输出 #2

1
77

提示

【数据范围】

对于$ 40%$的数据有 N ≤ 10 N≤10 N10

对于$ 70% 的 数 据 有 的数据有 N≤18$。

对于 100 % 100\% 100%的数据有 N ≤ 500 N≤500 N500

思路

计算机的选择是贪心的,永远在想选取与人默契度最高的,所以不论是人还是计算机都拿不到最大,但是人可以选择第二大,这样计算机就永远不可能选到比人还大的,那么人就一定会赢。
那么我们只需看我们能选到的最大默契度的一组是谁。
将默契值按二维数组写出来,答案也就是每行第二大的最大。

代码

#include 
using namespace std;
const int Max=505;
int n;
int mapp[Max][Max];
int main(){
    cin >> n;
    int k=1;
    for(int i = 1; i < n; i++){
        for(int j = i+1; j <= n; j++){
            int x;
            cin >> x;
            mapp[i][j]=x;
            mapp[j][i]=x;
        }
        mapp[i][i]=0;
    }
    mapp[n][n]=0;
    int sec=0;
    for(int i = 1; i <= n; i++){
        sort(mapp[i]+1, mapp[i]+1+n);
        sec=max(sec, mapp[i][n-1]);
    }
    cout << 1 << endl;
    cout << sec << endl;
}

P1007 独木桥

题目背景

战争已经进入到紧要时间。你是运输小队长,正在率领运输部队向前线运送物资。运输任务像做题一样的无聊。你希望找些刺激,于是命令你的士兵们到前方的一座独木桥上欣赏风景,而你留在桥下欣赏士兵们。士兵们十分愤怒,因为这座独木桥十分狭窄,只能容纳 1 1 1 个人通过。假如有 2 2 2 个人相向而行在桥上相遇,那么他们 2 2 2 个人将无法绕过对方,只能有 1 1 1 个人回头下桥,让另一个人先通过。但是,可以有多个人同时呆在同一个位置。

题目描述

突然,你收到从指挥部发来的信息,敌军的轰炸机正朝着你所在的独木桥飞来!为了安全,你的部队必须撤下独木桥。独木桥的长度为 L L L,士兵们只能呆在坐标为整数的地方。所有士兵的速度都为 1 1 1,但一个士兵某一时刻来到了坐标为 0 0 0 L + 1 L+1 L+1 的位置,他就离开了独木桥。

每个士兵都有一个初始面对的方向,他们会以匀速朝着这个方向行走,中途不会自己改变方向。但是,如果两个士兵面对面相遇,他们无法彼此通过对方,于是就分别转身,继续行走。转身不需要任何的时间。

由于先前的愤怒,你已不能控制你的士兵。甚至,你连每个士兵初始面对的方向都不知道。因此,你想要知道你的部队最少需要多少时间就可能全部撤离独木桥。另外,总部也在安排阻拦敌人的进攻,因此你还需要知道你的部队最多需要多少时间才能全部撤离独木桥。

输入格式

第一行共一个整数 L L L,表示独木桥的长度。桥上的坐标为 1 , 2 , ⋯   , L 1, 2, \cdots, L 1,2,,L

第二行共一个整数 N N N,表示初始时留在桥上的士兵数目。

第三行共有 N N N 个整数,分别表示每个士兵的初始坐标。

输出格式

共一行,输出 2 2 2 个整数,分别表示部队撤离独木桥的最小时间和最大时间。 2 2 2 个整数由一个空格符分开。

样例输入 #1

4
2
1 3

样例输出 #1

2 4

提示

对于 100 % 100\% 100% 的数据,满足初始时,没有两个士兵同在一个坐标, 1 ≤ L ≤ 5 × 1 0 3 1\le L\le5\times 10^3 1L5×103 0 ≤ N ≤ 5 × 1 0 3 0\le N\le5\times10^3 0N5×103,且数据保证 N ≤ L N\le L NL

思路

最好的情况:分为左右两半,左边向左走,右边向右走,谁都不会冲突。也即找到每个人最短时间的最大,即最靠近中间的那个人的最短时间。
最坏的情况:每个人会产生冲突,左边的往右走,右边的往左走。也即找到每个人最长时间的最大,即最靠近两边的那个人的最长时间。

代码

#include 
using namespace std;
const int Max=5*1e3+5;
int l;  //独木桥长度
int n;  //桥上士兵数
int mapp[Max];
int minans=0, maxans;
void Min(){
    int left=0,r=l+1;
    for(int i = l/2; i >0; i--){
        if(mapp[i]!=0){
            left=mapp[i];
            break;
        }
    }
    for(int i = l/2+1; i <= l; i++){
        if(mapp[i]!=0){
            r=mapp[i];
            break;
        }
    }
    minans=max(left, l+1-r);

}

void Max1(){
    int left=l+1,r=0;
    for(int i = 1; i <= l/2; i++){
        if(mapp[i]!=0){
            left=mapp[i];
            break;
        }
    }
    for(int i = l; i >= l/2-1; i--){
        if(mapp[i]!=0){
            r=mapp[i];
            break;
        }
    }
    maxans=max(l+1-left, r);
}
int main(){
    cin >> l;
    cin >> n;
    for(int i = 1; i <= n; i++){
        int x;
        cin >> x;
        mapp[x]=x;
    }
    Min();
    Max1();
    cout << minans << " " << maxans;
}

P1223 排队接水

题目描述

n n n 个人在一个水龙头前排队接水,假如每个人接水的时间为 T i T_i Ti,请编程找出这 n n n 个人排队的一种顺序,使得 n n n 个人的平均等待时间最小。

输入格式

第一行为一个整数 n n n

第二行 n n n 个整数,第 i i i 个整数 T i T_i Ti 表示第 i i i 个人的等待时间 T i T_i Ti

输出格式

输出文件有两行,第一行为一种平均时间最短的排队顺序;第二行为这种排列方案下的平均等待时间(输出结果精确到小数点后两位)。

样例输入 #1

10 
56 12 1 99 1000 234 33 55 99 812

样例输出 #1

3 2 7 8 1 4 9 6 10 5
291.90

提示

n ≤ 1000 , t i ≤ 1 0 6 n \leq 1000,t_i \leq 10^6 n1000,ti106,不保证 t i t_i ti 不重复。

t i t_i ti 重复时,按照输入顺序即可(sort 是可以的)

思路

只要让每个人等待时间最少,那么即可得到最短等待时间。
即让用时最少的人最先接水。

代码

#include 
using namespace std;
int N;
struct paixu{
    int ind;  //下标
    int value;  //值
    bool operator <(const paixu b){
        return (value < b.value || (value==b.value && ind< b.ind));
    }
}T[1005];
int main(){
    cin >> N;
    for(int i=1; i<=N; i++){
        cin >> T[i].value;
        T[i].ind=i;
    }
    sort(T+1,T+1+N);
    double all=0;
    for(int i=1; i<=N; i++){
        all+=T[i].value*(N-i);
        cout << T[i].ind << " ";
    }
    cout << endl;
    printf("%.2lf",all/N);
}

P1090 [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G

题目描述

在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。

每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过 n − 1 n-1 n1 次合并之后, 就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。

因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为 1 1 1 ,并且已知果子的种类 数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。

例如有 3 3 3 种果子,数目依次为 1 1 1 2 2 2 9 9 9 。可以先将 1 1 1 2 2 2 堆合并,新堆数目为 3 3 3 ,耗费体力为 3 3 3 。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 12 12 12 ,耗费体力为 12 12 12 。所以多多总共耗费体力 = 3 + 12 = 15 =3+12=15 =3+12=15 。可以证明 15 15 15 为最小的体力耗费值。

输入格式

共两行。
第一行是一个整数 n ( 1 ≤ n ≤ 10000 ) n(1\leq n\leq 10000) n(1n10000) ,表示果子的种类数。

第二行包含 n n n 个整数,用空格分隔,第 i i i 个整数 a i ( 1 ≤ a i ≤ 20000 ) a_i(1\leq a_i\leq 20000) ai(1ai20000) 是第 i i i 种果子的数目。

输出格式

一个整数,也就是最小的体力耗费值。输入数据保证这个值小于 2 31 2^{31} 231

样例输入 #1

3 
1 2 9

样例输出 #1

15

提示

对于 30 % 30\% 30% 的数据,保证有 n ≤ 1000 n \le 1000 n1000

对于 50 % 50\% 50% 的数据,保证有 n ≤ 5000 n \le 5000 n5000

对于全部的数据,保证有 n ≤ 10000 n \le 10000 n10000

思路

每一次挑选最小代价合并果子,合并后在入队
本质上就是最小堆(Huffman树)的应用
我采用优先队列以及最小堆类模板两种方法实现

代码

1.priority_queue(优先队列)

#include 
using namespace std;
const int Max=1e4+5;
int n;
int ans;
priority_queue<int, vector<int>, greater<int> > q;
int main(){
    cin >> n;
    for(int i = 0; i < n; i++){
        int x;
        cin >> x;
        q.push(x);
    }
    while(q.size()>1){
        int f=q.top(); 
        q.pop();
        int s=q.top();
        q.pop();
        ans+=f+s;
        q.push(f+s);
    }
    cout << ans;
}

2.最小堆模板类

#include 
using namespace std;

template <class T>
class MinHeap {	// 最小堆类定义
private:
	T *heap;		// 最小堆元素存储数组
	int currentSize;	// 最小堆当前元素个数
	int maxHeapSize;	// 最小堆最大容量

	void siftDown (int start, int m);  // 调整算法:从 start 开始向下调整到 m
	void siftUp (int start);	// 调整算法:从 start 开始向上调整

public:
	MinHeap(int sz = 10);		// 构造函数:长度为 sz

	MinHeap(T arr[ ], int n);	// 构造函数:用数组 arr[ ] 里的元素创建堆

	~MinHeap( ) { delete [ ]heap; }		// 析构函数

	bool Insert (const T d);			// 插入
	bool Remove (T& d);					// 删除

	bool IsEmpty ( )const;				// 判堆空否
	bool IsFull ( ) const;				// 判堆满否
	void MakeEmpty ( );					// 置空堆

	void Display( )const {
		for(int i = 0; i < currentSize; i++)
			cout << heap[i] << "  ";
		cout << endl;
	}

};

template <class T>
void MinHeap<T>::siftDown (int start, int m ) {
// 私有函数:从结点 start 开始到 m 为止,自上向下比较,
// 将当前start所指的节点放到正确的位置。
	int i = start;
    int j = 2*i + 1;
    T temp=heap[i];
    while(j<=m){
        if(j < m && heap[j] > heap[j+1]) j++;
        if(temp <= heap[j])  break;
        else{
            heap[i]=heap[j];
            i=j;
            j=2*i+1;
        }
    }
    heap[i]=temp;
}

template <class T>
void MinHeap<T>::siftUp(int start) {
// 私有函数:从结点 start 开始到结点 0 为止,自下向上比较,
// 将当前start所指的节点放到正确的位置。
    int j = start, i=(j-1)/2;
    T temp = heap[j];
    while(j>0){
        if(heap[i] > temp){
            heap[j]= heap[i];
            j=i;
            i=(j-1)/2;
        }
        else break;
    }
    heap[j]=temp;
}

template <class T>
MinHeap<T>::MinHeap(int sz) {
// 构造函数:建立大小为 sz 的堆
	maxHeapSize = (10 < sz) ? sz : 10;
	heap = new T[maxHeapSize];	// 创建堆空间
	if (heap == NULL) {
		cerr << "堆存储分配失败!" << endl;	exit(1);
	}
	currentSize = 0;	// 建立当前大小
}

template <class T>
MinHeap<T>::MinHeap (T arr[ ], int n) {
// 构造函数:用数组 arr[ ] 里的元素创建堆
	maxHeapSize = (10 <= n) ? n : 10;
    heap = new T[maxHeapSize];
    if(heap == NULL){
        cerr << "堆存储分配失败!" << endl;
        exit(1);
    }
    currentSize = n;
    for (int i = 0; i < n; i++) heap[i] = arr[i];
    int currentPos = (currentSize - 2)/2;
    while (currentPos >= 0){
        siftDown(currentPos, currentSize-1);
        currentPos--;
    }
}

template <class T>
bool MinHeap<T>::Insert (const T x ) {
// 将 x 插入到最小堆中
    if(currentSize==maxHeapSize){
        cerr << "堆满" << endl;
        return false;
    }
    heap[currentSize]=x;
    siftUp(currentSize);
    currentSize++;
    return true;
}

template <class T>
bool MinHeap<T>::Remove (T& x) {
// 从堆中删除元素,并用 x 返回
    if(currentSize==0){
        return false;
    }
    x=heap[0];
    heap[0]=heap[currentSize-1];
    currentSize--;
    siftDown(0,currentSize-1);
    return true;
}

template <class T>
bool MinHeap<T>::IsEmpty ( ) const {
// 判堆空否
    if(currentSize==0)
	    return  true;
    else return false;
}
template <class T>
bool MinHeap<T>::IsFull ( ) const {
// 判堆满否
    if(currentSize==maxHeapSize)
    	return true;
    else  return false;
}
template <class T>
void MinHeap<T>::MakeEmpty ( ) {
// 置空堆
    currentSize=0;
}

int main(){
	int n;
	MinHeap<int> q(n);
	cin >> n;
	for(int i = 0; i < n; i++){
		int x;
		cin >> x;
		q.Insert(x);
	}
	int ans=0;
	while(n>1){
		int a,b;
		q.Remove(a);
		q.Remove(b);
		ans+=a+b;
		q.Insert(a+b);
		n--;
	}
	cout << ans;
}

本周难点在于思想,针对贪心的思想转变。

你可能感兴趣的:(ACM预备队训练,算法,开发语言,c++,贪心算法,数据结构)