程序员进阶之算法练习(五十一)

正文

题目1

题目链接
题目大意:
给出一个图形,下面是n=1、2、3、4的时候:

现在需要把上面的图形染色,由若干个菱形组成;
问,有多少种染色方法?

输入:
第一行,整数𝑡表示有t个样例数量 (1≤𝑡≤1000)
接下来每个样例一行,整数𝑛 (1≤𝑛≤10^9).

输出:
每个样例一行,染色的方法数量。

Examples
input
2
2
1
output
2
1
样例解释:
对于样例1,当n=2的时候一共有2种染色方法:


对于样例2,当n=1的时候,只有1种染色方法:

题目解析:
找规律的问题,先从n=1开始考虑,只有一种方案;
n=2的时候,我们可以采用染色方案1,将第一个竖着的菱形染色;也可以先上下斜着放,将第二个竖着的菱形染色;
同理n=3时,有将第1、2、3个菱形染色的方案;
总结规律, ans=n;

代码实现:


int main(int argc, const char * argv[]) {
    // insert code here...
    
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        cout << n << endl;
    }   
    
    return 0;
}

题目2

题目链接
题目大意:
有n个砝码,每个砝码的重量是一样的;
由于砝码的重量标识已经模糊,只能大概知道的重量区间是在 [a-b, a+b]这个区间内;
现在想知道,这n个砝码的重量,能否在区间[c-d, c+d]内;
如果可以,则输出YES;如果不可以,则输出NO;

输入:
第一行,整数𝑡表示有t个样例数量 (1≤𝑡≤1000)
接下来每个样例一行,5个整数 n,𝑎,𝑏,𝑐,𝑑 (1≤𝑛≤1000, 0≤𝑏<𝑎≤1000, 0≤𝑑<𝑐≤1000)

输出:
每个样例一行,如果可以,则输出YES;如果不可以,则输出NO;

Examples
input
5
7 20 3 101 18
11 11 10 234 2
8 9 7 250 122
19 41 21 321 10
3 10 8 6 1

output
Yes
No
Yes
No
Yes

题目解析:
因为a和b比较小,枚举下a和b的区间,可以解决问题;

但是假如,a和b很大呢?(0≤𝑏<𝑎≤10^9, 0≤𝑑<𝑐≤10^9)
我们用[l, r]来表示[c-d, c+d]的区间;
我们用[gapL, gapR] 来表示[(a-b)n, (a+b)n]的区间;
只要这俩个区间有重叠,则表示存在可能性;
这样就不用枚举,复杂度从O(N^2)变成O(1)。

代码实现:


int main(int argc, const char * argv[]) {
    // insert code here...
    
    int t;
    cin >> t;
    while (t--) {
        lld n, a, b, c, d;
        cin >> n >> a >> b >> c >> d;
        lld l = (c - d), r = (c + d);
        lld ans = -1;
        lld gapL = (a - b) * n, gapR = (a + b) * n;
        bool ok = 0;;
        if (gapL <= l && l <= gapR) {
            ok = 1;
        }
        else if (gapL <= r && r <= gapR) {
            ok = 1;
        }
        else if (l <= gapL && gapL <= r) {
            ok = 1;
        }
        else if (l <= gapR && gapR <= r) {
            ok = 1;
        }
        if (ok) {
            cout << "YES" << endl;
        }
        else {
            cout << "NO" << endl;
        }
    }   
    
    return 0;
}

题目3

题目链接
题目大意:
有n个整数组成的数组,现在对数组的元素重新组织,希望新的数组满足:
|𝑎[1]−𝑎[2]| ≤ |𝑎[2]−𝑎[3]| ≤ … ≤ |𝑎[𝑛−1]−𝑎[𝑛]|
求新的数组。

输入:
第一行,整数𝑡表示有t个样例数量 (1≤𝑡≤1000)
接下来每t个样例,第一行, 整数𝑛 (3≤𝑛≤10^5)
第二行是n个整数,𝑎1,𝑎2,…,𝑎𝑛 (−10^9≤𝑎𝑖≤10^9).

输出:
每个样例一行,新数组的n个整数;

Examples
input
2
6
5 -2 4 8 6 5
4
8 1 4 2
output
5 5 4 6 8 -2
1 2 4 8

题目解析:
假设n个数字散落在一维数轴上,那么任意两个数字的绝对值之差,就是两个数字在数轴之间的间距;
题目的问题转化为,求一个排列,使得相邻两个数字的间距越来越大;
假设从小到大排序完之后,数组是a[N];容易知道,所有数字中间距的是a[n]-a[1],那么可以将这个数字放到最右边;
同理,第二大的数字要么是a[1]和a[n-1],要么是a[2]和a[n],我们任取其中一种,假设是a[1]和a[n-1];
同理,第三大的数字就是a[2]和a[n-1];
同理,第四大的数字就是a[3]和a[n-1];
如此交替选择,会使得间距越来越小,得到了一种满足题意的解法;

代码实现:

int a[N];
int ans[N];

int main(int argc, const char * argv[]) {
    // insert code here...
    
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        for (int i = 0; i < n; ++i) cin >>a[i];
        sort(a, a + n);
        int l = 0, r = n - 1, k = n - 1;
        while (l <= r) {
            ans[k--] = a[r--];
            if (l <= r) {
                ans[k--] = a[l++];
            }
        }
        for (int i = 0; i < n; ++i) {
            cout << ans[i] << " ";
        }
        cout << endl;
    }   
    
    return 0;
}

题目4

题目链接
题目大意:
n个数字的数组,如果某个数字比相邻左右两个数字大,则称为峰;

从n个数字里面选出连续的k个数字,希望包括尽可能多的山峰;
如果有多种可能,使得第一个位置尽可能小;

输入:
第一行,整数𝑡表示有t个样例数量 (1≤𝑡≤1000)
接下来每个样例第一行,整数n和k (3≤𝑘≤𝑛≤2⋅1e5)
第二行n个整数, 𝑎1,𝑎2,…,𝑎𝑛 (0≤𝑎𝑖≤1e9, 𝑎𝑖 ≠ 𝑎[𝑖+1])

输出:
每个样例一行,输出整数t和l,分别表示k个连续数字最多能被峰分为几部分,以及区间开始位置;

Examples
input
2
8 6
1 2 4 1 2 4 1 2
5 3
3 2 3 2 1

output
3 2
2 2

样例解释:
样例一选择的数字区间是[2, 7],峰的数字有4和4,可以把区间分为3部分,区间开始位置是2;
样例二的区间是[2, 4],峰的数字只有3,可以把区间氛围2部分,区间开始位置是2;
题目解析:
先找出所有的山峰,假设是m个,遍历这些山峰;
对于第1个山峰,直接放入队列q;
对于第2个山峰,直接放入队列q,接下来判断队列中的距离是否超过k:如果满足q.back() - q.front() + 2 >= k 则表示队列中山峰已经无法用连续的k个数字来包括;
此时需要pop掉队头的山峰,也是最早加入的数字。

每加入一个数字到山峰,检查完队列数字的合法性,接着计算这个队列的结果是否更优;

代码实现:

int a[N];
queue<int> q;

int main(int argc, const char * argv[]) {
    // insert code here...
    
    int t;
    cin >> t;
    while (t--) {
        int n, k;
        cin >> n >> k;
        for (int i = 0; i < n; ++i) {
            cin >> a[i];
        }
        while (!q.empty()) {
            q.pop();
        }
        
        int ans = 0, start = 0;
        for (int i = 1; i < n - 1; ++i) {
            if (a[i] > a[i - 1] && a[i] > a[i + 1]) {
                q.push(i);
                while (q.back() - q.front() + 2 >= k) {
                    q.pop();
                }
                if (q.size() > ans) {
                    ans = q.size();
                    start = q.back() + 1 - (k - 1);
                }
            }
        }
        cout << ans + 1 << " " << max(0, start) + 1 << endl;
    }   
    
    return 0;
}

题目5

题目链接
题目大意:
给出n个整数的数组,现在每秒可以对数组进行一次操作:
第x秒,可以从数组中选择出来若干个数,将每个数加上2^(x-1);

现在希望数组满足𝑎1≤𝑎2≤…≤𝑎𝑛 ,问最少需要多少秒;

输入:
第一行,整数𝑡表示有t个样例数量 (1≤𝑡≤1000)
接下来t个样例,第一行,整数𝑛 (1≤𝑛≤10^5)
第二行,n个整数 𝑎1,𝑎2,…,𝑎𝑛 (−109≤𝑎𝑖≤109).

输出:
最少需要秒数;

Examples
input
3
4
1 7 6 5
5
1 2 3 4 5
2
0 -4
output
2
0
3

题目解析:
按照题意,分别可以加上数字1、2、4、8、16、、、
由于数字递增很快(指数级),可以遇见不会添加很多次;
那么可以考虑暴力来解决,枚举x=0、1、2、3、4、5、6的情况是否满足要求,每次枚举的复杂度是O(NLogM);M是数字大小,最多枚举LogM次;

接着,需要找一种能快速验证,当x=k的时候,是否满足要求;
由贪心的思想,我们知道对于数字a[n],由于预期a[n]是最大的数字,可以直接将所有的数字加到a[n]上;
对于数字a[n-1],我们希望a[n-1]在满足a[n-1]<=a[n]的情况下,尽可能的大;
同理,我们希望a[n-2]、a[n-3]、、、都是如此的处理;

那么问题变成,如何保证a[i-1]在满足a[i-1]<=a[i]的情况下,a[i-1]尽可能的大?
答案就是:从大到小的加数字(2^(x-1)),直到数字无法添加,此时数字就是最大;

注意,这里不是从小到大;比如说数字6变成12,如果从大到小有6+4+2=12,但是如果变成从小开始,则会出现取了数字1、2之后,无法取4,最大值就是9;

代码实现:

typedef long long lld;
lld p[N];
lld a[N];

bool check(int n, int k) {
    lld last = llinf;
    for (int i = n - 1; i >= 0; --i) {
        lld tmp = a[i];
        for (int j = k; j >= 1; --j) {
            if (tmp + p[j-1] <= last) {
                tmp += p[j - 1];
            }
        }
        if (tmp > last) {
            return false;
        }
        last = tmp;
    }
    return true;
}

int main(int argc, const char * argv[]) {
    // insert code here...
    p[0] = 1;
    for (int i = 1; i < 31; ++i) p[i] = p[i-1] * 2;
    
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        for (int i = 0; i < n; ++i) cin >> a[i];
        int k = 0;
        while (1) {
            if (check(n, k)) {
                cout << k << endl;
                break;
            }
            k++;
        }
    }   
    
    return 0;
}

总结

题目1是找规律,注意看样例;
题目2是区间重叠,其实是判断两个区间是否存在重叠;
题目3是构造,需要一点点想法,借住数轴更容易想通;
题目4是贪心,尽量保证最长的一个队列,然后枚举每一个位置;
题目5是暴力+贪心,发现题目计算次数不多,寻找一个快速验证的方法。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容

  • 今天感恩节哎,感谢一直在我身边的亲朋好友。感恩相遇!感恩不离不弃。 中午开了第一次的党会,身份的转变要...
    迷月闪星情阅读 10,498评论 0 11
  • 彩排完,天已黑
    刘凯书法阅读 4,146评论 1 3
  • 表情是什么,我认为表情就是表现出来的情绪。表情可以传达很多信息。高兴了当然就笑了,难过就哭了。两者是相互影响密不可...
    Persistenc_6aea阅读 120,501评论 2 7