zoukankan      html  css  js  c++  java
  • 2021"MINIEYE杯"中超(4)补题

    2021"MINIEYE杯"中超(4)

    1002 Kanade Loves Maze Designing

    本题点数n最多为2000,因此允许O(n²)的做法,我们用dfs来计算任意两点间的点的种类数即可得出答案。因本题数据量较小,所以无需快速幂优化,直接循环n次预处理出K ^ (j - 1)即可。

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 2010, MOD1 = 1e9 + 7, MOD2 = 1e9 + 9, K = 19560929;
    
    int t;
    int n;
    int cnt[N];
    int am[N][N];
    int c[N];
    int ans;
    int md1[N], md2[N];
    vector<int> v[N];
    
    void cal1(int i, int &k)//计算答案
    {
        for (int j = 1; j <= n; j ++ ) k = (1ll * k + 1ll * am[i][j] * md1[j - 1]) % MOD1;
    }
    
    void cal2(int i, int &k)//计算答案
    {
        for (int j = 1; j <= n; j ++ ) k = (1ll * k + 1ll * am[i][j] * md2[j - 1]) % MOD2;
    }
    
    void init()//预处理数组
    {
        md1[0] = md2[0] = 1;
        for (int i = 1; i < N; i ++ )
        {
            md1[i] = 1ll * md1[i - 1] * K % MOD1;
            md2[i] = 1ll * md2[i - 1] * K % MOD2; 
        }
    }
    //+-1法求[i, j]间点的种类数
    void add(int k)//加操作
    {
        if (!cnt[k]) ans ++ ;
        cnt[k] ++ ;
        return;
    }
    
    void del(int k)//遍历过后的删除操作
    {
        if (cnt[k] == 1) ans -- ;
        cnt[k] -- ;
        return;
    }
    
    void dfs(int now, int to, int fr)//删除操作,now表示从now点开始搜,to表示当前点,fr表示来向防止重复搜
    {
        add(c[to]);//加上此点的值
        am[now][to] = ans;//记录now到此点的结果
        for (auto t : v[to])
            if (t != fr)//防止重复搜索
                dfs(now, t, to);
        del(c[to]);
        
        return;
    }
    
    void solve()
    {
        cin >> n;
        for (int i = 2, x; i <= n; i ++ )
        {
            cin >> x;
            v[i].push_back(x), v[x].push_back(i);
        }
        for (int i = 1; i <= n; i ++ ) cin >> c[i];
        
        for (int i = 1; i <= n; i ++ )
        {
            dfs(i, i, -1);//当前搜i点,因为i点是起点,所以来向标记为-1
            
            int ans1 = 0, ans2 = 0;
            cal1(i, ans1), cal2(i, ans2);
            
            cout << ans1 << ' ' << ans2 << endl;
        }
        
        //for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= n; j ++ ) cout << am[i][j] << (j == n ? '
    ' : ' ');
        for (int i = 1; i <= n; i ++ ) v[i].clear();
    }
    
    int main()
    {
        ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
        init();//初始化
        cin >> t;
        while (t -- ) solve();
        
        return 0;
    }
    
    1007 Increasing Subsequence

    首先思考朴素一点的做法,采用一般处理线性dp的思路。f[i]表示以a[i]结尾的极大子序列,接着考虑贡献,对于j (j < i),只有[j, i]中间没有a[j] ~ a[i]的元素,那么f[j] 才对 f[i]有贡献。f[i]对答案有贡献当且仅当[i ~ n]中没有比a[i]大的元素,所以朴素一点的线性dp解法就有了。O(n²)的复杂度,考虑到n是1e5的级别,会t。

    接着思考优化,处理区间[l, r]时,以m为终点将其分开,然后两边维护两个单调栈,左边单调栈元素值从小到大,下标从大到小,右边单调栈元素值从小到大,下标从小到大。接着考虑贡献,对于f[i] (i > mid),我们找到左边第一个比f[i]小的值a[j],因为右边也维护了一个单调栈,所以栈顶元素即满足要求,如果栈空那么就返回0。然后去找左边单调栈中第一个比a[j]大的值和最后一个比a[i]小的值,那么这个范围内都是对f[i]有贡献的。直接将这些dp值加上即可。为了方便计算,我们可以用一个vector储存左边区间的dp值前缀和,并且在维护单调栈的同时也维护这个前缀和dp的vector。

    code
    #include <bits/stdc++.h>
    
    using namespace std;
    
    typedef pair<int, int> pii;
    
    const int P = 998244353;
    
    int add(int a, int b) {a += b; return a < P ? a : a - P;}
    int sub(int a, int b) {a -= b; return a < 0 ? a + P : a;}
    
    const int N = 100001;
    
    int a[N], dp[N];
    
    void solve(int l, int r)
    {
        if (l + 1 == r) return;
        
        int mid = (l + r) >> 1;
        solve(l, mid);//处理左边
        vector<int> pos(r - l);
        iota(pos.begin(), pos.end(), l);//递增赋值下标
        sort(pos.begin(), pos.end(), [&](int i, int j) { return a[i] < a[j]; });//按照值递增排序排序
        vector<int> ls, rs;
        vector<int> sum(1, 0);//初始化加入一个0,方便前缀和
        for (int i : pos)//pos是有序的,所以每个a[i]一定是比当前在栈中的值都要大
        {
            if (i < mid)
            {
                while (!ls.empty() && ls.back() < i)//在我左边而且值还比我小,一定没有贡献,删掉就行
                    sum.pop_back(), ls.pop_back();
                ls.push_back(i);//将i加入左边序列的单调栈
                sum.push_back(add(sum.back(), dp[i]));//更新左边dp前缀和         
            }
            else 
            {
                while (!rs.empty() && rs.back() > i)//在我右边还比我小,一定没有贡献,同样删掉
                    rs.pop_back();
                if (ls.empty()) continue;//如果左边是空的,没有可以继承过来的状态
                //id1是找到大于a[i]的第一个元素,由于dp前缀和中有一个0,所以偏移一下会到小于a[i]的最后一个元素
                int id1 = partition_point(ls.begin(), ls.end(), [&](int x) { return a[x] < a[i]; }) - ls.begin();
                //id2是找到大于a[rs.back()]的第一个元素
                int id2 = rs.empty() ? 0 : partition_point(ls.begin(), ls.end(), [&](int x) { return a[x] < a[rs.back()]; }) - ls.begin();
                //更新处于右边的dp数组
                dp[i] = add(dp[i], sub(sum[id1], sum[id2]));
                rs.push_back(i);
            }
        }
        solve(mid, r);//递归处理右边
    }
    
    int main(void) 
    {
        int T; scanf("%d", &T);
        while (T--) 
        {
            int n; scanf("%d", &n);
            for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
            for (int i = 1, v = INT_MAX; i <= n; ++i)//处理dp数组
            {
                if (v > a[i])//左边最小的比当前的大,说明以a[i]结尾的有贡献
                    dp[i] = 1;
                else //反之一定会成为其他序列的子序列,没有贡献
                    dp[i] = 0;
                v = min(v, a[i]);
            }
            solve(1, n + 1);//左闭右开
            int ans = 0;
            for (int i = n, v = 0; i >= 1; --i)
            {
                if (v < a[i])
                    ans = add(ans, dp[i]);
                v = max(v, a[i]);
            }
            printf("%d
    ", ans);
        }
        return 0;
    }
    
    1008 Lawn of the Dead

    对于每个点,只有其左边或者上面可以到达,它才可以到达。对于每个地雷,那么它一定挡住了从左边到达的这条路,这时候我们只能考虑从上方到达,因此对于每个地雷[i, j],我们考虑[i - 1, j + 1]这个点向右连续不可到达的区域,那么第i行的这些列的可达情况肯定和其是一致的。因此我们可以通过这种操作算出不可到达的数量,最后用总点数量减去不可到达的数量再减去地雷数得到的就是答案。涉及到区间查询和区间修改的操作,因此可以用线段树维护状态,另外,因为考虑第i行的时候,我们只需要考虑第i - 1行的状态,类比滚动数组的思想,我们只需要建两棵线段树即可完成上述操作。

    code
    #include <iostream>
    #include <cstdio>
    #include <vector>
    #include <algorithm>
    
    using namespace std;
    
    const int N = 200010;
    
    int n, m, k;
    int t;
    vector<int> mine[N];//存每行地雷下标
    struct node
    {
        int p, l, r, val, lmx;//lmx为区间左端点开始连续不可到达的长度
        int lazy;//懒标记
    }tr[2][N * 4];//两棵树
    
    void build(int x, int u, int l, int r)
    {
        tr[x][u].l = l, tr[x][u].r = r, tr[x][u].lazy = -1;//初始化懒标记
        if (l == r)
        {
            tr[x][u].val = tr[x][u].lmx = 0;
            tr[x][u].lazy = -1;
            
            return;
        }
        
        int mid = l + r >> 1;
        build(x, u << 1, l, mid), build(x, u << 1 | 1, mid + 1, r);
        
        //pushup操作
        tr[x][u].val = tr[x][u << 1].val + tr[x][u << 1 | 1].val;
        tr[x][u].lmx = 0;
    }
    
    void pushdown(int x, int u)
    {
        if (tr[x][u].lazy != -1)//打标记了
        {
            //处理左子树
            tr[x][u << 1].val = (tr[x][u << 1].r - tr[x][u << 1].l + 1) * tr[x][u].lazy;
            tr[x][u << 1].lmx = (tr[x][u << 1].r - tr[x][u << 1].l + 1) * tr[x][u].lazy;
            //处理右子树
            tr[x][u << 1 | 1].val = (tr[x][u << 1 | 1].r - tr[x][u << 1 | 1].l + 1) * tr[x][u].lazy;
            tr[x][u << 1 | 1].lmx = (tr[x][u << 1 | 1].r - tr[x][u << 1 | 1].l + 1) * tr[x][u].lazy;
            //传递懒标记
            tr[x][u << 1].lazy = tr[x][u].lazy;
            tr[x][u << 1 | 1].lazy = tr[x][u].lazy;
            //清除父节点懒标记
            tr[x][u].lazy = -1;
        }
    }
    
    void modify(int x, int u, int l, int r, int v)
    {
        if (tr[x][u].l >= l && tr[x][u].r <= r)
        {
            tr[x][u].val = (tr[x][u].r - tr[x][u].l + 1) * v;
            tr[x][u].lmx = (tr[x][u].r - tr[x][u].l + 1) * v;
            tr[x][u].lazy = v;
            
            return;
        }
        
        pushdown(x, u);
        int mid = tr[x][u].l + tr[x][u].r >> 1;
        if (l <= mid) modify(x, u << 1, l, r, v);
        if (r > mid) modify(x, u << 1 | 1, l, r, v);
        //pushup操作
        tr[x][u].val = tr[x][u << 1].val + tr[x][u << 1 | 1].val;//维护val即区间不可到达的数量
        if (tr[x][u << 1].val == tr[x][u << 1].r - tr[x][u << 1].l + 1)//左子树区间全部不可到达
            tr[x][u].lmx = tr[x][u << 1].val + tr[x][u << 1 | 1].lmx;//那么等于左区间尺寸加右区间不可达
        else tr[x][u].lmx = tr[x][u << 1].lmx;//否则就直接等于左区间不可达
        
        return;
    }
    
    int query(int x, int u, int l, int r)
    {
        if (tr[x][u].l == l && tr[x][u].r == r) return tr[x][u].lmx;//此区间完全包含查询区间
        
        pushdown(x, u);
        int mid = tr[x][u].l + tr[x][u].r >> 1;
        if (r <= mid) return query(x, u << 1, l, r);
        if (l > mid)  return query(x, u << 1 | 1, l, r);
        
        int temp = query(x, u << 1, l, mid);//查询左子树,左边全部到不了再查右子树
        if (temp == mid - l + 1) return temp + query(x, u << 1 | 1, mid + 1, r);
        else return temp;
    }
    
    int main()
    {
        ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
        cin >> t;
        while (t -- )
        {
            cin >> n >> m >> k;
            build(0, 1, 1, m), build(1, 1, 1, m);//建立两棵线段树
            for (int i = 0; i <= n + 10; i ++ ) mine[i].clear();//清空vector
            int x, y;
            for (int i = 1; i <= k; i ++ )
            {
                cin >> x >> y;
                mine[x].push_back(y);
            }    
            
            long long ans = 0;//储存不可到达的非地雷区域数目
            modify(0, 1, 1, m, 1);//处理第0行,懒标记为1表示不可到达
            for (int i = 1; i <= n; i ++ )//一行一行处理
            {
                sort(mine[i].begin(), mine[i].end());//将地雷按照纵坐标升序排列
                mine[i].push_back(m + 1);//保证最后一个区间一定有右端点,加一个哨兵
                int last = 0;//此行上一个雷的位置
                modify((i & 1), 1, 1, m, 0);//清空线段树
                
                for (int j = 0; j < mine[i].size(); j ++ )//遍历所有雷
                {
                    int poi = mine[i][j];//雷的位置
                    if (i == 1 && !last) {last = poi; continue;}//处理第一行
                    int l = last + 1, r = poi - 1, len;//处理区间[l + 1, r - 1]
                    if (l <= r)
                    {
                        len = query((i & 1) ^ 1, 1, l, r);//查询上一行
                        ans += 1ll * len;
                    }
                    else len = 0;//否则此区间不存在不可到达的点
                    if (!(l - 1 == 0 && l - 1 + len == 0)) modify((i & 1), 1, l - 1, l - 1 + len, 1);//标记左侧的地雷
                    last = poi;//更新上一个地雷的位置
                }
            }
            
            cout << 1ll * n * m - ans - 1ll * k << endl;
        }
        
        return 0;
    }
    
  • 相关阅读:
    多屏显示
    Scss sass
    静态页面常用到
    display:flex
    怎么看服务器是属于阿里云的还是腾讯云
    介绍一款好用 mongodb 可视化工具
    图解Mongo Shell的使用
    Win10 安装配置 MongoDB 4.0 踩坑记
    phpexcel来做表格导出(多个工作sheet)及设置单元格格式
    phpexcel 导出方法
  • 原文地址:https://www.cnblogs.com/scl0725/p/15101476.html
Copyright © 2011-2022 走看看