疯狂队列问题

1. 题目

  • 小易老师是非常严厉的,它会要求所有学生在进入教室前都排成一列,并且他要求学生按照身高不递减的顺序排列。有一次,n个学生在列队的时候,小易老师正好去卫生间了。学生们终于有机会反击了,于是学生们决定来一次疯狂的队列,他们定义一个队列的疯狂值为每对相邻排列学生身高差的绝对值总和。由于按照身高顺序排列的队列的疯狂值是最小的,他们当然决定按照疯狂值最大的顺序来进行列队。现在给出n个学生的身高,请计算出这些学生列队的最大可能的疯狂值。小易老师回来一定会气得半死。
  • 输入描述:
    输入包括两行,第一行一个整数n(1 ≤ n ≤ 50),表示学生的人数
    第二行为n个整数h[i](1 ≤ h[i] ≤ 1000),表示每个学生的身高
  • 输出描述:
    输出一个整数,表示n个学生列队可以获得的最大的疯狂值。
  • 如样例所示:
    当队列排列顺序是: 25-10-40-5-25, 身高差绝对值的总和为15+30+35+20=100。
    这是最大的疯狂值了。

2. 分析

  我首先想到的是回溯解法,8分钟写完了代码,但是因为时长问题,AC了百分之50的CASE,当然该算法是正确的,但是回溯的缺点就是耗时大,时间复杂度为 O ( n ! )
  第二种解法贪婪。贪婪问题即是简单的,又是复杂的。如果你知道贪婪策略,那么这个题就是简单的,否则,想破脑袋恐怕也想不出来。想要疯狂值最大,只要交错安排队列的顺序,保证相邻两个人的身高差是最大的:

  • 以身高最小的人A为中心,然后安排身高最大的两个人B、C(B < C)分别站在左边和右边
  • 再以C为中心,找到剩余人中身高最小的D站在C的右边;以B为中心,剩余身高最小的人E站在B的左边。显然E>D。
  • 最终如果两端最后的两个数相等,需要微调。

3. 实现

回溯

#include<iostream>
#include<vector>
using namespace std;
void dfs(vector<int> & nums,vector<int> & flags,int & ans,vector<int> tmp){
    if(tmp.size()==nums.size()){
        int sum=0;
        for(int i=1;i<tmp.size();++i)
            sum+=abs(tmp[i]-tmp[i-1]);
        if(ans<sum)
            ans=sum;
        return ;
    }
    for(int i=0;i<nums.size();++i)
        if(flags[i] != 1){
            flags[i]=1;
            tmp.push_back(nums[i]);
            dfs(nums,flags,ans,tmp);
            tmp.pop_back();
            flags[i]=0;
        }
}
int main (void){
    vector<int > nums;
    vector<int > flags;
    int tmp,tmp2,ans=0;
    while(cin>>tmp){
        for(int i=0;i<tmp;++i){
            cin>>tmp2;
            nums.push_back(tmp2);
        }
        flags.resize(tmp);
        dfs(nums,flags,ans,{});
        cout<<ans<<endl;
    }
}

贪婪

#include<iostream>
#include<vector>
#include<algorithm>
#include<deque>
using namespace std;
int greed(vector<int> & nums) {
    deque<int> ans;
    sort(nums.begin(), nums.end());
    int left = 0, right = nums.size() - 1, flag = 0;
    ans.push_back(nums[left++]);
    //因为right--的特点,所以right==left的时候也是需要处理的。
    for (; left<=right;) {
        if (!flag) {
            ans.push_back(nums[right--]);
            //这个判断条件容易忽略,造成越界访问,因为输入数据有可能为偶数个。
            if (left > right)
                break;
            ans.push_front(nums[right--]);
            flag = 1;
        }
        else {
            ans.push_back(nums[left++]);
            //同样需要,可以举2和4的简单例子快速判定这点。
            if (left > right)
                break;
            ans.push_front(nums[left++]);
            flag = 0;
        }
    }
    //可以通过下面的方式减少思考量,不论是那种情况都可以妥善处理
    if (ans[0] == ans[1]) {
        ans.push_back(ans.front());
        ans.pop_front();
    }
    if (ans[ans.size() - 1] == ans[ans.size() - 2]) {
        ans.push_front(ans.back());
        ans.pop_back();
    }
    int sum = 0;
    for (int i = 1; i<ans.size(); ++i) {
        sum += abs(ans[i] - ans[i - 1]);
    }
    return sum;

}
int main(void) {
    vector<int > nums;
    int tmp, tmp2;
    while (cin >> tmp) {
        for (int i = 0; i<tmp; ++i) {
            cin >> tmp2;
            nums.push_back(tmp2);
        }
        cout << greed(nums) << endl;
        nums = {};
    }
}

4. 小结

  deque两端队列在本题的场景非常方便,另外对于贪婪解法的一些实现上的技巧如注释所示。最后可以使用贪婪算法的地方,理论上都可以使用DP,这个题的DP解法可以再多思考下。

猜你喜欢

转载自blog.csdn.net/LoveStackover/article/details/81194656