牛客算法課 (算法入門班) 貪心與模擬(4)

目录

相差最大(貪心策略列子1)

[NOIP1998]拼数

区间覆盖

NC16561 国王的游戏

NC25043 protecting the flower

NC10712 CF484A Bits

NC1879 毒瘤xor

NC20860 兔子的區間密碼

起床困难综合症


對於任何二進制題目, 我們都要考慮是否可以每一位單獨考慮!

相差最大(貪心策略列子1)

給定長度為n的整數數列{Ai}, 找出兩個整數 ai 和 aj (i < j), 使得ai - aj盡量的大.

暴力一定是固定一個點ai, 枚舉aj, 然後打擂台.

你會發現如果我們把後綴最小的aj 提前算出來, 我們就可以省略一個nested loop去找最小aj了.

又或者你固定aj, 去找前綴最大ai.

思維講解 沒有代碼

[NOIP1998]拼数

一開始直接有一個思路就是按照字典序排序, 然後從大到小合拼, 但是, 由於每個數字的長度不一樣, 所以有機會出錯, 我們可以考慮, 比較的時候, 讓a + b 和 b + a 去比字典序, 那麼長度的憂慮就可以消除了

#include<bits/stdc++.h>
using namespace std;
bool cmp(string a, string b){
    return a + b > b + a;
}
int main(){
    int n;
    cin >> n;
    vector<string> v;
    for(int i = 0; i < n; i++){
        string ccc;
        cin >> ccc;
        v.push_back(ccc);
    }
    sort(v.begin(), v.end(), cmp);

    string res = "";
    for(auto a : v){
        res += a;
    }
    cout << res<< endl;
    return 0;
}

区间覆盖

给出n个区间, 让你找出最多的区间,并且他们不想交.

可以利用贪心, 把他按照右端点进行排序, 因为只要一个区间的右端点靠前, 那么右端点比他大的区间能与靠后的区间成为一个子集, 那么靠前的那个区间也一定行. 利用这个思想就可以轻松把题目做出来.

NC16561 国王的游戏

套路题, 如果任意两个相邻的人交换不会影响后面的人和前面的人的结果, 我们可以进行推论.

设前面的人的乘积是pi. ab 为相邻的两个人. 

对于 a b 这种排法. a的答案为 pi / a.right , b 的答案为 pi * a.left / b.right. 结果为这两个取max

对于b a 这种排法. b的答案为 pi/ b.right, a 的答案为pi * b.left / a.right 结果为这两个取max

对于 b a 排法的a 的答案必然大于 a b 排法的a的答案. 所以我们可以忽略 掉a b 的a.

同样的我们也会忽略 b a 排法的 b.

所以最后就是, pi * a.left / b.right 跟 pi * b.left / a.right 做比较.

 很明显pi 可以越掉. 剩下就是比较 a.left / b.right 跟 b.left / a.right

移项 得出 a.left * a.right 跟 b.right * b.left 做比较. 

如果我们假设 a b 排法 为优, 那么必须满足 a.left * a.right <=  b.right * b.left

由於這裡要用大數, 所以用python比較方便

n = int(input())
a = []
t1, t2 = map(int, input().split())
for i in range(n):
    a.append(list(map(int, input().split())))
a.sort(key = lambda c : c[0] * c[1]);
sum = t1
maxn = 0
for i in a:
    maxn = max(maxn, sum // i[1])
    sum *= i[0]
print(maxn)

NC25043 protecting the flower

这个题也是交换任意两个相邻的牛对前面和后面的牛的吃草面积没有影响.

我们假设ab 这样的排法更优.

前面的牛所花的总时间为 T, d为吃草速度

那么牛a 所吃的草就是 T * da, 牛b 所吃的草 为 (T + 2 * ta) * db , 由于农夫把牛送回去和回来送b要花2倍的ta.

所以 ab 排法的花费为 T* da + (T+2*ta) *db

对于ba 排法的花费为 T*db + (T + 2*tb)*da

ab <= ba, T* da + (T+2*ta) *db <= T*db + (T + 2*tb)*da

化简得出  T* da + T*db+2*ta*db<= T*db + T *da+ 2*tb*da

ta*db<= tb*da

所以要让ab <= ba, 就必须满足 ta / da <= tb/ db, 我们按照这个排序算的结果必然为最好.

NC10712 CF484A Bits

n次詢問, 搵從l 到 r 之間, 哪個數的二進制中最多1.

我們嘗試每次把l 的 二進制中的0 變成1, 看看會不會超過r, 如果不會就繼續加, 會就打印答案.

記住l 有前導0, 所以就算位數不一樣也可以的.

NC1879 毒瘤xor

 xor 操作是取反操作, 假如說在l到r之間 二進制中的 第一位出現0的個數比出現1的個數要多, 那麼第一位顯然要xor 1, 才能確保我們xor的所有數都盡可能的大. 

那麼也就是說如果我們可以統計每一個位數出現0和1的個數, 我們就可以知道我們的x 每一個位數到底是0 還是1. 

只要0 多於1半我們就要幫我們的 x += (1 << j) j就是指定的位數.

顯然要O(1)得到位數的0出現個數 需要用到前綴和. 我們可以用prefixSum[i][j] 表示前i個數中, 第j位有多少個0. 

而最多只會出現r - l + 1 那麼多個0. 所以在查詢的l r 區間中, 只要 (presum[r][j] - presum[l - 1][j]) * 2 < r - l + 1, 就要把x 加上1 << j

#include<iostream>
using namespace std;
int n, a[100010];
int sum[1e6 + 1][40];
int main(){
	cin >> n;
	for(int i = 1; i <= n; i++){
		cin >> a[i];
	}
	for(int j = 0; j <= 30; j++){
		for(int i = 1; i <= n; i++){
			sum[i][j] = sum[i - 1][j];
			if((a[i]>>j) & 1)sum[i][j]++;
		}
	}
	int q;
	cin >> q;
	for(int i = 1; i <= q; i++){
		int l, r;
		cin >> l >> r;
		int x = 0;
		for(int j = 0; j < 31; j++){
			//to check if the amount of 0 is above half or not, if it is we xor this pos with 1, to turn all of them 
			//1
			if((sum[r][j] - sum[l - 1][j])*2 < r - l + 1){
				x|=(1<<j);
			}
		}
		cout << x << endl;
	}
	return 0;
}

NC20860 兔子的區間密碼

要找出l 到 r區間中 任何兩個數xor後的最大值..

假如 l = 101000 r = 101100

只要從最高位開始數起他們的二進制表達式都是一樣的, 那麼在l到r這個區間裡的所有數字他們從最高位開始數起他們的二進制表達式都是一樣的. 

所以如果我們要考慮選兩個數字他們的xor最大, 那麼只要從最高位開始走起, 只要遇到一個位他們是不一樣的, 我們就可以直接把後面所有數字變成1, 那麼就會是 xor後的結果.

假如 l = 101000 r = 101100

從最高位第4位開始不一樣, 我們永遠能選擇和 r 相反的數字, 而且可以保證不會數值不會低於l.

所以選擇的兩個數字為 101011 和 101100, xor後的結果為 111.

#include <bits/stdc++.h>
using namespace std;
#define int long long
 
int kpow(int a,int n) {
    int ans=1;
    while(n) {
        if(n&1)ans*=a;
        a*=a;
        n>>=1;
    }
    return ans;
}
 
signed main() {
    int t,a,b;
    cin>>t;
    while(t--) {
        cin>>a>>b;
        int ans=0;
        for(int i=63;i>=0;i--)
            if((a>>(i-1))!=(b>>(i-1))) {
                ans=kpow(2,i)-1;
                break;
            }
        cout<<ans<<'\n';
    }
    return 0;
}

起床困难综合症

由於每一個位數到最後的結果都是分開和獨立的, 所以我們第一個方案就是考慮, 用0 去走一遍看看最後結果是什麼, 再用1去走一遍看看結果是什麼, 如果其中一個結果是1, 那麼我們就取1, 我們把m的最大值 111111111111111111111..... , 和 最小值 00000000000000000...., 都走一遍最後有1的看看他初始攻擊力是0 還是 1, 然後跟著取回原來的數就可以了.

由於題目希望 初始攻擊力盡可能小, 所以我們優先把 0 可以變 1 的考慮. 

其次 1 最後經過m 個門也是1 的, 我們也會考慮要1. 但是同時 也要考慮 我們的初始攻擊力不會超過m的範圍 就是 1e10, 10000000000, 我們的答案有可能會是 10001000000, 這些的, 所以我們每取了一個位數, 我們就把m 減掉這個數字, 就可以保證我們不會超過m的範圍.

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int main()
{
    int n,m;
    cin >> n >> m;
    int ans=0,ans2=-1;
    for(int i=1;i<=n;i++){
        string s;
        cin >> s;
        int t;
        cin >> t;
        if(s[0]=='O')  
            ans|=t,ans2|=t;
        if(s[0]=='X') 
            ans^=t,ans2^=t;
        if(s[0]=='A') 
            ans&=t,ans2&=t;
    }
    int res=0;
    for(int i=30;i>=0;i--)
    {
        if(ans>>i&1) 
            res+=(1<<i);
        else if(ans2>>i&1&&(1<<i)<=m) 
            res+=(1<<i),m-=(1<<i);
    }
    cout << res << endl;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值