zoukankan      html  css  js  c++  java
  • SSL-OI夏日合宿 2020.08.17 A组

    SSL-OI夏日合宿 2020.08.17 A组

    今天是他的生日,让我们祝他生日快乐!

    这套题是北大爷不知道从哪里找来的, 只知道出题人署名是 HRupd:刚刚北大爷悄悄透露了, 大佬叫(胡睿), 裸考被屏蔽强的一批QwQ.

    今天也是卑微爆0, 下次一定(?)认真打简单题&暴力.

    博客好久没更了, 主要是因为腐太多了? 还是要好好打一打题解的, 不然真的啥都没留下就退役了.

    今天的代码估计要咕, 还是先打完博客梳理一下思路吧.

    后续如果能改出来的话, 会更新在Github.

    T1 图

    题意

    给一个n个点n条边的图, 边权表示所连的两点的权值和. 给出边权, 求点权.

    正解

    显然这是一棵基环树, 图中的环是一个(k)元一次方程组, 可以求解. 环上的点权解出后, 对于每棵树上的点权直接求出.

    有关x元一次方程组的求解, 可以用高斯消元, 但在这道题中有更加简单的解法: 考虑到每个方程组只有两个非({0})系数且系数都为({1}), 可以用钦定一个点(x)作为起点, 从它的一条出边开始遍历环, 将环上的(k)条边都表示成含(x)的式子. 显然, 当我们回到点(x)时,这条边上的方程只有变量(x)且系数为({2}).

    #define MXN (100020)
    
    #include <math.h>
    #include <stdio.h>
    #include <string.h>
    
    #include <vector>
    
    int n;
    
    struct Edge {
        int v, w;
    } cer[MXN];
    std::vector<Edge> edge[MXN];
    int cnt;
    
    int nc;
    bool vis[MXN], in;
    void dfs1(int x, int f) {
        if (vis[x]) {
            nc = x;
        } else {
            vis[x] = true;
            for (int i = edge[x].size() - 1; !nc && i >= 0; --i) {
                if (edge[x][i].v != f) {
                    dfs1(edge[x][i].v, x);
                    if (in) cer[++cnt] = edge[x][i];
                }
            }
            vis[x] = false;
        }
        if (x == nc) in = !in;
    }
    
    double ans[MXN];
    
    void dfs2(int x) {
        vis[x] = true;
        for (int i = edge[x].size() - 1; i >= 0; --i)
            if (!vis[edge[x][i].v])
                ans[edge[x][i].v] = edge[x][i].w - ans[x], dfs2(edge[x][i].v);
    }
    
    signed main() {
    #ifndef ONLINE_JUDGE
        freopen("A.in", "r", stdin);
    #endif
    
        scanf("%d", &n);
    
        for (int i = 0, x, y, w; i < n; ++i)
            scanf("%d%d%d", &x, &y, &w), edge[x].push_back(Edge{y, w}), edge[y].push_back(Edge{x, w});
    
        dfs1(1, 0);
    
        for (int i = 1, p = -1; i <= cnt; ++i)
            ans[cer[1].v] += (p *= -1) * cer[i].w;
        ans[cer[1].v] /= 2.0;
    
        dfs2(cer[1].v);
    
        for (int i = 1; i <= n; ++i)
            printf("%g
    ", ans[i]);
    
        return 0;
    }
    

    T2 上升子序列

    题意

    给出一个数组, 要求将其分为两个上升子序列, 求两个子序列的差最小.(无解输出({-1}))

    正解

    考虑到每一个值都必须在某个上升子序列中, 那么对于每个逆序对, 都必在两个不同的上升子序列中. 若每个逆序对连一条边跑二分图染色, 若染色成功会留下若干个连通块. 若染色不成功, 则序列不合法.

    证明 每个连通块的值域没有交集: 若值域有交集, 前面一个区间的最大值和后面一个区间的最小值是一个逆序对, 必有连边.

    所以我们只用枚举一个分界点. (i,i+1)是两个连通块的分界点, 当且仅当(max[1,i]<min(i,N]).

    然后对于每个连通块区间, 判断是否合法. 想起一道经典题<导弹拦截>: 最小划分上升子序列数等于最长降, 所以只要存在一个长度为(3)的下降序列, 这个区间就不合法.

    对于每个连通块, 最多只有一种划分方法. 所以只要求出一个最长上升子序列, 其中必包含每个逆序对的其中一个. 用区间长度减去最长升长度, 得到每个连通块的差值. 要使总差值最小, 只要DP每个区间取({+})或取({-}).

    
    

    T3 大冰隙2

    题意

    给出一个序列, 每个元素(i)包含两个值: 种类 ({b_iin{0,1}}) , 数值 ({c_i<2^{30}}).

    支持两个操作:

    修改: 单点修改某个元素的数值.

    查询: 相邻两个种类为的元素会被消去(不计入答案), 某一段消去后其左右两边视为相邻. 求一段区间中没被消去的元素的最大数值. (无剩余输出({0}))

    思路 (故事)

    暴力的日子就要用暴力的解法.很显然这题是一个区间查询问题, 那么我们用莫队; 这题有修改, 那么我们用带修莫队; 这题合并不可逆, 那么我们用回滚莫队. 另外还要维护一棵平衡树(multiset)支持插入删除求最大.

    综上: 这题可以用带修回滚莫({Nsqrt{N}log_2{N}})的复杂度获得(?)分的好成绩. (正常都是因为没打出来而爆0吧?)

    正解1 树剖

    我们发现有顺序的({01})抵消很像出入栈顺序, 我们把一段({01})序列视作某棵树的dfs序的一部分, ({0})表示向下遍历, ({1})表示向上回溯.
    注意: 若有"多余"的({1})则新建一个点作为新的根, 将旧的根作为其儿子.

    如此建树后, 每棵棵子树内的所有点都没有贡献, 区间查询问题变成了树上两点间的路径最大值查询, 我们用树链剖分即可.
    注意: 由于边权有向的, 设(x<y)有: 链 ({x}) -> ({LCA_{x,y}})应取向上边权, 链 ({LCA_{x,y}}) -> ({y})应取向下边权. 我们开两棵线段树分别处理向上/向下边权即可.

    
    

    正解2 树套树

    显然对于某段区间, 充分合并后只剩余若干个({1})和若干个({0}), 形如({111100}).

    我们维护一个线段树, 对于每个区间维护({1})的数量和({0})的数量, 再用线段树分别对于({1})({0})处理出区间最大值.

    合并时两个区间中间同时消去(min(0_{Left},1_{Right})), 再将剩下区间的线段树合并.

    
    
  • 相关阅读:
    PowerToys插件扩展(类似Alfred)
    .net打独立运行环境遇到无法trim遇到的bug
    blazor wasm开发chrome插件
    将php代码部署到新浪云测试(简单方法,包含数据库的连接)
    python修改csv某一列的内容
    windows10调用libcurl
    Java动态脚本Groovy,高级啊!
    Linux 配置Git
    Java动态脚本Groovy读取配置文件
    Java动态脚本Groovy获取Bean(奇淫技巧操作)
  • 原文地址:https://www.cnblogs.com/mxxr/p/13519612.html
Copyright © 2011-2022 走看看