Codeforces Round #661 (Div. 3)

CodeforcesRound #661 (Div. 3) 3.2

DBinaryStringToSubsequences

D题,给出一个01序列,问可以最少将其分成多少个子序列,使得每个子序列中都是01交替的(就是不存在00或者11的情况)

队列维护当前以0或者1结尾的子序列信息,如果当前点为1,考虑是否还有结尾为0的,有就加进去,没有的话就只能新建一个

void solve(int group_Id) {
    int n, cnt = 0, ans = 0;
    read(n);
    string s;
    cin >> s;
    queue<int> q1, q0;
    VI res(n + 1);
    for (auto c : s) {
        ++ cnt;
        if (c == '0') {
            if (!q1.empty()) {
                int t = q1.front();
                q1.pop();
                res[cnt] = res[t];
                q0.push(cnt);
            }else {
                q0.push(cnt);
                res[cnt] = ++ ans;
            }
        }else {
            if (!q0.empty()) {
                int t = q0.front();
                q0.pop();
                res[cnt] = res[t];
                q1.push(cnt);
            }else {
                q1.push(cnt);
                res[cnt] = ++ ans;
            }
        }
    }
    cout << ans << endl;
    for (int i = 1;i <= n;i ++) {
        cout << res[i] << " ";
    }
    cout << endl;
}

E1WeightsDivision(easyversion)

E1​,一颗以1为根的树,该树的贡献为从根节点1到每个节点的距离之和,现在每次可以选择一条边,将这条边的权值除二向下取整,问最少操作几次可以使得贡献小于S

考虑每条边在计算总贡献时用到了cnt次,那么对当前边操作,显然是使得总贡献减少了(w[i]w[i]/2)cnt这么多,根据贪心,我们每次希望这个数最大,这里用优先队列来实现

然后每次取出top​即可,然后再将操作完后的边加入优先队列中去

int n;
ll sum, now;
int h[N], e[M], ne[M], idx;
ll w[M];
struct cmp {
    bool operator () (PLL a, PLL b) const {// 定义排序规则
        return (a.first - a.first / 2) * a.second < (b.first - b.first / 2) * b.second;
    }
};
void add(int a, int b, int c)  // 添加一条边a->b,边权为c
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void solve(int group_Id) {
    read(n, sum);
    now = 0;
    for (int i = 1;i <= n;i ++) h[i] = -1;
    idx = 0;
    VI d(n + 1, 0);
    for (int i = 1, u, v, w;i < n;i ++) {
        read(u, v, w);
        add(u, v, w);
        add(v, u, w);
        d[u] ++, d[v] ++;// 用来判断叶子节点
    }

    VI cnt(n + 1);
    priority_queue<PLL, vector<PLL>, cmp> q;
    function<void(int, int)> dfs1 = [&](int u, int fa) {
        if (d[u] == 1 && u != 1) {
            cnt[u] = 1;
            return ;
        }
        for (int i = h[u];~i;i = ne[i]) {
            int v = e[i];
            if (v == fa) continue;
            dfs1(v, u);
            cnt[u] += cnt[v];
        }
    };
    function<void(int, int)> dfs2 = [&](int u, int fa) {
        for (int i = h[u];~i;i = ne[i]) {
            int v = e[i];
            if (v == fa) continue;
            now += w[i] * cnt[v];// 计算总贡献
            q.push({w[i], cnt[v]});// 对于该边,加入队列中去
            dfs2(v, u);
        }
    };
    dfs1(1, -2);
    dfs2(1, -2);

    // for (int i = 1;i <= n;i ++) {
    //     out(cnt[i]);
    // }
    int res = 0;
    while (now > sum) {
        now -= (q.top().first - q.top().first / 2) * q.top().second;
        q.push({q.top().first / 2, q.top().second});// 操作完后的边
        q.pop();
        res ++;
    }
    cout << res << endl;

}

E2WeightsDivision(hardversion)

E2​,是在E1​的基础上做了加强,现在对于每条边多了一个权值,1或者2,代表如果操作该边,需要花费1或者2,现在问最小花费

显然是可以根据上题一样,采用两个优先队列去维护,但是需要判断的条件过于繁琐,所以这里换一种方法

对于花费为1的边放到集合里面,求出来dis1[i]代表对这个集合里面操作i次,会减少多少贡献,当然方法也是根据上题一样,用优先队列去贪

对于花费为2的同理,求出了dis2[i]

然后,now作为我们最初的总贡献,当我们dis1[i]+dis2[j]>=nowS的时候,显然i+j2是一个答案,为了找到最小的答案,我们i从0到cnt1,j从cnt2到0,每次合法状态取min(res,i+j2)

int n;
ll sum, now;
int h[N], e[M], ne[M], idx;
ll w[M];
int ww[M];
struct cmp {
    bool operator () (PLL a, PLL b) const {
        return (a.first - a.first / 2) * a.second < (b.first - b.first / 2) * b.second;
    }
};
void add(int a, int b, int c, int d)  // 添加一条边a->b,边权为c
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], ww[idx] = d, h[a] = idx ++ ;
}

void solve(int group_Id) {
    read(n, sum);
    now = 0;
    for (int i = 1;i <= n;i ++) h[i] = -1;
    idx = 0;
    VI d(n + 1, 0);
    for (int i = 1, u, v, w, c;i < n;i ++) {
        read(u, v, w, c);
        add(u, v, w, c);
        add(v, u, w, c);
        d[u] ++, d[v] ++;
    }

    VI cnt(n + 1);
    priority_queue<PLL, vector<PLL>, cmp> q1;
    priority_queue<PLL, vector<PLL>, cmp> q2;
    function<void(int, int)> dfs1 = [&](int u, int fa) {
        if (d[u] == 1 && u != 1) {
            cnt[u] = 1;
            return ;
        }
        for (int i = h[u];~i;i = ne[i]) {
            int v = e[i];
            if (v == fa) continue;
            dfs1(v, u);
            cnt[u] += cnt[v];
            now += cnt[v] * w[i];
            if (ww[i] == 1) q1.push({w[i], cnt[v]});
            else q2.push({w[i], cnt[v]});
        }
    };
    
    dfs1(1, -1);
    VL dis1(n * 22), dis2(n * 22);
    int cnt1 = 0, cnt2 = 0;
    
    auto calc = [](VL &dis, priority_queue<PLL, vector<PLL>, cmp> &q, int &ans) {// 计算dis数组
        for (int i = 1;!q.empty();i ++) {
            auto t = q.top();
            q.pop();
            dis[i] = dis[i - 1] + (t.first - t.first / 2) * t.second;
            if (dis[i] == dis[i - 1]) break;
            q.push({t.first / 2, t.second});
            ans = i;
        }
    };
    calc(dis1, q1, cnt1);
    calc(dis2, q2, cnt2);
    ll D = now - sum;
    if (D <= 0) {
        cout << 0 << endl;
        return ;
    }
    ll res = (ll)inf * inf;
    int j = cnt2;
    for (int i = 0;i <= cnt1;i ++) {// i 和 j 可以颠倒,都符合要求
        while (j && dis2[j - 1] + dis1[i] >= D) j --;
        if (dis1[i] + dis2[j] >= D) {
            Min(res, (ll)i + j * 2);
        }
    }
    cout << res << endl;
}

FYetAnotherSegmentsSubset

F​,区间dp问题,给出n(n<3e3)​个线段在x轴上,l,r​分别代表这条线段的起点和终点,线段不重复,现在问在x轴上,取一个区间,使得这个区间内合法线段的个数最多

合法线段:线段之间可以存着包含关系,其中端点可以重复,如果没有包含关系,那么不可以相交,其中端点也不可以重复

一般的区间dp我们习惯是三重循环:枚举起点,枚举长度,枚举中间点,但是显然这里不适用,n3的复杂度会超时

所以考虑n2的复杂度可不可以,那么不可以枚举中间的,我们状态转移就只能:f[l][r]=max(f[l+1][r],f[l][r1])这样去转移,然后考虑中间的覆盖问题

由于线段不重复,我们考虑以l为起点的所有线段,用sta数组记录下来以l为起点的终点的坐标,那么转移:f[l][r]=max(f[l][end]+f[end+1][r],f[l][r])

最后别忘记每个线段的贡献,如果lr是存在线段的,就再让f[l][r]++

void solve(int group_Id) {
    int n;
    read(n);
    VP line(n + 1);
    VI dic;
    for (int i = 1, l, r;i <= n;i ++) {
        read(l, r);
        line[i] = {l, r};
        dic.pb(l);
        dic.pb(r);
    }
    sort(all(dic));
    dic.erase(unique(all(dic)), dic.end());// 数据还是比较大的,离散化一下
    int cnt = len(dic) + 1;
    auto get = [&](int x) {
        return lower_bound(all(dic), x) - dic.begin() + 1;
    };
    for (int i = 1;i <= n;i ++) {
        line[i] = {get(line[i].first), get(line[i].second)};
    }

    VVI sta(cnt + 1);
    vector<vector<bool>> st(cnt + 1, vector<bool> (cnt + 1, false));
    for (int i = 1;i <= n;i ++) {
        // out(line[i].first, line[i].second, cnt)
        sta[line[i].first].pb(line[i].second);// 在起点坐标的位置存下所有终点
        st[line[i].first][line[i].second] = true;// 表示st[l][r]存在线段
    }

    VVI f(cnt + 3, VI (cnt + 3));
    for (int len = 1;len <= cnt;len ++) {
        for (int l = 1;l + len - 1 <= cnt;l ++) {
            int r = l + len - 1;
            f[l][r] = max(f[l + 1][r], f[l][r - 1]);
            for (auto pos : sta[l]) {// 枚举所有终点
                if (pos > r) continue;
                f[l][r] = max(f[l][pos] + f[pos + 1][r], f[l][r]);
            }
            if (st[l][r]) f[l][r] ++;// 最后别忘了+1
        }
    }

    cout << f[1][cnt] << endl;
}
posted @   _Aking  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示