qwq

CF1997(edu168)题解 A-F

A. Strong Password

注意到最大效果是在两个相同字符之间插入一个不同的,贡献为 3。

否则在一开始插入一个和首位不同的,贡献为 2。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

void solve()
{
    string s; cin >> s;
    bool ok = 0;
    for(int i = 0; i < s.size() - 1; i ++)
    {
        if(s[i] == s[i + 1])
        {
            ok = 1;
            cout << s.substr(0, i + 1) + (char)((s[i] - 'a' + 1) % 26 + 'a') + s.substr(i + 1) << "\n";
            break;
        }
    }
    if(!ok) cout << (char)((s[0] - 'a' + 1) % 26 + 'a') << s << "\n";
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);
    int t;cin >> t;while(t --) solve();

    return 0;
}

B. Make Three Regions

注意到如果要分割成三部分,那么这一块的三面一定都要是 .

设新堵上的块为 o,图应该长这样:

.o.
?.?

因为三块不连通,所以 ? 应该是 x

所以

图应该长这样(两种):

.o. | x.x
x.x | .o.

因为保证原图联通,所以直接判断有几个像这样的图形就行了。

C. Even Positions

直接考虑贪心填右括号,不用担心右括号填多了出现 (())))(( 的情况。

因为最差也可在每个右括号前补一个左括号(奇数位都可以自己选择)。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 2e5 + 5;

void solve()
{
    int n; string s; cin >> n >> s;
    int ans = 0;
    stack<int> stk;
    for(int i = 0; i < s.size(); i ++)
        if(stk.size() && s[i] != '(') ans += i - stk.top(), stk.pop();
        else stk.push(i);
    cout << ans << "\n";
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);
    int t;cin >> t;while(t --) solve();

    return 0;
}

上面用栈维护左括号位置,还可以拆贡献,只要维护未配对左括号个数即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 2e5 + 5;

void solve()
{
    int n; string s; cin >> n >> s;
    int ans = 0;
    int c = 0;
    for(int i = 0; i < s.size(); i ++)
    {
        if(c && s[i] != '(') c --;
        else c ++;
        ans += c;
    }
    cout << ans << "\n";
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);
    int t;cin >> t;while(t --) solve();

    return 0;
}

D. Maximize the Root

因为节点 \(u\) 做操作不会影响父节点,所以可以 dp,设 \(f(u)\) 表示 \(u\) 子树内最小值最大是多少。

\(g(u)=\min_{v\in sons(u)}f(v)\)

\(f(u)=\min(a_u,g(u))\)

注意到当 \(a_u<g(u)\) 时, \(a_u\) 成为 \(f(u)\) 的瓶颈,于是可以通过操作增大 \(a_u\),减小 \(g(u)\) 做到平衡。

所以可以让 \(a_u\)\(g_u\) 都变成 \(\dfrac{g(u)+a_u}{2}\) 即可。

注意下取整一下。

注意到如果 \(a_u>g(u)\),那么答案不会变大,\(f(u)=g(u)\),特判一下。

所以有

\(f(u)=\min\left(a_u,\dfrac{\min_{v\in sons(u)}f(v)+a_u}{2}\right)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 2e5 + 5;
ll n, f[N], a[N];
vector<int> e[N];

void dfs(int x)
{
    if(e[x].empty()) return f[x] = a[x], void();
    f[x] = 0;
    ll mn = 2e9;
    for(int i : e[x])
    {
        dfs(i);
        mn = min(mn, f[i]);
    }
    if(x == 1) cout << a[1] + mn << "\n";
    if(a[x] >= mn) f[x] = mn;
    else f[x] = (mn + a[x]) / 2;
}

void solve()
{
    cin >> n;
    for(int i = 1; i <= n; i ++) e[i].clear();
    for(int i = 1; i <= n; i ++) cin >> a[i];
    for(int i = 2; i <= n; i ++)
    {
        int x; cin >> x;
        e[x].push_back(i);
    }
    dfs(1);
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);
    int t;cin >> t;while(t --) solve();

    return 0;
}

二分答案也不是不行。但是都会二分答案了还不会线性做法吗?

E. Level Up

法一:主席树

注意到 \(\sum_{i=1}^n \frac 1i=O(n\log n)\)

所以离线下来,对于每个 \(k\) 枚举等级增加的位置,也就是 \(O(n\log n)\) 个。

于是问题变成:求最小的 \(r\) 使得 \(\sum_{i=l}^r[a_i\geq lev]=k\),这可以在 \(a_i\) 的大小这一维上可持久化一下,以 \(i\) 为下标,线段树上二分一下就可以了,复杂度 \(O(n\log^2n)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 2e5 + 5, V = 2e5;

int rt[N];
int fdp;
struct sgt
{
    int a[N << 6], ls[N << 6], rs[N << 6], idx;
    inline void pu(int x) {a[x] = a[ls[x]] + a[rs[x]];}
    int upd(int q, int l, int r, int x, int v)
    {
        int u = ++idx; a[u] = a[x], ls[u] = ls[x], rs[u] = rs[x];
        if(l == r) return a[u] += v, u;
        int mid = l + r >> 1;
        if(mid >= q) ls[u] = upd(q, l, mid, ls[x], v);
        else rs[u] = upd(q, mid + 1, r, rs[x], v);
        pu(u); return u;
    }
    void fnd(int l, int r, int x, int k)
    {
        if(l == r) return fdp = l, void();
        int mid = l + r >> 1;
        if(a[ls[x]] >= k) return fnd(l, mid, ls[x], k);
        else return fnd(mid + 1, r, rs[x], k - a[ls[x]]);
    }
    int qry(int ql, int qr, int l, int r, int x, int k)
    {
        if(ql > qr) return 0;
        if(ql <= l && r <= qr)
        {
            if(a[x] < k) return a[x];
            return fnd(l, r, x, k), k;
        }
        int mid = l + r >> 1, ans = 0;
        if(mid >= ql) ans += qry(ql, qr, l, mid, ls[x], k);
        if(ans < k && mid < qr) ans += qry(ql, qr, mid + 1, r, rs[x], k - ans);
        return ans;
    }
}t;

vector<pair<int, int>> q[N];
int n, ans[N], Q, a[N];
vector<int> v[N];

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);
    cin >> n >> Q;
    for(int i = 1; i <= n; i ++) cin >> a[i], v[a[i]].push_back(i);
    for(int i = V; i >= 1; i --)
    {
        rt[i] = rt[i + 1];
        for(int j : v[i])
            rt[i] = t.upd(j, 1, n, rt[i], 1);
    }
    for(int i = 1; i <= Q; i ++)
    {
        int x, y; cin >> x >> y;
        q[y].push_back({x, i});
    }
    for(int i = 1; i <= n; i ++)
    {
        if(q[i].empty()) continue;
        vector<int> pos;
        int lst = 0;
        for(int j = 1; j <= n / i; j ++)
        {
            if(t.qry(lst + 1, n, 1, n, rt[j], i) < i) break;
            pos.push_back(fdp);
            lst = fdp;
        }
        for(auto [j, id] : q[i])
        {
            int lev = lower_bound(pos.begin(), pos.end(), j) - pos.begin() + 1;
            ans[id] = a[j] >= lev;
        }
    }
    for(int i = 1; i <= Q; i ++)
        cout << (ans[i] ? "YES" : "NO") << "\n";

    return 0;
}

法二:树状数组

考虑上面的无脑做法,发现可以通过把 \(a_i\) 的大小这一维离线考虑,这样就不要可持久化了。

从小到大考虑 \(i\),找到从上一个位置开始的第 \(k\)\(\geq i\)\(a_j\),这也不用线段树,只要用树状数组上二分找到第 \(k+\left(上一个位置前有几个\geq i 的数\right)\) 个数即可。

每次考虑完一个 \(i\),就把 \(a_j=i\)\(j\) 删除。

常数很小。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 2e5 + 5, V = 2e5;

struct bit
{
    static const int K = 18;
    int a[(1 << K) + 5];
    void upd(int x, int v)
    {
        for(int i = x; i <= (1 << K); i += (i & -i)) a[i] += v;
    }
    int qry(int x)
    {
        int ans = 0;
        for(int i = x; i; i -= (i & -i)) ans += a[i];
        return ans;
    }
    int lower_bound(int k)
    {
        int now = 1 << K;
        if(a[now] < k) return 1e9;
        for(int i = K - 1; i >= 0; i --)
            if(a[now - (1 << i)] >= k) now = now - (1 << i);
            else k -= a[now - (1 << i)];
        return now;
    }
} t2;

int n, Q, a[N];
vector<int> v[N];
vector<int> pos[N];

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);
    cin >> n >> Q;
    for(int i = 1; i <= n; i ++) cin >> a[i], v[a[i]].push_back(i);
    for(int i = 1; i <= n; i ++) t2.upd(i, 1), pos[i].push_back(0);
    for(int i = 1, r = n; i <= n; i ++)
    {
        for(int j = 1; j <= r; j ++)
        {
            int p = t2.lower_bound(t2.qry(pos[j].back()) + j);
            if(p <= n) pos[j].push_back(p);
            else r = j - 1;
        }
        for(int j : v[i]) t2.upd(j, -1);
    }
    for(int i = 1; i <= Q; i ++)
    {
        int x, y; cin >> x >> y;
        int lev = lower_bound(pos[y].begin(), pos[y].end(), x) - pos[y].begin();
        cout << ((a[x] >= lev) ? "YES\n" : "NO\n");
    }

    return 0;
}

法三:更简单的树状数组

更简单的:

和其他的方法解题角度不一样,其他方法是考虑对于每个 \(k\),第 \(i\) 个位置等级是多少,这个想法是考虑第 \(i\) 个位置,在 \(k\ge t_i\) 的时候答案从 NO 变成 YES

代码简单跑得快。

考虑二分 \(t_i\),注意到求在 \(i\) 前面有多少次交手等于求出 \(\sum_{j=1}^{i-1}[t_j\le mid]\)。因为从前往后求,后面没贡献,所以等于求 \(\sum_{j=1}[t_j\le mid]\)

树状数组维护 \(t\) 即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 2e5 + 5, V = 2e5;

struct bit
{
    static const int K = 18;
    int a[(1 << K) + 5];
    void upd(int x, int v)
    {
        for(int i = x; i <= (1 << K); i += (i & -i)) a[i] += v;
    }
    int qry(int x)
    {
        int ans = 0;
        for(int i = x; i; i -= (i & -i)) ans += a[i];
        return ans;
    }
} t2;

int n, Q, a[N], t[N];
vector<int> v[N];

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);
    cin >> n >> Q;
    for(int i = 1; i <= n; i ++) cin >> a[i];
    for(int i = 1; i <= n; i ++)
    {
        int l = 1, r = n;
        while(l < r)
        {
            int mid = l + r >> 1;
            if(t2.qry(mid) / mid + 1 <= a[i]) r = mid;
            else l = mid + 1;
        }
        t2.upd(t[i] = r, 1);
    }
    for(int i = 1; i <= Q; i ++)
    {
        int x, y; cin >> x >> y;
        cout << ((t[x] <= y) ? "YES\n" : "NO\n");
    }

    return 0;
}

法四:根号算法

\(B\) 为分治阈值。

对于 \(k\le B\)\(O(nB)\) 暴力模拟,处理出升级的位置即可。

对于 \(k> B\),升级的位置不超过 \(B\) 个,于是用 \(B\) 个指针维护升级的位置,从小到大枚举 \(k\),每个指针一定单调右移,指针移动总距离 \(O(n^2/B)\)

维护两个指针 \(j-1,j\) 之间有几个数 \(\ge j\)。可以前缀和,但是更好的方法是直接维护 \(cnt_j\) 表示两个指针 \(j-1,j\) 之间有几个数 \(\ge j\)

指针 \(j\) 右移,更新 \(cnt_j,cnt_{j+1}\)

\(B=\sqrt n\),复杂度最小。

时间复杂度 \(O(n\sqrt n+q\log n)\),空间复杂度 \(O(n\log n)\)

把询问离线可以做到时间复杂度 \(O(n\sqrt n+q)\),空间复杂度 \(O(n + q)\)

但是因为常数原因,\(B\) 开大一点跑得更快。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 2e5 + 5, V = 2e5, B = 1000;
vector<int> v[N];

int n, Q, a[N];

vector<int> pos[N];

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);
    cin >> n >> Q;
    for(int i = 1; i <= n; i ++) cin >> a[i], v[a[i]].push_back(i);
    for(int i = 1; i <= n; i ++) pos[i].push_back(0);
    for(int i = 1; i <= min(n, B); i ++)
    {
        for(int j = 1, c = 0, k = 1; j <= n; j ++)
        {
            c += a[j] >= k;
            if(c == i)
            {
                k ++, c = 0;
                pos[i].push_back(j);
            }
        }
    }
    int pt[B + 5] = {}, cnt[B + 5] = {};
    for(int i = 1; i <= min(n, B); i ++) pt[i] = 0;
    for(int i = B + 1; i <= n; i ++)
    {
        int r = B;
        for(int j = 1; j <= r; j ++)
        {
            while(pt[j] <= n && cnt[j] < i) pt[j] ++, cnt[j] += a[pt[j]] >= j, cnt[j + 1] -= a[pt[j]] >= j + 1;
            if(pt[j] == n + 1) r = j - 1;
        }
        pos[i].resize(r + 1);
        for(int j = 1; j <= r; j ++) pos[i][j] = pt[j];
    }
    for(int i = 1; i <= Q; i ++)
    {
        int x, y; cin >> x >> y;
        int lev = lower_bound(pos[y].begin(), pos[y].end(), x) - pos[y].begin();
        cout << ((a[x] >= lev) ? "YES\n" : "NO\n");
    }

    return 0;
}

F. Chips on a Line

注意到这很像斐波那契数列,又注意到 \(i\to i-1,i-2\),我们发现,一个位于 \(i\) 上的棋子等价\(fib_i\) 个位于 \(1\)\(2\) 的棋子。(不断使用这个操作,反过来,可以使用逆操作)。

\(i\) 上有 \(c_i\) 个棋子,设状态 \(S=\sum_{i=1}^x c_i\times fib_i\)

这个性质让我们发现,不同状态之间是不能转化的,相同状态之间是可以随便转化的,于是可以处理出每个 \(S\) 的代价。考虑 dp。

\(f(i)\)\(S=i\) 时的代价。

于是有 \(f(i)=\min_{j=1}f(i-fib_j)+1\)

然后考虑计数有多少个状态为 \(S\) 的方案,考虑背包,设 \(g(i,j,k)\) 表示 dp 到 \(fib_i\),填了 \(j\) 个棋子,状态为 \(k\) 的方案数,有:

\(g(i,j,k) = \sum_{c=0}g(i-1,j-c,k-c\times fib_i)\)

复杂度不太对。

注意到这是一个完全背包,于是改写成:

\(g(i,j,k)=g(i,j-1,k-fib_i)+g(i-1,j,k)\)

空间开不下。

滚动数组优化一下就好了。

答案就是 \(ans=\sum_{i=0} [f(i)=m]g(x,n,i)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 1005, M = N * 60, p = 998244353;
int a[40];
int f[M], n, x, m;
int g[N][M];

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);
    cin >> n >> x >> m;

    a[1] = a[2] = 1;
    for(int i = 3; i < 40; i ++) a[i] = a[i - 1] + a[i - 2];

    memset(f, 0x3f, sizeof f);
    f[0] = 0;
    for(int i = 1; i < M; i ++)
        for(int j = 0; j < 40; j ++)
            if(i >= a[j]) f[i] = min(f[i], f[i - a[j]] + 1);
    g[0][0] = 1;
    for(int i = 1; i <= x; i ++)
    {
        for(int j = 1; j <= n; j ++)
        for(int k = a[i]; k <= n * 55; k ++)
            g[j][k] = (g[j][k] + g[j - 1][k - a[i]]) % p;
    }
    ll ans = 0;
    for(int i = 1; i < M; i ++)
        ans += (f[i] == m) * g[n][i];
    cout << ans % p;

    return 0;
}

作者:adam01

出处:https://www.cnblogs.com/adam01/p/18334349

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   adam01  阅读(84)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示
more_horiz
keyboard_arrow_up dark_mode palette
选择主题