基本算法小结

飞行员兄弟

“飞行员兄弟”这个游戏,需要玩家顺利的打开一个拥有16个把手的冰箱。
已知每个把手可以处于以下两种状态之一:打开或关闭。
只有当所有把手都打开时,冰箱才会打开。
把手可以表示为一个4х4的矩阵,您可以改变任何一个位置[i,j]上把手的状态。
但是,这也会使得第i行和第j列上的所有把手的状态也随着改变。
请你求出打开冰箱所需的切换把手的次数最小值是多少。

可以参考递推与递归中"费解的开关"以及位运算异或的知识。我们把所有可能的情况枚举,将二维矩阵压缩成一维,利用异或更改开关状态。

#include <iostream>
#include <string>
#include <vector>
#include <limits.h>

using namespace std;

//c用来装载输入,state是将输入转化成一维‘0’‘1’数据,check是state的备份,用来核验操作,ans是当前操作次数,answer是最少操作次数
//res是最优操作步骤,temp是当前操作步骤
string c[4];
int state=0, check, ans, answer = INT_MAX;
vector<pair<int, int>> res, temp;

//get函数用来将一维的某位转化成二维的行列,并输出操作当前开关位置应该异或的数字
int get(int index){
    int row, col, num = 0;
    row = index / 4;
    col = index % 4;
    temp.push_back(pair<int, int>(row+1, col+1));
    for(int i=0; i<4; i++){
        num += 1 << row * 4 + i;
    }
    for(int i=0; i<4; i++){
        if(i != row) num += 1 << col + 4 * i;
    }
    
    return num;
}

int main(){
	//输入
    for(int i=0; i<4; i++) cin >> c[i];
	//压缩成一维
    for(int i=0; i<4; i++)
        for(int j=0; j<4; j++)
            if(c[i][j] == '+') state += 1 << (i*4 + j);
    
    //枚举所有可能的操作
    for(int i=0; i<(1<<16); i++){
        ans = 0, check = state;
        temp.clear();
        for(int j=1, index=0; j<=i; index++){
            if(j&i){
                int num = get(index);
                check = check ^ num;//异或1,可以更改开关状态
                ans++;
            }
            j <<= 1;
        }
        //如果最终开关全部打开,比较当前操作数与之前的结果
        if(check == 0){
            if(ans < answer){
                answer = ans;
                res = temp;
            }
        }
    }
    
    cout << answer << endl;
    for(int i=0; i<res.size(); i++) cout << res[i].first << " " << res[i].second << endl;
    
    return 0;
}
占卜DIY

达达学会了使用扑克DIY占卜。
方法如下:
一副去掉大小王的扑克共52张,打乱后均分为13堆,编号1~13,每堆4张,其中第13堆称作“生命牌”,也就是说你有4条命。
这里边,4张K被称作死神。
初始状态下,所有的牌背面朝上扣下。
流程如下:
1.抽取生命牌中的最上面一张(第一张)。
2.把这张牌翻开,正面朝上,放到牌上的数字所对应编号的堆的最上边。(例如抽到2,正面朝上放到第2堆牌最上面,又比如抽到J,放到第11堆牌最上边,注意是正面朝上放)
3.从刚放了牌的那一堆最底下(最后一张)抽取一张牌,重复第2步。(例如你上次抽了2,放到了第二堆顶部,现在抽第二堆最后一张发现是8,又放到第8堆顶部…)
4.在抽牌过程中如果抽到K,则称死了一条命,就扔掉K再从第1步开始。
5.当发现四条命都死了以后,统计现在每堆牌上边正面朝上的牌的数目,只要同一数字的牌出现4张正面朝上的牌(比如4个A),则称“开了一对”,当然4个K是不算的。
6.统计一共开了多少对,开了0对称作”极凶”,12对为“大凶”,3对为“凶”,45对为“小凶”,6对为“中庸”,78对“小吉”,9对为“吉”,1011为“大吉”,12为“满堂开花,极吉”。

#include <iostream>

using namespace std;

int a[14][4], b[14];

int main(){
	//处理输入
    char input;
    for(int i=1; i<=13; i++){
        for(int j=0; j<4; j++){
            cin >> input;
            if(input == '0') a[i][j] = 10;
            else if(input == 'A') a[i][j] = 1;
            else if(input <= '9') a[i][j] = input - '0';
            else if(input == 'J') a[i][j] = 11;
            else if(input == 'Q') a[i][j] = 12;
            else a[i][j] = 13;
        }
    }
    
    //k代表K被抽到几次,res是最终有几对,num是当前牌的数字,temp是备份
    int k = 0, res = 0, num, temp;
    //如果K抽到4次结束
    while(k != 4){
    	//其实num就是最后一堆牌
        num = a[13][k];
        //如果num不是K就循环放牌取牌的操作
        while(num != 13){
        	//b数组是记录这个数字的牌抽到了几次
            b[num]++;
            //对于已经抽出来的牌,数值置为-1
            for(int i=3; i>=0; i--){
                if(a[num][i] != -1){
                    temp = a[num][i];
                    a[num][i] = -1;
                    num = temp;
                    break;
                }
            }
        }
        //循环结束说明抽到了K
        k ++;
    }
    
    //计算有几对
    for(int i=1; i<14; i++)
        if(b[i] == 4) res++;
        
    cout << res;
    
}
分形

分形,具有以非整数维形式充填空间的形态特征。
通常被定义为“一个粗糙或零碎的几何形状,可以分成数个部分,且每一部分都(至少近似地)是整体缩小后的形状”,即具有自相似的性质。
现在,定义“盒子分形”如下:
一级盒子分形:
X
二级盒子分形:
X  XXX  X
如果用B(n - 1)代表第n-1级盒子分形,那么第n级盒子分形即为:
在这里插入图片描述
你的任务是绘制一个n级的盒子分形。

类比递推与递归中分形之城的做法,采用递归,对上一级结果进行平移,在返回给下一级。

#include <iostream>
#include <cmath>

using namespace std;

char res[1000][1000];

void core(int n){
	//边界条件
    if(n == 1){
        res[1][1] = 'X';
        return;
    }
    
    //递归
    core(n-1);
    
    //len是代表上一级图形的长度
    int len = (int)pow(3, n-2);
    //对上一级返回的结果进行平移
    for(int i=1; i<=len; i++){
        for(int j=1; j<=len; j++){
            res[i + 2 * len][j] = res[i][j];
            res[i + len][j + len] = res[i][j];
            res[i][j + 2 * len] = res[i][j];
            res[i + 2 * len][j + 2 * len] = res[i][j];
        }
    }
    
}

int main(){
	//n代表输入级别
    int n;
    while(cin >> n){
    	//n为-1结束
        if(n == -1) break;
        else core(n);
        //len是图形长度
        int len = (int)pow(3, n-1);
        for(int i=1; i<=len; i++){
            for(int j=1; j<=len; j++){
            	//'\0'是字符数组的默认值
                if(res[i][j] == '\0') cout << " ";
                else cout << res[i][j];
            }
            cout << endl;
        }
        cout << '-' << endl;
    }
    
    return 0;
}
袭击

在与联盟的战斗中屡战屡败后,帝国撤退到了最后一个据点。
依靠其强大的防御系统,帝国击退了联盟的六波猛烈进攻。
经过几天的苦思冥想,联盟将军亚瑟终于注意到帝国防御系统唯一的弱点就是能源供应。
该系统由N个核电站供应能源,其中任何一个被摧毁都会使防御系统失效。
将军派出了N个特工进入据点之中,打算对能源站展开一次突袭。
不幸的是,由于受到了帝国空军的袭击,他们未能降落在预期位置。
作为一名经验丰富的将军,亚瑟很快意识到他需要重新安排突袭计划。
他现在最想知道的事情就是哪个特工距离其中任意一个发电站的距离最短。
你能帮他算出来这最短的距离是多少吗?

采用分治的方法,按照x坐标排序,均分成两部分,分别计算两部分的距离最小值,记为d。对于两部分的最小值我们只需要考虑分界线两侧+d和-d范围内的点即可,因为距离分界线远的点必然会超出当前的距离最小值。然后对范围内的点按照y方向排序,根据鸽巢原理我们可以证明遍历每个点的时候最多只需要考虑6个点即可,并且y方向排序可以采用归并排序,因此最终时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)。表示成下图
在这里插入图片描述
灰虚线表示将当前排序的点均分,灰实线表示距离分界线d的范围,d是两部分距离的最小值,在合并的时候只需考虑灰实线范围内的点的距离即可,只有这部分内的点距离可能会小于d。对于灰实线范围内的点,按照y轴排好序后,遍历每个点身边的常数个点,更新距离最小值并返回,对于每个点最多只需考虑周围的6个点,我们可以利用鸽巢原理表示成下图。
在这里插入图片描述

对于一个点,我们只需考虑以其为圆心,半径为d的半圆内的点,只有该范围的点到圆心的距离才可能小于d,如上图蓝色半圆所示,当我们遍历到红色的点的时候只需考虑该半圆内的点。为了方便求解,把半圆简化成矩形,将矩形分割成6个小部分,我们可以证明该矩形范围内不会超过6个点,因为每个小矩形的对角线的长度小于d,因此若矩形内落入7个以上的点,必有2点落入同一小矩形,那么两点的距离小于d,就不满足d是最小距离的前提条件,所以每一层递归遍历最多是 O ( 6 n ) O(6n) O(6n),6是常数项,可以省去。

#include <iostream>
#include <algorithm>
#include <cmath>
#include <limits.h>

using namespace std;

//储存每一个点的信息,包括x,y,type表示是核电站还是特工,重载比较符,方便排序。
struct info{
    double x, y;
    int type;
    bool operator< (const info &i) const{
        return x < i.x;
    }
}point[200010], temp[200010];

//计算两个点的距离,若是同一阵营(type)就返回无穷,否则计算欧式距离
double dis(info i, info j){
    if(i.type == j.type) return INT_MAX;
    double dx = i.x - j.x, dy = i.y - j.y;
    return sqrt(dx*dx + dy*dy);
}

//递归
double dfs(int l, int r){
	//边界条件,mid是中值,mid_x存储中线x坐标,递归两部分,返回距离最小值res
    if(l>=r) return INT_MAX;
    int mid = l + r >> 1;
    double mid_x = point[mid].x;
    double res = min(dfs(l, mid), dfs(mid+1, r));
    
    //归并排序,排序y值
    int f = l, b = mid+1;
    for(int i=l; i<=r; i++){
        if(f > mid || b <= r && point[f].y > point[b].y) temp[i] = point[b++];
        else temp[i] = point[f++];
    }
    
    for(int i=l; i<=r; i++) point[i] = temp[i];

	//统计处于正负d范围内的点。
    int index = 0;
    for(int i=l; i<=r; i++){
        if(point[i].x <= mid_x + res && point[i].x >= mid_x - res)
            temp[index++] = point[i];
    }
    
    //遍历范围内的点,更新res
    for(int i=0; i<index; i++){
        for(int j=i-1; j>=0 && temp[j].y + res >= temp[i].y; j--)
            res = min(res, dis(temp[j], temp[i]));
    }
    return res;
}

int main(){
	//t表示测试集数量,n是特工和核电站的数量
    int t, n;
    cin >> t;
    while(t--){
        cin >> n;
        for(int i=0; i<n; i++){
            cin >> point[i].x >> point[i].y;
            point[i].type = 0;
        }
        for(int i=n; i<n*2; i++){
            cin >> point[i].x >> point[i].y;
            point[i].type = 1;
        }
        
        //排序
        sort(point, point+2*n);
       	
        int l = 0, r = 2*n - 1;
        double res = dfs(l, r);
        printf("%.3f\n", res);
    }
    
    return 0;
}
防线

达达学习数学竞赛的时候受尽了同仁们的鄙视,终于有一天…受尽屈辱的达达黑化成为了黑暗英雄怪兽达达。
就如同中二漫画的情节一样,怪兽达达打算毁掉这个世界。
数学竞赛界的精英 lqr 打算阻止怪兽达达的阴谋,于是她集合了一支由数学竞赛选手组成的超级行动队。
由于队员们个个都智商超群,很快,行动队便来到了怪兽达达的黑暗城堡的下方。
但是,同样强大的怪兽达达在城堡周围布置了一条“不可越过”的坚固防线。
防线由很多防具组成,这些防具分成了 N 组。
我们可以认为防线是一维的,那么每一组防具都分布在防线的某一段上,并且同一组防具是等距离排列的。
也就是说,我们可以用三个整数 S, E 和 D 来描述一组防具,即这一组防具布置在防线的 S,S + D,S + 2D,…,S + KD(K∈ Z,S + KD≤E,S + (K + 1)D>E)位置上。
黑化的怪兽达达设计的防线极其精良。如果防线的某个位置有偶数个防具,那么这个位置就是毫无破绽的(包括这个位置一个防具也没有的情况,因为 0 也是偶数)。
只有有奇数个防具的位置有破绽,但是整条防线上也最多只有一个位置有奇数个防具。
作为行动队的队长,lqr 要找到防线的破绽以策划下一步的行动。
但是,由于防具的数量太多,她实在是不能看出哪里有破绽。作为 lqr 可以信任的学弟学妹们,你们要帮助她解决这个问题。

暴力的算法是将所有输入填入一个数组,但这样的时间复杂度太高,最多能达到 O ( l e n n ) O(len^{n}) O(lenn),len是数组长度。
题干中“整条防线上也最多只有一个位置有奇数个防具”,提醒我们可以利用前缀和的思想,当某一个位置之前所有的防具总数是奇数,说明奇数防具的位置在该位置之前。因此可以采用二分查找,以前缀和条件来判断,因此总的时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)。对于每种防具在某一位置之前的数量可以用 ( m i n ( p o s , E ) − S ) / d + 1 (min(pos, E) - S)/d + 1 (min(pos,E)S)/d+1来表示。

#include <iostream>
#include <algorithm>

using namespace std;

//t是测试集数量,n是防具种数
int t, n;

//存储防具信息
struct info{
    int s, e, d;
}defence[200010];

//计算防具个数
long long check(int mid){
    long long sum = 0;
    for(int i=0; i<n; i++)
        if(mid >= defence[i].s) sum += (min(mid, defence[i].e) - defence[i].s) / defence[i].d + 1;
    return sum;
}

int main(){ 
    cin >> t;
    while(t--){
        cin >> n;
        int len = 0;
        for(int i=0; i<n; i++){
            cin >> defence[i].s >> defence[i].e >> defence[i].d;
            len = max(defence[i].e, len);
        }
        //二分查找
        int l = 0, r = len, mid;
        while(l < r){
            mid = l + r >> 1;
            if(check(mid) % 2) r = mid;
            else l = mid + 1;
        }
        if(r != len) cout << r << " " << check(r) - check(r-1) << endl;
        else cout << "There's no weakness." << endl;
    }
    
    return 0;
}
赶牛入圈

农夫约翰希望为他的奶牛们建立一个畜栏。
这些挑剔的畜生要求畜栏必须是正方形的,而且至少要包含C单位的三叶草,来当做它们的下午茶。
畜栏的边缘必须与X,Y轴平行。
约翰的土地里一共包含N单位的三叶草,每单位三叶草位于一个1 x 1的土地区域内,区域位置由其左下角坐标表示,并且区域左下角的X,Y坐标都为整数,范围在1到10000以内。
多个单位的三叶草可能会位于同一个1 x 1的区域内,因为这个原因,在接下来的输入中,同一个区域坐标可能出现多次。
只有一个区域完全位于修好的畜栏之中,才认为这个区域内的三叶草在畜栏之中。
请你帮约翰计算一下,能包含至少C单位面积三叶草的情况下,畜栏的最小边长是多少。

因为输入坐标范围太大,我们可以先将其离散化。利用前缀和可以快速求解某个正方形内草的数量。但是朴素的算法复杂度有 O ( n 4 ) O(n^{4}) O(n4),为了降低时间,可以采用二分的方法。对于正方形的边长查找,用前缀和作为判定条件,可以将时间复杂度降低到 O ( n 2 l o g n ) O(n^{2}logn) O(n2logn)

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

//grass表示草坪前缀和,c是草的最低数量,n是输入坐标数,axis存储输入坐标,trans是离散化
const int N=1010;
int grass[N][N], c, n;
pair<int, int> axis[N];
vector<int> trans;

//判断当前正方形长度是否满足条件
bool check(int len){
    int a = 0, b = 0, num;
    for(int i=0; i<trans.size(); i++){
        while(a < i && trans[i] - trans[a + 1] + 1 > len) a++;
        b = 0;
        for(int j=0; j<trans.size(); j++){
            while(b < j && trans[j] - trans[b + 1] + 1 > len) b++;
            num = grass[i][j] + grass[a][b] - grass[a][j] - grass[i][b];
            if(num >= c) return true;
        }
    }
    return false;
}

//二分找到离散化后的坐标
int get(int num){
    int l = 0, r = trans.size() - 1, mid;
    while(l < r){
        mid = l + r >> 1;
        if(trans[mid] >= num) r = mid;
        else l = mid + 1;
    }
    return r;
}

int main(){
	//max_len存储最大长度 
    int max_len = 0;
    cin >> c >> n;
    trans.push_back(0);
    for(int i=0; i<n; i++){
        cin >> axis[i].first >> axis[i].second;
        trans.push_back(axis[i].first);
        trans.push_back(axis[i].second);
        max_len = max(axis[i].first > axis[i].second? axis[i].first: axis[i].second, max_len);
    }
    
    //去重
    sort(trans.begin(), trans.end());
    trans.erase(unique(trans.begin(), trans.end()), trans.end());

    for(int i=0; i<n; i++) grass[get(axis[i].first)][get(axis[i].second)]++;
	
	//求解前缀和
    for(int i=1; i<trans.size(); i++)
        for(int j=1; j<trans.size(); j++)
            grass[i][j] = grass[i-1][j] + grass[i][j-1] - grass[i-1][j-1] + grass[i][j];
	
	//二分查找边长
    int l = 0, r = max_len, mid;
    while(l < r){
        mid = l + r >> 1;
        if(check(mid)) r = mid;
        else l = mid + 1;
    }
    
    cout << r;
    
    return 0;
}
糖果传递

有n个小朋友坐成一圈,每人有a[i]个糖果。
每人只能给左右两人传递糖果。
每人每次传递一个糖果代价为1。
求使所有人获得均等糖果的最小代价。

类比贪心算法中的均分纸牌问题。

#include <iostream>
#include <algorithm>

using namespace std;

long long child[1000010];

int main(){
	//n是孩子个数,num是糖果数量
    int n, num;
    long long sum = 0;
    cin >> n;
    //形成前缀和
    for(int i=1; i<=n; i++){
        cin >> num;
        child[i] = num + child[i-1];
    }
    
    //求出均值
    long long avg = child[n] / n;
    
    for(int i=1; i<=n; i++) child[i] -= i * avg;
    
    sort(child + 1, child + n);
    
    //中位数问题
    for(int i=1; i<=n; i++) sum += abs(child[n + 1 >> 1] - child[i]);
    
    cout << sum;
    
    return 0;
}
士兵

格格兰郡的N名士兵随机散落在全郡各地。
格格兰郡中的位置由一对(x,y)整数坐标表示。
士兵可以进行移动,每次移动,一名士兵可以向上,向下,向左或向右移动一个单位(因此,他的x或y坐标也将加1或减1)。
现在希望通过移动士兵,使得所有士兵彼此相邻的处于同一条水平线内,即所有士兵的y坐标相同并且x坐标相邻。
请你计算满足要求的情况下,所有士兵的总移动次数最少是多少。
需注意,两个或多个士兵不能占据同一个位置。

类比排序算法的中位数问题。我们可以将x和y分开求解,y直接找到中位数即可,而x我们要转化一下,先将x排序,然后假设我们要排列的起始位置是a,那么目标位置就变成 a , a + 1 , a + 2 , ⋅ ⋅ ⋅ , a + n a,a+1,a+2,···,a+n a,a+1,a+2,,a+n,于是我们要求解的最下值问题变成:
x i − a − i + 1 x_{i} - a - i + 1 xiai+1
x i ′ = x i − i + 1 x'_{i} = x_{i} - i + 1 xi=xii+1, 则转变成
x i ′ − a x'_{i} - a xia
便可利用中位数方法求解。

#include <iostream>
#include <algorithm>

using namespace std;

long long x[10010], y[10010];

int main(){
	//n是士兵数量
    int n;
    cin >> n;
    for(int i=1; i<=n; i++){
        cin >> x[i] >> y[i];
    }

    sort(x+1, x+n+1);
    sort(y+1, y+n+1);
    
    //计算y的中位数,以及对x处理
    long long sum = 0;
    for(int i=1; i<=n; i++){
        sum += abs(y[n + 1 >> 1] - y[i]);
        x[i] -= i - 1;
    }
    
    sort(x+1, x+n);
    
    //计算x的中位数
    for(int i=1; i<=n; i++){
        sum += abs(x[n + 1 >> 1] - x[i]);
    }
    
    cout << sum;
    
    return 0;
}
数的进制转换

编写一个程序,可以实现将一个数字由一个进制转换为另一个进制。
这里有62个不同数位{0-9,A-Z,a-z}。

可以先转换成10进制,再转换成要求进制。
也可以直接利用input进制,除以output进制,得到的余数就是最终结果。

#include <iostream>
#include <string>
#include <vector>

using namespace std;

int t, input, output;
string s;
vector<int> num, res, temp;

//将输入字符串转换成数字
void trans(){
    res.clear();
    num.clear();
    for(char c : s){
        if(c >= '0' && c <= '9') num.push_back(c - '0');
        else if(c >= 'A' && c <= 'Z') num.push_back(c - 'A' + 10);
        else num.push_back(c - 'a' + 36);
    }
}

//高精度除法
void solve(){
    while(num.size()){
        bool start = false;
        int flag = 0;
        temp.clear();
        for(int i=0; i<num.size(); i++){
            flag = num[i] + flag * input;
            if(flag / output){
                temp.push_back(flag / output);
                flag %= output;    
                start = true;
            }
            else{
                if(start) temp.push_back(0);
            }
        }
        num = temp;
        res.insert(res.begin(), flag);
    }
}

void out(){
    for(int number : res){
        if(number >= 0 && number <= 9) cout << number;
        else if(number >= 10 && number <= 35) cout << (char)('A' + number - 10);
        else cout << (char)('a' + number - 36);
    }
    cout << endl;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    cin >> t;
    while(t--){
        cin >> input >> output;
        cin >> s;
        trans();
        solve();
        cout << input << " " << s << endl;
        cout << output << " ";
        out();
        cout << endl;
    }
    
    return 0;
}
耍杂技的牛

农民约翰的N头奶牛(编号为1…N)计划逃跑并加入马戏团,为此它们决定练习表演杂技。
奶牛们不是非常有创意,只提出了一个杂技表演:
叠罗汉,表演时,奶牛们站在彼此的身上,形成一个高高的垂直堆叠。
奶牛们正在试图找到自己在这个堆叠中应该所处的位置顺序。
这N头奶牛中的每一头都有着自己的重量Wi以及自己的强壮程度Si。
一头牛支撑不住的可能性取决于它头上所有牛的总重量(不包括它自己)减去它的身体强壮程度的值,现在称该数值为风险值,风险值越大,这只牛撑不住的可能性越高。
您的任务是确定奶牛的排序,使得所有奶牛的风险值中的最大值尽可能的小。

类比贪心算法中的国王游戏,该题最终导出依据 W i + S i W_{i} + S_{i} Wi+Si排序。

#include <iostream>
#include <algorithm>
#include <limits.h>

using namespace std;

pair<int, int> cow[50010];

int main(){
    int n, w, s;
    cin >> n;
    for(int i=0; i<n; i++){
        cin >> w >> s;
        cow[i].first = w + s;
        cow[i].second = s;
    }
    
    sort(cow, cow+n);
    
    long long sum = 0, res = INT_MIN;
    for(int i=0; i<n; i++){
        res = max(res, sum - cow[i].second);
        sum += cow[i].first - cow[i].second;
    }
    
    cout << res;
    
    return 0;
}
最大的和

给定一个包含整数的二维矩阵,子矩形是位于整个阵列内的任何大小为1 * 1或更大的连续子阵列。
矩形的总和是该矩形中所有元素的总和。
在这个问题中,具有最大和的子矩形被称为最大子矩形。

我们先考虑对于给定的一个数列,如何求出其最大连续子数列的和。可以采用动态规划的思想,遍历数列并累加和,当该和值小于0的时候我们就舍弃,重新累加,因为如果之前数列的和值小于0,会拉低后续数列的值。
d p [ i ] = m a x ( d p [ i − 1 ] , 0 ) + n u m [ i ] dp[i] = max(dp[i-1], 0) + num[i] dp[i]=max(dp[i1],0)+num[i]
那么对于二维数组,我们可以通过前缀和的方式加速求解,同时限定 x m i n x_{min} xmin x m a x x_{max} xmax,在某些行内遍历所有列,这样可以使得算法复杂度从 O ( n 4 ) O(n^{4}) O(n4)降低到 O ( n 3 ) O(n^{3}) O(n3)。表示成下图。
在这里插入图片描述
遍历所有可能的 x m i n x_{min} xmin x m a x x_{max} xmax,限定浅灰色的范围。在该范围内对于某一列的和,即阴影部分的和,可以采用前缀和快速求解,即蓝色减去绿色。再配合动态规划求解。

#include <iostream>
#include <limits.h>

using namespace std;

int nums[110][110];

int main(){
	//n表示边长,num是二维数组
    int n;
    cin >> n;
    for(int i=1; i<=n; i++)
        for(int j=1; j<=n; j++)
            cin >> nums[i][j];
    
    //形成前缀和
    for(int i=1; i<=n; i++)
        for(int j=1; j<=n; j++)
            nums[i][j] += nums[i-1][j];
	
	//限定行,遍历列
    int res = INT_MIN, sum;
    for(int i=1; i<=n; i++){
        for(int j=0; j<i; j++){
            sum = 0;
            for(int k=1; k<=n; k++){
                sum += nums[i][k] - nums[j][k];
                res = max(res, sum);
                if(sum < 0) sum=0;
            }
        }
    }
    
    cout << res;
    
    return 0;
}
任务

今天某公司有M个任务需要完成。
每个任务都有相应的难度级别和完成任务所需时间。
第i个任务的难度级别为yi,完成任务所需时间为xi分钟。
如果公司完成此任务,他们将获得(500 * xi + 2 * yi)美元收入。
该公司有N台机器,每台机器都有最长工作时间和级别。
如果任务所需时间超过机器的最长工作时间,则机器无法完成此任务。
如果任务难度级别超过机器的级别,则机器无法完成次任务。
每台机器一天内只能完成一项任务。
每个任务只能由一台机器完成。
请为他们设计一个任务分配方案,使得该公司能够最大化他们今天可以完成的任务数量。
如果有多种解决方案,他们希望选取赚取利润最高的那种。

对于每个任务获得的收入,x的占比要大于y,因此我们应当优先完成x大的任务,可以将x从大到小遍历,对于每个任务,选取符合条件的最小的y,可以参考贪心算法中的防晒。

#include <iostream>
#include <algorithm>
#include <set>

using namespace std;

pair<int, int> mch[100010], task[100010];

int main(){
	//n代表机器数量,m代表任务数量
    int n, m;
    cin >> n >> m;
    for(int i=0; i<n; i++) cin >> mch[i].first >> mch[i].second;
    for(int i=0; i<m; i++) cin >> task[i].first >> task[i].second;
    
    //按照先x后y的顺序排序
    sort(mch, mch+n);
    sort(task, task+m);
    
    long long res = 0;
    int cnt = 0, index = n-1;
    //存储满足大于x的集合,再利用lower_bound二分查找符合y的最小值
    multiset<int> s;
    for(int i=m-1; i>=0; i--){
        while(mch[index].first >= task[i].first) s.insert(mch[index--].second);
        auto iter = s.lower_bound(task[i].second);
        if(iter != s.end()){
            cnt++;
            res += task[i].first * 500 + task[i].second * 2;
            s.erase(iter);
        }
    }
    
    cout << cnt << " " << res;
    
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值