算法练习1801

CF371C Hamburgers

题面:已知做汉堡要B,S,C三种原料,单价分别为pb,ps,pc,现给出食谱,并已经有原料数量分别为nb,ns,nc和钱r,问最多可以做多少个汉堡?

分析:汉堡越多,需要的钱不会更少,存在单调性,可用二分搜索,注意确定好上下界,避免溢出。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL b, s, c, nb, ns, nc, pb, ps, pc, r;
bool chk(LL x) {
    return r >= pb * max(b * x - nb, 0LL) + ps * max(s * x - ns, 0LL) + pc * max(c * x - nc, 0LL);
}
int main() {
    string str;
    cin >> str;
    for (auto i : str) {
        if (i == 'B') b++;
        if (i == 'S') s++;
        if (i == 'C') c++;
    }
    cin >> nb >> ns >> nc >> pb >> ps >> pc >> r;
    LL lo = 0, hi = 2e12, mid;
    while (lo < hi) {
        mid = lo + (hi - lo + 1) / 2;
        if (chk(mid))
            lo = mid;
        else
            hi = mid - 1;
    }
    cout << lo << endl;
    return 0;
}

CF915C Permute Digits

题面:给定数字a和b,1<=a,b<=1e18,求a的一个排列构成的数X,使得X在满足不超过b的前提下尽可能得大,输入保证解一定存在。

分析:可以从左到右,从小到大依次确定各位数,不断更新答案。

#include <bits/stdc++.h>
using namespace std;
string a;
long long b;
int main() {
    cin >> a >> b;
    sort(a.begin(), a.end());
    for (int i = 0; i < a.length(); i++) {
        for (int j = i + 1; j < a.length(); j++) {
            string t = a;
            swap(t[i], t[j]);
            if (stoll(t) > stoll(a) && stoll(t) <= b)
                swap(a[i], a[j]);
        }
    }
    cout << a << endl;
    return 0;
}

也可以从左到右,从大到小搜索答案,找到的第一个即为所求。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL a, b, cnt[10], n, p[20];
void dfs(int x, LL y) {
    if (x == 0) {
        cout << y << endl;
        exit(0);
    }
    for (int i = 9; i >= 0; i--) {
        if (cnt[i] && y + i * p[x] <= b) {
            cnt[i] -= 1;
            dfs(x - 1, y + i * p[x]);
            cnt[i] += 1;
        }
    }
}
int main() {
    cin >> a >> b;
    while (a) {
        n += 1;
        cnt[a%10] += 1;
        a /= 10;
    }
    p[1] = 1;
    for (int i = 2; i <= n; i++)
        p[i] = p[i-1] * 10;
    dfs(n, 0);
    return 0;
}

UVA-12333 Revenge of Fibonacci

题面:给出一个不超过40位的数字a,问fib数列中以a为前缀的项最小序号是多少?如果前10w都不存在,输出-1。

分析:由于询问很多,先预处理出前10w项,考虑到前缀最长只有40位,可以只计算每一项的前几位,为保证准确性,可以多算几位,比如前50位,利用滚动数组优化空间,前缀查询可通过trie高效实现。

#include <bits/stdc++.h>
using namespace std;
struct Node {
    int val;
    struct Node *next[10];
}root;
int T, f[2][21000], L, R;
void insert(int idx, string &s) {
    Node *p = &root;
    for (auto i : s) {
        int d = i - '0';
        if (p->next[d] == NULL) {
            p->next[d] = new Node();
            p->next[d]->val = idx;
        }
        p = p->next[d];
    }
}
int query(string &s) {
    if (s == "1") return 0;
    Node *p = &root;
    for (auto i : s) {
        int d = i - '0';
        if (p->next[d] == NULL)
            return -1;
        p = p->next[d];
    }
    return p->val ? p->val : -1;
}
int main() {
    f[0][0] = f[1][0] = 1;
    L = R = 0;
    for (int i = 2, j = 0; i < 100000; i++, j = !j) {
        for (int k = L; k <= R; k++) {
            f[j][k] += f[!j][k];
            if (f[j][k] > 9) {
                f[j][k] -= 10;
                f[j][k+1] += 1;
            }
        }
        if (f[j][R+1]) R += 1;
        if (R - L > 60) L += 1;
        string k;
        for (int u = R, v = 0; u >= L && v < 40; u--, v++)
            k += f[j][u] + '0';
        insert(i, k);
    }
    ios::sync_with_stdio(0);
    cin >> T;
    for (int i = 1; i <= T; i++) {
        string k;
        cin >> k;
        cout << "Case #" << i << ": " << query(k) << endl;
    }
    return 0;
}

CF919B Perfect Number

题面:称各位数之和为10的数为完美数,求第k(1<=k<=10000)小的完美数。

分析:由于k范围较小,可以直接暴力枚举。

#include <bits/stdc++.h>
using namespace std;
bool chk(int x) {
    int sum = 0;
    while (x) {
        sum += x % 10;
        x /= 10;
    }
    return sum == 10;
}
int main() {
    int n, k;
    cin >> k;
    for (n = 1; k; n++)
        if (chk(n)) k--;
    cout << n - 1 << endl;
}

如果k的范围变大,可考虑dfs构造完美数的方法。

#include <bits/stdc++.h>
using namespace std;
vector<int> ans;
void dfs(int r, int n) {
    if (r == 0) {
        ans.push_back(n);
        return;
    }
    for (int i = 0; i < 10; i++) {
        if (i == 0 && n == 0) continue;
        if (i <= r) dfs(r - i, n * 10 + i);
    }
}
int main() {
    dfs(10, 0);
    sort(ans.begin(), ans.end());
    int k;
    cin >> k;
    cout << ans[k-1] << endl;
    return 0;
}

CF816B Karen and Coffee

题面:咖啡有n种原料,已知煮咖啡时每种原料的温度要求范围l[i]~r[i],对于某个特定的温度,如果它包含于至少k种原料的温度范围内,则认为该温度是可接受的,给定k,现有多组询问(a,b),问在温度范围a~b之间有多少个可接受温度?温度都是正整数。

分析:先插线,然后求前缀和得到各个温度的覆盖数,根据该值与k的大小关系判断此温度是否为可接受温度,然后再做前缀和预处理,根据前缀和可在常数时间内回答各个询问。

#include <bits/stdc++.h>
using namespace std;
const int N = 200000;
int n, k, q, a, b, l, r, f[N+5];
int main() {
    ios::sync_with_stdio(0);
    cin >> n >> k >> q;
    for (int i = 0; i < n; i++) {
        cin >> l >> r;
        f[l]++, f[r+1]--;
    }
    for (int i = 1; i <= N; i++) f[i] += f[i-1];
    for (int i = 1; i <= N; i++) f[i] = f[i] >= k;
    for (int i = 1; i <= N; i++) f[i] += f[i-1];
    for (int i = 0; i < q; i++) {
        cin >> a >> b;
        cout << f[b] - f[a-1] << endl;
    }
    return 0;
}

CF920F Sum and Replace

题面:记d(x)表示正整数x的约数个数,给定初始数组a[n],有两种操作:

分析:可以先用筛选法预处理出所有d[n],然后就是个插线问线问题,由于要求使用在线算法,考虑用线段树,但问题关键在于如何快速更新。经过观察发现,对于正整数n,执行很少次的n=d[n]操作便会得到n=1或n=2,后续再执行n=d[n]不会有作何变化,利用该特性可以通过维护区间最大值进行优化。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 300005;
int n, m, a[N+5], d[1000005];
LL sum[4*N], mx[4*N];
void init() {
    for (int i = 1; i <= 1e6; i++) {
        for (int j = i; j <= 1e6; j += i)
            d[j] += 1;
    }
}
void pushup(int x) {
    sum[x] = sum[2*x] + sum[2*x+1];
    mx[x] = max(mx[2*x], mx[2*x+1]);
}
void build(int x, int l, int r) {
    if (l == r) {
        sum[x] = mx[x] = a[l];
        return;
    }
    int mid = (l + r) / 2;
    build(2*x, l, mid);
    build(2*x+1, mid+1, r);
    pushup(x);
}
void update(int x, int l, int r, int L, int R) {
    if (R < l || r < L) return;
    if (L <= l && r <= R && mx[x] < 3) return; // core optimization
    if (l == r) {
        a[l] = d[a[l]];
        sum[x] = mx[x] = a[l];
        return;
    }
    int mid = (l + r) / 2;
    update(2*x, l, mid, L, R);
    update(2*x+1, mid+1, r, L, R);
    pushup(x);
}
LL query(int x, int l, int r, int L, int R) {
    if (R < l || r < L) return 0;
    if (L <= l && r <= R) return sum[x];
    int mid = (l + r) / 2;
    return query(2*x, l, mid, L, R) + query(2*x+1, mid+1, r, L, R);
}
int main() {
    init();
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    build(1, 1, n);
    for (int i = 0; i < m; i++) {
        int t, l, r;
        scanf("%d%d%d", &t, &l, &r);
        if (t == 1)
            update(1, 1, n, l, r);
        else
            printf("%lld\n", query(1, 1, n, l, r));
    }
    return 0;
}
Table of Contents