2024牛客寒假算法基础集训营2

A

模拟

map<int, int> mp;
void solve()
{
    mp[100] = 0;
    mp[150] = 1;
    mp[200] = 2;
 
    mp[29] = 0;
    mp[30] = 0;
    mp[31] = 0;
    mp[32] = 0;
 
    mp[34] = 1;
    mp[36] = 1;
    mp[38] = 1;
    mp[40] = 1;
 
    mp[45] = 2;
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        int ans=0;
        for (int j = 0; j < 3; j++)
        {
            int x;
            cin >> x;
            ans += mp[x];
        }    cout << ans << endl;
    }
 
}

B

计数

int dx[] = {-1, 1, 0, 0}, dy[] = {0, 0, 1, -1};
void solve()
{
    int n, m, k;
    cin >> n >> m >> k;
    vector<vector<int>> dist(n + 10, vector<int>(m + 10, 0));
    queue<pii> q;
    int ans = 4 * k, cnt = 0;
    for (int i = 1; i <= k; i++)
    {
        int x, y;
        cin>>x>>y;
        dist[x][y] = 1;
    }
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            if (dist[i][j])
            {
                for (int k = 0; k < 4; k++)
                {
                    int xx = i + dx[k], yy = j + dy[k];
                    if (dist[xx][yy])
                        cnt++;
                }
            }
    cout << ans - cnt / 2 << endl;
}

C

发现答案只跟max,min,len有关

如果max,min确定了,那么答案就是\(2^{i-j-1}\)

考虑枚举max,用字典树来查询满足条件的右端点,并查询\(\frac{1}{2^i}\)的和

struct Trie
{
    int root, tot, nex[N * M][2];
    ll val[N * M]; // N 输入的字符串的长度和 ,M字符集
    int newnode()
    {
        memset(nex[tot], 0, sizeof nex[tot]);
        val[tot] = 0;
        return tot++;
    }
    void init()
    {
        memset(nex[0], 0, sizeof nex[0]);
        val[0] = 0;
        tot = 1;
        root = newnode();
    }
    void upd(ll x, ll v)
    {
        int id = root;
        for (int i = 30; ~i; i--)
        {

            int t = (x >> i) & 1;
            if (!nex[id][t])
                nex[id][t] = newnode();
            id = nex[id][t];
            val[id] = (val[id] + v) % mod;
        }
    }
    ll ask(ll x, ll k)
    {
        int id = root;
        ll res = 0;
        for (int i = 30; ~i; i--)
        {
            int t = (x >> i) & 1;
            if (((k >> i) & 1) == 1)
            {
                if (nex[id][t])
                    res = (res + val[nex[id][t]]) % mod;
                id = nex[id][t ^ 1];
            }
            else
                id = nex[id][t];
        }
        res = (res + val[id]) % mod;
        return res;
    }
} tr;
void solve()
{
    auto qmi = [&](ll a, ll b) -> ll
    {
        ll res = 1;
        while (b)
        {
            if (b & 1)
                res = res * a % mod;
            b >>= 1;
            a = a * a % mod;
        }
        return res;
    };
    int n, k;
    cin >> n >> k;
    vector<ll> a(n + 1);
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    sort(a.begin() + 1, a.end());
    tr.init();
    ll ans = 0;
    for (int i = 1; i <= n; i++)
    {
        ll res = (1 + tr.ask(a[i], k) * qmi(2, i - 1)) % mod;
        tr.upd(a[i], qmi(qmi(2, i), mod - 2));
        ans = (ans + res) % mod;
    }
    cout << ans << endl;
}

D

1.dp

预处理出走i步的最小代价\(cost[i]\),然后dp

void solve()
{
    int n, m, k;
    cin >> n >> m >> k;
    vector<ll> f(n + 1, 1e18);
    f[n - k] = 0;
    vector<ll> cost(n + 1, 1e18);
    for (int i = 1; i <= m; i++)
    {
        ll v, w;
        cin >> v >> w;
        for (int i = 1; i <= n; i++)
            cost[i * v % n] = min(cost[i * v % n], i * w);
    }
    for (int i = 1; i < n; i++)
        for (int j = 0; j < n; j++)
        {
            int to = (j - i + n) % n;
            f[to] = min(f[to], f[j] + cost[i]);
        }
    if (f[0] == 1e18)
        f[0] = -1;
    cout << f[0] << endl;
}

2.二进制优化多重背包

一种卡片最多使用n次,所以可以把问题看作一个多重背包问题,用二进制优化即可

void solve()
{
    int n, m, k;
    cin >> n >> m >> k;
    vector<vector<pll>> e(n + 1);
    vector<ll> f(n + 1, 1e18);
    f[k - 1] = 0;
    for (int i = 1; i <= m; i++)
    {
        ll a, b, num = n;
        cin >> a >> b;
        for (ll j = 1; j <= num; j *= 2)
        {
            num -= j;
            for (int k = 0; k < n; k++)
                f[(k + a * j) % n] = min(f[(k + a * j) % n], f[k] + j * b);
        }
        if (num)
            for (int k = 0; k < n; k++)
                f[(k + a * num) % n] = min(f[(k + a * num) % n], f[k] + num * b);
    }
    if (f[n - 1] == 1e18)
    {
        cout << "-1" << endl;
        return;
    }
    cout << f[n - 1] << endl;
}

3.最短路

在%n意义下,从k走到0的最短路

建边跑朴素diijkstra即可(优化反而慢)

void solve()
{
    int n, m, k;
    cin >> n >> m >> k;
    vector<vector<pll>> e(n + 1);
    vector<ll> dist(n + 1, 1e18);
    for (int i = 1; i <= m; i++)
    {
        ll a, b;
        cin >> a >> b;
        for (int j = 0; j < n; j++)
            e[j].push_back({(j + a) % n, b});
    }
    auto dij = [&](int s, int t) -> ll
    {
        vector<ll> dist(n + 1, 1e18);
        vector<bool> vis(n + 1, 0);
        dist[s] = 0;
        while (true)
        {
            int x = -1;
            for (int i = 0; i < n; i++)
                if (!vis[i] && dist[i] != 1e18)
                    if (x == -1 || dist[i] < dist[x])
                        x = i;
            if (x == t || x == -1)
                break;
            vis[x] = 1;
            for (auto [to, w] : e[x])
                dist[to] = min(dist[to], dist[x] + w);
        }
        if (dist[t] != 1e18)
            return dist[t];
        return -1;
    };
    cout << dij(k - 1, n - 1) << endl;
}

E

每次选颜色最右边的位置最小的

F

线段树维护每种颜色最右边的位置,每次操作选位置最左的

这样可以把每次操作优化为单log

struct node
{
    ll pos, col;
};
struct segtree
{
    int n;
    vector<node> a;
    segtree(int _n) : n(_n * 4 + 10), a(n + 1) {}
    void update(int id)
    {
        a[id].pos = min(a[id * 2].pos, a[id * 2 + 1].pos);
        if (a[id * 2].pos < a[id * 2 + 1].pos)
            a[id].col = a[id * 2].col;
        else
            a[id].col = a[id * 2 + 1].col;
    }
    void build(int id, int l, int r, vector<int> &arr)
    {
        if (l == r)
            a[id].pos = arr[l], a[id].col = l;
        else
        {
            int mid = l + r >> 1;
            build(id * 2, l, mid, arr);
            build(id * 2 + 1, mid + 1, r, arr);
            update(id);
        }
    }
    void change(int id, int l, int r, int pos, int t)
    {
        if (l == r) // 叶子节点
            a[id].pos = t;
        else
        {
            int mid = l + r >> 1;
            if (pos <= mid)
                change(id * 2, l, mid, pos, t);
            else
                change(id * 2 + 1, mid + 1, r, pos, t);
            update(id);
        }
    }
    pii query(int id, int l, int r, int ql, int qr)
    {
        if (l == ql && r == qr)
            return {a[id].pos, a[id].col};
        int mid = l + r >> 1;
        if (qr <= mid)
            return query(id * 2, l, mid, ql, qr);
        else if (ql > mid)
            return query(id * 2 + 1, mid + 1, r, ql, qr);
        else
        {
            auto t1 = query(id * 2, l, mid, ql, mid);
            auto t2 = query(id * 2 + 1, mid + 1, r, mid + 1, qr);
            if (t1.x < t2.x)
                return t1;
            return t2;
        }
    }
};
void solve()
{
    int n, opt;
    cin >> n;
    vector<int> a(n + 1);
    vector<vector<int>> e(n + 1);
    vector<int> dist(n + 1, 1e9);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        e[a[i]].push_back(i);
    }
    for (int i = 1; i <= n; i++)
        if (e[i].size())
            dist[i] = e[i].back();
    segtree seg(n);
    seg.build(1, 1, n, dist);
    int r = n, ans = 0;
    int cnt = 1;
    while (r > 0)
    {
        ans++;
        auto [pos, col] = seg.query(1, 1, n, 1, n);
        for (int i = pos; i <= r; i++)
        {
            int u = a[i];
            e[u].pop_back();
            int res = (e[u].size() ? e[u].back() : 1e9);
            seg.change(1, 1, n, u, res);
        }
        r = pos - 1;
    }
    cout << ans << endl;
}

G

设选择的答案区间为\([i,j]\)

有一个贪心结论,对手一定只能选最右边的一个,左端点一定是l,证明显然

s表示a的前缀和

那么答案就是\(s[j-1]-a[j]-s[l-1]\)

等效为\(s[j]-s[l-1]-2*a[j]\)

那么怎样才能一个j,满足上式值最大?

考虑用线段树维护\(s[j]-2*a[j]\)

然后原来的操作1,单点修改就变成了在现在的线段树上进行一个单点修改+区间修改

在额外用树状数组维护一下答案里的\(s[l-1]\)这是一个单点修改

struct node
{
    ll t, val, sz, mx;
};
struct segtree
{
    int n;
    vector<node> a;
    segtree(int _n) : n(_n * 4 + 10), a(n + 1) {}
    void update(int id)
    {
        a[id].val = a[id * 2].val + a[id * 2 + 1].val;
        a[id].mx = max(a[id * 2].mx, a[id * 2 + 1].mx);
    }
    void settag(int id, ll t)
    {
        a[id].val = a[id].val + t * (a[id].sz);
        a[id].t = a[id].t + t;
        a[id].mx = a[id].mx + t;
    }
    void pushdown(int id)
    {
        if (a[id].t)
        {
            settag(id * 2, a[id].t);
            settag(id * 2 + 1, a[id].t);
            a[id].t = 0;
        }
    }
    void build(int id, int l, int r, vector<ll> &arr)
    {
        a[id].t = 0;
        a[id].sz = r - l + 1;
        if (l == r)
            a[id].val = a[id].mx = arr[l];
        else
        {
            int mid = l + r >> 1;
            build(id * 2, l, mid, arr);
            build(id * 2 + 1, mid + 1, r, arr);
            update(id);
        }
    }
    void modify(int id, int l, int r, int ql, int qr, ll t)
    {
        if (l == ql && r == qr)
        {
            settag(id, t);
            return;
        }
        int mid = l + r >> 1;
        pushdown(id);
        if (qr <= mid)
            modify(id * 2, l, mid, ql, qr, t);
        else if (ql > mid)
            modify(id * 2 + 1, mid + 1, r, ql, qr, t);
        else
        {
            modify(id * 2, l, mid, ql, mid, t);
            modify(id * 2 + 1, mid + 1, r, mid + 1, qr, t);
        }
        update(id);
    }
    ll query(int id, int l, int r, int ql, int qr)
    {
        if (l == ql && r == qr)
            return a[id].mx;
        int mid = l + r >> 1;
        pushdown(id);
        if (qr <= mid)
            return query(id * 2, l, mid, ql, qr);
        else if (ql > mid)
            return query(id * 2 + 1, mid + 1, r, ql, qr);
        else
        {
            return max(query(id * 2, l, mid, ql, mid), query(id * 2 + 1, mid + 1, r, mid + 1, qr));
        }
    }
};
struct BIT
{
    int n;
    vector<ll> a;
    BIT(int _n) : n(_n + 3), a(n + 1) {}
    int lb(int x) { return x & -x; }
    void build(int n, vector<ll> &s)
    {
        for (int i = 1; i <= n; i++)
        {
            a[i] += s[i];
            int fa = i + lb(i);
            if (fa <= n)
                a[fa] += a[i];
        }
    }
    void add(int x, int y)
    {
        for (; x < n; x += lb(x))
            a[x] += y;
    }
    ll query(int x)
    {
        ll res = 0;
        for (; x; x ^= lb(x))
            res += a[x];
        return res;
    }
};
void solve()
{
    int n, q;
    cin >> n >> q;
    vector<ll> a(n + 2), s(n + 2, 0);
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = 1; i <= n; i++)
        s[i] = s[i - 1] + a[i];
    for (int i = 1; i <= n; i++)
        s[i] -= 2 * a[i];
    BIT bit(n);
    bit.build(n, a);
    segtree seg(n);
    seg.build(1, 1, n, s);
    int opt, l, r;
    for (int i = 1; i <= q; i++)
    {
        cin >> opt >> l >> r;
        if (opt == 1)
        {
            seg.modify(1, 1, n, l, l, 2 * a[l] - 2 * r);
            seg.modify(1, 1, n, l, n, r - a[l]);
            bit.add(l, r - a[l]);
            a[l] = r;
        }
        else
        {
            ll res = seg.query(1, 1, n, l + 1, r) - bit.query(l - 1);
            cout << res << endl;
        }
    }
}

I

化简题目中的边权计算公式可以发现,本质上是\(2*max(a[u],a[v])\)

贪心的考虑\(dist(i,j)\)一定是直接从\(i\)\(j\)

将所有点按照点权排序,枚举考虑每个点对答案的贡献

贡献分为两类,比\(a[i]\)小的是\(2*a[i]\),比\(a[i]\)大的是\(2*a[j]\)

二分分界点,大的用前缀和维护即可

void solve()
{
    int n;
    ll ans = 0, sum = 0;
    cin >> n;
    vector<ll> a(n + 1), b(n + 1);
    vector<ll> s(n + 1);
    for (int i = 1; i <= n; i++)
        cin >> a[i], b[i] = a[i], sum += a[i];
    sort(b.begin(), b.end());
    for (int i = 1; i <= n; i++)
        s[i] = b[i] + s[i - 1];
    for (int i = 1; i <= n; i++)
    {
        int l = 1, r = n;
        while (l < r)
        {
            int mid = (l + r + 1) >> 1;
            if (b[mid] <= a[i])
                l = mid;
            else
                r = mid - 1;
        }
        ans += 2 * (a[i] * l);
        ans += 2 * (s[n] - s[l]);
    }
    cout << ans - 2 * sum << endl;
}

j

思路类似\(I\)

边权本质是\(2*min(a[i],a[j])\)

有两种情况:

直接从\(i\)\(j\),

先从\(i\)到边权最小的\(k\),再从\(k\)\(j\)

然后用类似\(I\)的方法二分算贡献即可

void solve()
{
    int n;
    ll ans = 0, sum = 0;
    multiset<ll> st;
    cin >> n;
    vector<ll> a(n + 1), b(n + 1), s(n + 1);
    for (int i = 1; i <= n; i++)
        cin >> a[i], b[i] = a[i];
    sort(b.begin() + 1, b.end());
    for (int i = 1; i <= n; i++)
        s[i] = s[i - 1] + b[i];
    ll mn = 1e18;
    for (int i = 1; i <= n; i++)
        mn = min(mn, a[i]);
    for (int i = 1; i <= n; i++)
    {
        int l = 1, r = n;
        while (l < r)
        {
            int mid = (l + r + 1) >> 1;
            if (2 * min(b[i], b[mid]) <= 4 * mn)
                l = mid;
            else
                r = mid - 1;
        }
        ll res = 2 * s[i] + 2 * b[i] * (n - i);
        ans += 2 * s[min(l, i)];
        ans += 2 * b[i] * max(0, l - i);
        ans += 4 * mn * (n - l);
        ans -= min(2 * b[i], 4 * mn);
    }
    cout << ans << endl;
}

K

暴力枚举所有情况计算即可

void solve()
{
    int n, x, ans = 0;
    cin >> n;
    vector<char> a(n + 1);
    vector<int> path(n + 1, 0), vis(100, 0);
    vector<int> mp(1000, -1);
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    cin >> x;
    auto dfs = [&](auto dfs, int u) -> void
    {
        if (u > n)
        {
            if (n > 1 && !path[1])
                return;
            int p = 0, sum = 0;
            for (int i = 1; i <= n; i++)
            {
                p = (p * 10 + path[i]) % 8;
                sum = sum * 10 + path[i];
            }
            ans += (!p && sum <= x);
            return;
        }
        if (a[u] <= 'z' && a[u] >= 'a')
        {
            if (mp[a[u]] != -1)
            {
                path[u] = mp[a[u]];
                dfs(dfs, u + 1);
            }
            else
            {
                for (int i = 0; i <= 9; i++)
                    if (!vis[i])
                    {
                        vis[i] = 1;
                        mp[a[u]] = i;
                        path[u] = i;
                        dfs(dfs, u + 1);
                        vis[i] = 0;
                        mp[a[u]] = -1;
                    }
            }
        }
        else if (a[u] <= '9' && a[u] >= '0')
        {
            path[u] = a[u] - '0';
            dfs(dfs, u + 1);
        }
        else
        {
            for (int i = 0; i <= 9; i++)
            {
                path[u] = i;
                dfs(dfs, u + 1);
            }
        }
    };
    dfs(dfs, 1);
    cout << ans << endl;
}
posted @ 2024-02-29 18:12  0x3ea  阅读(11)  评论(0编辑  收藏  举报