牛客算法課 (算法入門班) 二分, 三分, 01分數規劃

目录

NC19916[CQOI2010] 撲克牌

NC116564 [NOIP2012]借教室

K-th Number

三分思路

圆覆盖问题

传送带

01分数规划

小咪买东西

[USACO 2009 Dec S]Music Notes

完全平方数(好問題!!!!)

[USACO 2010 Feb S]Chocolate Eating

[NOIP2015]跳石头

[USACO 2017 Dec P]Greedy Gift Takers

华华给月月准备礼物

[NOIP2011]聪明的质监员 (复习用)

裝備合成

位数差(難)

[HEOI2014]南园满地堆轻絮

P6174 [USACO16JAN]Angry Cows S

GPA (補做)


NC19916[CQOI2010] 撲克牌

我們二分答案, 有多少套牌.

然後如果二分出來的是 x套牌, 我們檢查, 組成x套牌需要用到多少張joker, 如果joker的數量超過x, 說明同一個組合裡用到了超過1張的joker. 我們還要檢查使用的joker 有沒有超過題目限定的m.

NC116564 [NOIP2012]借教室

給定每天有多少個教室可以借. 和. 每天有多少份訂單(包括借出和反還時間 和 數量).

暴力思路, 直接遍歷每天的訂單 同時 修改每天可以借出的教室, 用 O(n * m * (t - s)),這個顯然會超時.

由於我們知道, 如果某一個天所有教室都借出去了, 然後有人再借那麼他就會是負數, 然後他後面所有的天數都是不滿足條件的.

我們考慮二分最後一個可行的天數.

利用差分數組對 區間內的數字進行修改, 看看有沒有負數, 如果有負數, 表示 答案在前面, 如果沒有負數 表示答案在後面.

為了防止出錯, 我們把借出時間 做一個排序.

#include<bits/stdc++.h>
using namespace std;
int n, m;
struct node{
    int l, r, d;
}a[10000000];
long long r[1000000];
long long del[1000000];
bool check(int x){
    for(int i = 1; i <= n; i++){
        del[i] = r[i] - r[i - 1];
    }
    for(int i = 1; i <= x; i++){
        del[a[i].l] -= a[i].d;
        del[a[i].r + 1] += a[i].d;
    }
    long long sum = 0;
    for(int i = 1; i <= n; i++){
        sum += del[i];
        if(sum < 0)return false;
    }
    return true;
}
int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        cin >> r[i];
    }
    for(int i = 1; i <= m; i++){
        cin >> a[i].d >> a[i].l >> a[i].r;
    }
    int l = 0, r = m;
    while(l <= r){
        int mid = (l + r) / 2; 
        if(check(mid))l = mid + 1;
        else r = mid - 1;
    }
    if(l > m)cout << 0 << endl;
    else cout << -1 << endl << l << endl;
    return 0;
}

 這裡要注意一個點. 就是我們的差分數組 每次都重新計算, 因為我們現在是要計算到底 x 是不是一個合法日期, 而不是整個所有日子是不是都是合法日期. 

K-th Number

最簡單的想法是直接枚舉第m大的可能.

因為我們知道第m大 x 意味著 他必須有m - 1 個數比x要大. 那麼我們直接去找所有區間看看 我們枚舉的x 是不是有m - 1個第k大比他大.

那麼我們知道其實這是一個單調性問題. 所以我們選擇二分這個x的可能性. 

那麼我們怎麼去統計有多少個區間的第k大比x 要大呢?

我們對給定的數組進行排序, 然後一旦我們在某一區間[l , r]中發現剛剛好裡面有k個數字比x大, 那麼我們就知道 第k大的數不可能是x, 而且他會比x大. 就可以直接統計區間的數量 用n - r + 1.

這裡就是普通的尺取法了.

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+500;
typedef long long ll;
ll n,k,m;
int w[N],b[N];

bool check(int u)//双指针检测就好了. 
{
    int l=1,r=0;//维护它为第k大的区间.
    int num=0;//维护这个区间里有多少小于等于u的数. 
    ll res=0;
    while(l<=n){
        //統計比u大的數有多少個
        while(r<=n&&num<k){
            if(w[++r]>=u)num++;    
        }
        //一旦有k個 立馬結算
        if(num==k)res+=n-r+1;
        //尋找下一個區間
        if(w[l]>=u){
            num--;
        }
        l++;
    }
    return res>=m;
}

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    int T;
    cin >> T;
    while(T--)
    {
        cin >> n >> k >> m;
        for(int i=1;i<=n;i++){
            cin >> w[i];
            b[i]=w[i];
        }
        //去掉連續出現的相同數字.
        int r=unique(b+1,b+1+n)-b-1;
        int l=1;
        sort(b+1,b+1+r);
        int ans=0;
        while(l<=r)
        {
            int mid=(l+r)>>1;
            if(check(b[mid]))//当我二分的这个值数量大于等于m时,要放大. 
            {
                l=mid+1;
                ans=max(ans,b[mid]);
            }
            else r=mid-1;//否则要缩小 
        }
        printf("%d\n",ans);
    }return 0;
} 

三分思路

如果有一個函數 存在峰值, 我們先求 l 到 mid 的mid 我們姑且叫他做lmid, 同理求出rmid, 然後如果lmid 比 rmid大證明, 峰值不可能存在於rmid 到r的區間, 我們捨去他. 如此類推 直到求出峰值.

如何求出lmid? 

我先把l 到 r 等分分割三段. 让l + 上这个长度, 就算到lmid 了, rmid 就是让r - 上这个长度.

三分的模板

#include<cstdio>
#include<cmath>
using namespace std;
const double eps=1e-5;
double a,b,c,xx,yy;
double f(double x){
    return sqrt((x-xx)*(x-xx)+(a*x*x+b*x+c-yy)*(a*x*x+b*x+c-yy));
}
int main(){
    scanf("%lf%lf%lf%lf%lf",&a,&b,&c,&xx,&yy);
    double l=-200,r=200;
    while(r-l>eps){
        double lm=l+(r-l)/3,rm=r-(r-l)/3;
        if(f(lm)<f(rm)) r=rm;
        else l=lm;
    }
    printf("%.3lf\n",f(l));
    return 0;
}

例题:

圆覆盖问题

给定二维坐标图上若干个点, 求出最小能包含所有点的圆半径.

观察发现, 如果不考虑y坐标, 只考虑x坐标, 假设我们的答案是m, 如果m 往左移, 或右移, 答案都会增大, 不会减少, 因为m已经是最优质的. 我们发现他是一个单峰值函数. 可以考虑3分解法.

对于y坐标同理.

现在唯一需要考虑的点是, 最优值的y 所构造出的最优质的x, 是不是就是全局最优?

这是正确的. 因为这个关系是一个三维关系, x 不变 会产生一个f(y)的函数

随着不同的x 产生, 会有这样一个图.

 反正就是随着x的增大, 这个图形就像是一个往里凹的腕一样. 当找到y的最优值, 再找x的最优质, 必然是最低点.

学过偏微分就明白了.

传送带

AB 之间存在一个最优点, 只要离这个点越远, 时间增加的越多.

同理 CD野存在.

所以我们可以三分AB 之间的一个最优点E, 和三分CD之间的最优点F. 就可以找到答案了.

f(E, F) = dis(A, E)/p + dis(F, D) / q + dis(E, F) / r

三分套三分的一道题目

三分出E 再去用E 三分 F

#include <bits/stdc++.h>
 
using namespace std;
 
const double esp = 1e-3;
 
struct Point {
    double x, y;
}A, B, C, D;
double P, Q, R;//!R 和 r不要搞混了,被坑死了
 
double dist(Point a, Point b) {
    return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
 
Point Fx1(double t) {
    return { (B.x - A.x) * t + A.x,(B.y - A.y) * t + A.y };
}
 
Point Fx2(double t) {
    return { (D.x - C.x) * t + C.x,(D.y - C.y) * t + C.y };
}
 
double calc(Point E) {
    double ans = dist(A, E) / P;
    double l = 0, r = 1;
    while (dist(Fx2(l), Fx2(r)) >= esp) {
        double mid1 = l + (r - l) / 3;
        double mid2 = r - (r - l) / 3;
        Point F1 = Fx2(mid1);
        Point F2 = Fx2(mid2);
        if (dist(E, F1) / R + dist(F1, D) / Q >= dist(E, F2) / R + dist(F2, D) / Q) l = mid1;
        else r = mid2;
    }
    Point F = Fx2(l);
    ans += dist(E, F) / R + dist(F, D) / Q;
    return ans;
}
 
int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> A.x >> A.y >> B.x >> B.y;
    cin >> C.x >> C.y >> D.x >> D.y;
    cin >> P >> Q >> R;
 
    double l = 0, r = 1;
    while (dist(Fx1(l), Fx1(r)) >= esp) {
        double mid1 = l + (r - l) / 3;
        double mid2 = r - (r - l) / 3;
        Point E1 = Fx1(mid1);
        Point E2 = Fx1(mid2);
        if (calc(E1) >= calc(E2)) l = mid1;
        else r = mid2;
    }
    Point E = Fx1(l);
    cout << fixed << setprecision(2) << calc(E) << '\n';
    return 0;
}

01分数规划

 二分答案, 进行公示移项, 试错.

小咪买东西

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n, k;
struct node{
    int c, v, tmp;
}a[10000000];
bool cmp(node a, node b){
    return a.tmp > b.tmp;
}
bool check(int x){
    for(int i = 1; i <= n; i++){
        a[i].tmp = a[i].v - a[i].c * x;
    }
    sort(a + 1, a + 1 + n, cmp);
    int sum = 0;
    for(int i = 1; i <= k; i++){
        sum += a[i].tmp;
    }
    return sum >= 0;
}
signed main(){
    int T;
    cin >> T;
    while(T--){
        cin >> n >> k;
        for(int i = 1; i <= n; i++){
            cin >> a[i].c >> a[i].v;
        }
        int ans = 0;
        int l = 1, r = 1e8 + 1000;
        while(l <= r){
            int mid = (l + r) / 2;
            if(check(mid)){
                l = mid + 1;
                ans = mid;
            }else{
                r = mid - 1;
            }
        }
        cout << ans << endl;
    }
    return 0;
}

[USACO 2009 Dec S]Music Notes

二分答案

題目說時間從0開始累積, 會彈奏某一個音符b次, 也就是b秒.

然後題目問某一個時間點彈到哪個音符.

其實不難, 由於彈奏是一個累積的數組, 我們先求前綴和. 然後, 每一個前綴和代表彈第bi個音符的時候的時間是多少. 

題目給了一個時間點t, 也就是說我們在這個前綴和中找出第一個大於或等於t的時間點中的索引就是答案的音符.

#include<iostream>
#include<algorithm>
using namespace std;
int main(){
    int n, q;
    cin >> n >> q;
    int b[n + 1];
    b[0] = 0;
    for(int i = 1; i <= n; i++){
        cin >> b[i];
        b[i] = b[i - 1] + b[i];
    }
    while(q--){
        int x;
        cin >> x;
        cout << (upper_bound(b + 1, b + 1 + n, x) - b) << endl;
    }
    return 0;
}

完全平方数(好問題!!!!)

由於10億開方只有30000多, 我們大可以創建一個數組, 把1 到30000全部放在裡面.

由於裡面全部都是實數, 所以每一個數字他的2次方都是完全平方數. 因此我們只需要找到 根號L到根號R之間有多少個數字就可以了. 最簡單的方法就是找第一個小於或等於根號L的數字, 和 第一個大於或等於根號R的地方, 相減就是完全平方數的數量了

#include<bits/stdc++.h>
using namespace std;
const int N =31625;
int a[N];
int main()
{
    for(int i=0;i<N;++i)a[i]=i;
    int n,l,r;
    cin>>n;
    while(n--){
        cin>>l>>r;
        int L=0,R=N;
        L=lower_bound(a,a+N,sqrt(l))-a;
        R=upper_bound(a,a+N,sqrt(r))-a;
        cout<<max(R-L,0)<<endl;
    }
    return 0;
}

[USACO 2010 Feb S]Chocolate Eating

我們二分一個每天最大可以保持的快樂值.

拿到這個值我們就去check 到底可不可以保持.

由於每天都會快樂減半, 所以一旦我們發現快樂值低於二分出來的x, 我們就不斷吃巧克力直到快樂水平達到或超過x. 

由於巧克力的數量有限, 一旦發現巧克力吃完了但還是無法達到x水平, 證明這個x 是不可能成為合法的快樂值的.

由於答案要我們同時輸出, 到底哪一天吃了哪些巧克力, 我們可以在吃巧克力讓快樂水平回到x的時候跟新一個答案 數組.

#include <iostream>
 
using namespace std;
 
typedef long long LL;
const int N = 1e6 + 10;
 
int n, d;
LL level;
LL a[N], c[N];
 
bool check(LL x)
{
    LL sum = 0, t = 0;
    for(int i = 1; i <= d; i ++)
    {
        while(sum < x) 
        {
            sum += a[++t];
            if(t > n) return false;
            if(x == level) c[t] = i;
        }
        sum /= 2;
    }
     
    return true;
}
 
int main()
{
    cin >> n >> d;
    for(int i = 1; i <= n; i ++) cin >> a[i];
     
    LL l = 0, r = 1e11;
    LL ans = 0;
    while(l <= r)
    {
        LL mid = l + r + 1 >> 1;
        if(!check(mid)) r = mid - 1;
        else{ l = mid + 1;
            ans = mid;}
    }
     
    level = ans;
    check(level);
    cout << level << endl;
    for(int i = 1; i <= n; i ++)
    {
        if(c[i]) cout << c[i] << endl;
        else cout << d << endl;
    }
         
    return 0;
}

[NOIP2015]跳石头

直接二分這個最短距離, 我們要讓所有距離都大於或等於這個距離. 

實現方面就是, 我們要怎麼算這個用了多少次移動機會, 還有怎麼去移動呢?

我們發現他所給的石頭距離是一個前綴和. 全部都是從起點到某個石頭的距離, 而且還是順序給出.

這樣的話, 我們就可以用O(1)的時候直接把兩個點相減直接算到兩點的距離, 假設你把兩點中間的點都刪掉了, 是不會影響從a 點 到b 點的總距離的.只會影響最短距離.

所以直接一個for 循環, 只要發現最後一次記錄的點 和當前點匹配的時候發現距離少於二分枚舉出來的點, 就直接用一個counter 計算移動次數 + 1. 如果比二分枚舉的x 要大 就直接跟新最後一詞出現的點.

#include<iostream>
using namespace std;
int dis, n, m, a[1000000];
bool check(int x){
    int lastt = a[0];
    int cnt = 0;
    for(int i = 1; i <= n; i++){
        if(a[i] - lastt < x){
            cnt++;
        }else{
            lastt = a[i];
        }
    }
    return cnt <= m && cnt <= n;
}
void work(){
    a[0] =0;
    cin >> dis >> n >> m;
    for(int i = 1; i <= n; i++){
        int y;
        cin >> a[i];
    }
    int l = 0, r = dis;
    int ans = 0;
    while(l <= r){
        int mid = (l + r) / 2;
        if(check(mid)){
            l = mid + 1;
            ans = mid;
        }else{
            r = mid - 1;
        }
    }
    cout << ans << endl;
}
int main(){
    work();
    return 0;
}

[USACO 2017 Dec P]Greedy Gift Takers

我們發現如果第i頭牛不能拿到禮物,他後面的所有牛都不可能拿到禮物. 

這裡具有11110000 這樣的單調性. 所以可以利用二分進行搜索最後一個出現可行的位置.

也就是我們二分出來的x 是可以拿到禮物的牛的總數量.

如何判斷是不是合法呢?

我們發現如果在從0 到i 這個位置之間, 如果有多于i頭牛的數量, 那麼就會出現無限循環了, 有牛出隊, 再從新進隊, 並且位置就在原來的位置.

最簡單的檢查方法就是, 我們直接用一個sum 記錄 在x 之前多少頭牛. 假設 我們已經知道每頭牛將會排在哪個位置, 我們就把0 到 x 的每個位置 會出現的牛的數量加起來, 如果超過他們所在的索引, 那麼就是犯法的. 原因是 在 x 前 你要讓他的和大於x, 只有當某個牛他會進隊的位置剛剛好在x 之前, 那麼就很明顯, 會開始一個循環.

由於牛所排的位置, 是從尾巴數過來的, 所以我們要提前做一個處理, 把牛會排的位置變成從頭數起會排在哪.

#include<iostream>
#include<cstring>
using namespace std;
int n, a[100000];
bool check(int mid){
    int s[1000000];
    memset(s, 0, sizeof(s));
    for(int i = 1; i< mid; i++){
        s[a[i]]++;
    }
    int sum = 0;
    for(int i = 1; i < mid; i++){
        sum+= s[i];
        if(sum >= i)return 1;
    }
    return 0;
}
int main(){
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
        a[i] = n - a[i];
    }
    int l  =0, r = n;
    while(l < r){
        int mid = l + r + 1>> 1;
        if(check(mid)){
            r = mid - 1;
        }else{
            l = mid;
        }
    }
    cout << n - r << endl;
    return 0;
}

华华给月月准备礼物

由於高於某個長度之後,的答案都是不可能的, 就是11110000 二分.

所以我們直接二分可能的長度, 然後看看到底能不能切出指定數量 k.

#include<iostream>
using namespace std;
int n, k;
int w[300000];
bool check(int mid){
    int cnt =  0;
    for(int i = 1; i <= n; i++){
        cnt += w[i]/mid;
    }
    return cnt >= k;
}
int main(){
    cin >> n >> k;
    for(int i = 1; i <= n; i++){
        cin >> w[i];
    }
    int l = 1, r = 2e9;
    while(l <= r){
        long long mid = l + r >> 1;
        if(check(mid)){
            l = mid + 1;
        }else{
            r = mid - 1;
        }
    }
    cout << l - 1<< endl;
    return 0;
}

[NOIP2011]聪明的质监员 (复习用)

题目的公式是跟你说, Y = 某一个区间中, 只计算 那些 重量比 你所选的重量W 要重的矿产的价值和.

价值和的公式 为 累积的数量 * 累积的价值.

所以我们需要有数量前缀和, 和 价值前缀和, 并且 这些前缀和都是把 重量 比W小的剔除了的.

然后我们二分 W.

这里你会发现 当W 越小, 符合条件的矿产越多, 计算出来的价值和会越大, 所以当你发现价值和比S大了, 你就把 W 变大, 反之亦然.

#include<bits/stdc++.h>
#include<cstring>
using namespace std;
long long n, m, s;
long long w[300000], v[300000];
long long cnt[300000], sum[3000000];
struct node{
    int l, r;
}a[3000000];
long long check(int mid){
    //cal the prefix sum and cnt

    for(int i = 1; i <= n; i++){
        if(w[i] >= mid){
            cnt[i] = cnt[i - 1] + 1;
            sum[i] = sum[i - 1] + v[i];
        }else{
            cnt[i] = cnt[i - 1];
            sum[i] = sum[i - 1];
        }
    }
    long long res = 0;
    for(int i = 1; i <= m; i++){
        res += (cnt[a[i].r] - cnt[a[i].l - 1])*(sum[a[i].r] - sum[a[i].l - 1]);
    }
    return res;
}
int main(){
    cin >> n >> m >> s;
    for(int i = 1; i <= n; i++){
        cin >> w[i] >> v[i];
    }
    for(int i = 1; i <= m; i++){
        cin >> a[i].l >> a[i].r;
    }
    int l = 1, r = 1e9;
    int ans = 0;
    while(l <= r){
        int mid = l + r >> 1;
        if(check(mid) >= s){
            l = mid + 1;
        }else{
            r = mid - 1;
        }
    }
    cout << min(abs(check(r) - s), abs(check(r + 1) - s))<< endl;
    return 0;
}

裝備合成

我們透過假設總共可以製造mid 個裝備, 那麼就表示 第一個方案的總數是k, 那麼第二個方案可以製造 mid - k 個裝備.

我們可以關係式求出來, 然後進行轉換得到不等式

4 * mid - x <= 2k <= 2*mid
0 <= 2k <= y - mid
0 <= 2k <= 2*mid

我們留意2k 就是我們的mid, 

        下界 = max(0, 4 * mid - x);
        上界 = min(2 * mid, y - mid);

我們發現要逼近2k 的最大值, 當上界比下界大的時候是符合要求的, 所以我們把他變大, 就利用二分, 把mid 變大, 如果下界比上界大, 就把mid 變小.

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
/*
k(2a + 3b) + (mid - k)(4a + b)
= 2ka + 3kb + 4mid*a + b*mid - 4ka - kb
= -2ka + 4mid*a + 2kb + b*mid
= (4mid - 2k)a + (2k + mid)b
The facts:
0 <= 4mid - 2k <= x
0 <= 2k + mid <= y
0 <= k <= mid because k is seperated from mid
0 <= 2k <= 2mid
make all the repersentation to be xx <= 2k <= xxx
4 * mid - x <= 2k <= 2*mid
0 <= 2k <= y - mid
0 <= 2k <= 2*mid
*/
ll work(ll x, ll y){
    ll l = 0, r = 1e9;
    while(l <= r){
        ll mid = l + r >> 1;
        ll tmp1 = max((ll)0, 4 * mid - x);
        ll tmp2 = min(2 * mid, y - mid);
        if(tmp1 == tmp2 && (tmp1 & 1)){
            r = mid - 1;
        }else if(tmp2 >= tmp1){
            l = mid + 1;
        }else{
            r = mid - 1;
        }
    }
    return l - 1;
    
}
int main(){
    int T;
    cin >> T;
    while(T--){
        int x, y;
        cin >> x >> y;
        cout << work(x,y) << endl;
    }
    return 0;
}

位数差(難)

這個題目的暴力寫法非常直接兩個循環找到所有組合, 再來一個循環 判斷位數差.

但是這樣顯然會超時.

利用分治的思想, 就好像歸併排序一樣, 我們把左邊和右邊的位數差統計出來相加就行了.

但是如何把左邊和右邊的位數差求出來呢?

我們留意到, 如果我們能找到第一個大於某個數的10^n 的數, 那麼對於所有10^(n + 1)以後的數字, 數位差都要加一.

再分治的時候我們會對他們進行排序.

我們利用二分找出第一個大於10 ^ n - a[i] 的數, 那麼在a[i]後面的數的數位差都至少跟a[i]一樣, 如此類推, 不斷累積.

#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
ll comp[9] =  { 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
ll n, a[10000000];
ll cal(int l, int r){
    if(l >= r)return 0;
    ll mid = l + r >> 1;
    ll ans = cal(l, mid) + cal(mid + 1, r);
    sort(a + mid + 1, a + r + 1);
    for(int i = l; i <= mid; i++){
        for(int j = 0; j < 9; j++){
            if(comp[j] - a[i] <= 0)continue;
            auto s = a + r + 1;
            auto pos = lower_bound(a + mid + 1, a + r + 1, comp[j] - a[i]);
            ans += s - pos;
        }
    }
    return ans;
}
int main(){
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
    }
    cout << cal(1, n) << endl;
    return 0;
}

[HEOI2014]南园满地堆轻絮

自己根據公式構造a數組. 

明顯是利用二分找出最佳的差值x. 

由於題目給出要求 b[i - 1] <= b[i] && abs(a[i] - b[i]) > x

那么就是要让ai - bi < x尽可能小,然后bi 在保证单调不下降的情况下,成立不等式.

bi 可以取值為 b[i - 1] 或者 ai - x, 因為bi最小為ai - x, 我們取兩者最大. 

如果ai - bi > x 那麼就不可能用x 作為答案.

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n, sa, sb, sc, sd, a[10000000], mod;
bool check(ll mid){
    ll b[10000000];
    b[1] = max((ll)0, a[1] - mid);
    for(int i = 2; i <= n; i++){
        b[i] = max(b[i - 1], a[i] - mid);
        if(abs(b[i] - a[i]) > mid)return 0;
    }
    return 1;
}
inline ll f(ll x){
    return (sa*x%mod*x%mod*x%mod + sb*x%mod*x%mod + sc*x%mod + sd%mod)%mod;
}
int main(){
    a[0] = 0;
    ll l = 0, r = 0;
    cin >> n >> sa >> sb >> sc >> sd >> a[1]>> mod;
    for(int i = 2; i <= n; i++){
        a[i] = (f(a[i - 1]) + f(a[i - 2]))%mod;
        r = max(r, a[i]);
    }
    r = max(r, a[1]);
    ll ans = 0;
    while(l <= r){
        ll mid = l + r >> 1;
        if(check(mid)){
            ans = mid;
            r = mid - 1;
        }else{
            l = mid + 1;
        }
    }
    cout << ans << endl;
    return 0;
}

P6174 [USACO16JAN]Angry Cows S

總之題目就是, 有一條水平線, 上面不同點上有不同的乾草, 然後你總共可以安排k 頭牛 進去吃草, 每一頭牛可以吃草的長度是 2 r, 讓你找出最小的r, 讓這k 頭牛把所有草吃干.

我們直接二分r, 如果發現r 可以把草吃完就減少r, 不然就增大r.

如何檢查是否可以吃完所有草呢?

我們把草所在的位置排序, 然後把第一個草的位置 加上2個 r, 如果我們循環到一個草 他的位置比上一個放牛的位置加上r 要大, 我們就需要繼續添加牛了.

#include<iostream>
#include<vector>
#include<bitset>
#include<cmath>
#include<algorithm>
#include<map>
#include<cstring>
using namespace std;
int N, K;
int x[1000000];
bool check(int r){
	int s = 0, p = -2000000000;
	for(int i = 1; i <= N; i++){
		if(p + r< x[i]){
			s++;
			p = x[i] + r;
		}
		if(s > K)return 0;
	}
	return 1;
}
int main(){
	cin >> N >> K;
	for(int i = 1; i <= N; i++){
		cin >> x[i];
	}
	sort(x + 1, x + 1 + N);
	int l = 0, r = x[N];
	int ans = 0;
	while(l <= r){
		int mid = l + r >> 1;
		if(check(mid)){
			r = mid - 1;
			ans = mid;
		}else{
			l = mid + 1;
		}
	}
	cout << ans << endl;
}

GPA (補做)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值