• NCD2019部分题解


    A

    解题思路

      突破点在二分答案上,如果想到二分的话后面就好做了。
      假设我们二分答案的大小为x,判断是否可行,首先肯定需要在长度不小于2x的线段中找。考虑枚举竖线来找符合条件的横线,对于一条竖线\({x_1, y_1, c_1}(x_1 \leq y_1)\)来说,需要判断是否存在一条横线\({x_2, y_2, c_2}(x_2\leq y_2)\),满足\(x_1+x \leq c_2 \leq y_1-x\)\(x_2+x \leq c_1 \leq y_2-x\),我们的问题有两个维度,如果想同时判断的话十分麻烦,可以考虑如何消去其中一维的影响,考虑另一维。
      这里有一个经典套路,我们发现对于横坐标x,一共分为三类,一类是横线的左端点,一类是横线的右端点,还有一类是竖线的横坐标。我们对于所有横坐标以及种类进行双关键字排序,从小到大遍历这些横坐标,如果当前横坐标是左端点,我们就把它对应的横线的纵坐标加进集合里,如果是右端点,因为横坐标是递增的,那么这个点对应的横线必定和后面的竖线没有交叉,将其从集合中删去,如果是竖线的横坐标,那我们就从集合里找一个大于等于竖线较小的纵坐标的最小的点,如果这个点小于竖线的较大的那个纵坐标,就说明有解。

    代码

    struct INFO {
        int a, b, c, tp;
    } sgx[maxn], sgy[maxn];
    int n, m; 
    int check(int x) {
        int t1 = 0, t2 = 0;
        vector<INFO> sg;
        for (int i = 1; i<=n; ++i) {
            if (sgx[i].b-sgx[i].a<2*x) continue;
            sg.push_back({sgx[i].a+x, 0, sgx[i].c, 1});
            sg.push_back({sgx[i].b-x, 0, sgx[i].c, 3});
        }
        for (int i = 1; i<=m; ++i) {
            if (sgy[i].b-sgy[i].a<2*x) continue;
            sg.push_back({sgy[i].c, sgy[i].a+x, sgy[i].b-x, 2});
        }
        multiset<int> st;
        sort(sg.begin(), sg.end(), [](INFO A, INFO B) {return A.a==B.a ? A.tp<B.tp:A.a<B.a;});
        for (auto v : sg) {
            if (v.tp==1) st.insert(v.c);
            else if (v.tp==3) st.erase(st.find(v.c));
            else {
                auto it = st.lower_bound(v.b);
                if (it!=st.end() && *it<=v.c) return 1;
            }
        }
        return 0;
    }
    int main() { 
        IOS;
        int __; cin >> __;
        while(__--) {
            cin >> n >> m;
            for (int i = 1; i<=n; ++i) {
                cin >> sgx[i].a >> sgx[i].b >> sgx[i].c;
                if (sgx[i].a>sgx[i].b) swap(sgx[i].a, sgx[i].b);
            }
            for (int i = 1; i<=m; ++i) {
                cin >> sgy[i].a >> sgy[i].b >> sgy[i].c;
                if (sgy[i].a>sgy[i].b) swap(sgy[i].a, sgy[i].b);
            }
            int l = 0, r = 1e5;
            while(l<r) {
                int mid = (l+r+1)>>1;
                if (check(mid)) l = mid;
                else r = mid-1;
            }
            cout << l << endl;
        }
        return 0;   
    } 
    

    B

    解题思路

      不难看出,能抓住Hasan的边是割边,而别的边没有影响。我们先对图中的所有连通块中的边双连通分量进行缩点,缩点后的图就变成了一片森林,我们在森林中的两棵树中连边,会变成一棵树,而割边数量会加1,如果我们对其中的一棵树上的两点连一条边,会发现两点简单路径上的所有点都会变成一个新的边双连通分量,减少的割边数量就是他们的最短距离,所以我们需要找到每棵树上任意两点的最长的最短距离,即树的直径,然后用割边数量-树的直径即为答案。

    代码

    const int maxn = 1e6+10;                                                               
    const int maxm = 1e6+10;
    struct E {
        int to, nxt;
    } e[maxm];
    int h[maxn], tot = 1;
    void add(int u, int v) {
        e[++tot] = {v, h[u]};
        h[u] = tot;
    }
    int n, m;
    int dfn[maxn], low[maxn], __dfn;
    int sk[maxn], tp, dcc, id[maxn];
    int isbrige[maxn];
    vector<int> g[maxn];
    void tarjan(int u, int fa) {
        dfn[u] = low[u] = ++__dfn;
        sk[++tp] = u;
        for (int i = h[u]; i; i = e[i].nxt) {
            if ((fa^1)==i) continue;
            int v = e[i].to;
            if (!dfn[v]) {
                tarjan(v, i);
                low[u] = min(low[u], low[v]);
                if (low[v]>dfn[u]) isbrige[i] = isbrige[i^1] = 1;
            }
            else low[u] = min(low[u], dfn[v]);
        }
        if (low[u]==dfn[u]) {
            int v; ++dcc;
            do {
                v = sk[tp--];
                id[v] = dcc;
            } while(v!=u);
        }
    }
    int maxx, vis[maxn];
    int dfs(int u) {
        vis[u] = 1;
        int f = 0;
        for (auto v : g[u]) {
            if (vis[v]) continue;
            int dis = dfs(v)+1;
            maxx = max(maxx, f+dis);
            f = max(f, dis);
        }
        return f;
    }
    void init() {
        __dfn = tp = dcc = 0;
        clr(isbrige, 0);
        tot = 1;
        for (int i = 0; i<=n; ++i) {
            h[i] = vis[i] = dfn[i] = low[i] = sk[i] = id[i] = 0;
            g[i].clear();
        }
    }
    int main() { 
        IOS;
        int __; cin >> __;
        while(__--) {
            cin >> n >> m;
            init();
            for (int i = 1, a, b; i<=m; ++i) {
                cin >> a >> b;
                add(a, b); add(b, a);
            }
            for (int i = 1; i<=n; ++i) 
                if (!dfn[i]) tarjan(i, -1);
            int ans = 0;
            for (int i = 1; i<=n; ++i)
                for (int j = h[i]; j; j = e[j].nxt) {
                    int v = e[j].to;
                    if (isbrige[j]) g[id[i]].push_back(id[v]), ++ans;
                }
            ans /= 2;
            maxx = 0;
            for (int i = 1; i<=dcc; ++i)
                if (!vis[i]) dfs(i);
            ans -= maxx;
            cout << ans << endl;
        }
        return 0;   
    } 
    

    C

    解题思路

      这题也是经典套路了,考虑\(O(n^2)\)的做法,设dp[j]为以j结尾的lis长度,我们在遍历前面比当前a[i]小的数a[j]的时候,如果dp[j]可以更新答案,那么方案数同dp[j]的方案数,如果dp[j]+1和dp[i]相同,那么方案数就可以累加。

    代码

    const int maxn = 2e5+10;                                                               
    const int maxm = 2e6+10;
    int a[maxn], dp[maxn], cnt[maxn];
    int main() { 
        IOS;
        int __; cin >> __;
        while(__--) {
            int n; cin >> n;
            for (int i = 1; i<=n; ++i) cin >> a[i];
            int maxx = 0, sum = 0;
            for (int i = 1; i<=n; ++i) {
                cnt[i] = dp[i] = 1;
                for (int j = i-1; j>=1; --j) {
                    if (a[j]<a[i]) {
                       if (dp[i]<dp[j]+1) {
                           dp[i] = dp[j]+1;
                           cnt[i] = cnt[j];
                       }
                       else if (dp[i]==dp[j]+1) {
                           cnt[i] = (cnt[i]+cnt[j])%MOD;
                       }
                    }
                }
                if (dp[i]>maxx) maxx = dp[i], sum = cnt[i];
                else if (dp[i]==maxx) sum = (sum+cnt[i])%MOD;
            }
            cout << maxx << ' ' << sum << endl;
        }
        return 0;   
    }
    

    D略

    E

    解题思路

      如果没有修改操作只有询问的话,我们求正反两个字符串的哈希值就能做,如果加上修改操作,比如i的位置修改一下,对于正的字符串来说,i到n的哈希值会改变,对于颠倒后的字符串的哈希值来说,n-i+1到n的哈希值会改变。
      如果是按照常规的计算哈希值的方法的话,我们就是对区间修改一个变量,这样维护起来十分的痛苦。我的方法是把每一个哈希值都往后补0,使得每一个哈希值对应的都是P进制下的一个n位整数,这样带来的好处是,由于长度是固定的,那么修改i的值之后,i到n这个区间上改变的值也是一样的,我们区间修改的就是一个常量了。这样我们只要写一个区间修改,单点询问的线段树。修改操作就不说了,对于查询,我们用r对应的值减去l-1对应的值,再去掉末尾0,得到一段字符串的哈希值,然后用类似的方法求出颠倒后的区间的哈希值,两者比较即可。

    const int maxn = 1e5+10;                                                               
    const int maxm = 2e6+10;
    ll lz[maxn<<2][2], tr[maxn<<2][2], f[maxn], h[maxn][2];
    inline void push_down(int rt, int s) {
        if (lz[rt][s]) {
            tr[rt<<1][s] = (tr[rt<<1][s]+lz[rt][s]+MOD)%MOD;
            tr[rt<<1|1][s] = (tr[rt<<1|1][s]+lz[rt][s]+MOD)%MOD;
            lz[rt<<1][s] = (lz[rt<<1][s]+lz[rt][s]+MOD)%MOD;
            lz[rt<<1|1][s] = (lz[rt<<1|1][s]+lz[rt][s]+MOD)%MOD;
            lz[rt][s] = 0;
        }
    }
    void build(int rt, int l, int r, int s) {
        tr[rt][s] = lz[rt][s] = 0;
        if (l==r) {
            tr[rt][s] = h[l][s];
            return;
        }
        int mid = (l+r)>>1;
        build(rt<<1, l, mid, s);
        build(rt<<1|1, mid+1, r, s);
    }
    void update(int rt, int l, int r, int L, int R, int V, int s) {
        if (l>=L && r<=R) {
            tr[rt][s] = (tr[rt][s]+V+MOD)%MOD;
            lz[rt][s] = (lz[rt][s]+V+MOD)%MOD;
            return;
        }
        push_down(rt, s);
        int mid = (l+r)>>1;
        if (L<=mid) update(rt<<1, l, mid, L, R, V, s);
        if (R>mid) update(rt<<1|1, mid+1, r, L, R, V, s);
    }
    ll ask(int rt, int l, int r, int pos, int s) {
        if (!pos) return 0;
        if (l==r) return tr[rt][s];
        push_down(rt, s);
        int mid = (l+r)>>1;
        if (pos<=mid) return ask(rt<<1, l, mid, pos, s);
        else return ask(rt<<1|1, mid+1, r, pos, s);
    }
    void ck(int rt, int l, int r, int s) {
        if (l==r) {
            cout << tr[rt][s] << ' ';
            return;
        }
        push_down(rt, s);
        int mid = (l+r)>>1;
        ck(rt<<1, l, mid, s);
        ck(rt<<1|1, mid+1, r, s);
    }
    char str[maxn], str2[maxn];
    int n, m;
    void change(int pos, ll ff) {
        ll x = 1ll*(str[pos])*f[n-pos]%MOD;
        update(1, 1, n, pos, n, MOD+ff*x, 0);
        int rpos = n-pos+1;
        ll y = 1ll*(str2[rpos])*f[n-rpos]%MOD;
        //cout << x << ' ' << y << endl;
        update(1, 1, n, rpos, n, MOD+ff*y, 1);
    }
    ll qp(ll x, ll y) {
        x %= MOD;
        ll res = 1;
        while(y) {
            if (y&1) res = res*x%MOD;
            x = x*x%MOD;
            y >>= 1;
        }
        return res;
    }
    int main() { 
        IOS;
        f[0] = 1;
        for (int i = 1; i<maxn; ++i) f[i] = f[i-1]*P%MOD;
        int __; cin >> __;
        while(__--) {
            cin >> n >> m;
            cin >> str+1;
            for (int i = 1; i<=n; ++i) str2[i] = str[n-i+1];
            for (int i = 1; i<=n; ++i) h[i][0] = (h[i-1][0]+1ll*(str[i])*f[n-i])%MOD;
            for (int i = 1; i<=n; ++i) h[i][1] = (h[i-1][1]+1ll*(str2[i])*f[n-i])%MOD;
            build(1, 1, n, 0);
            build(1, 1, n, 1);
            int op, l, r, pos; char ch[10];
            while(m--) {
                cin >> op;
                if (op==1) {
                    cin >> pos >> ch;
                    change(pos, -1);
                    str[pos] = ch[0];
                    str2[n-pos+1] = ch[0];
                    change(pos, 1);
                }
                else {
                    cin >> l >> r;
                    ll A = (ask(1, 1, n, r, 0)-ask(1, 1, n, l-1, 0)+MOD)%MOD;
                    //cout << A << endl;
                    A = A*qp(f[n-r], MOD-2)%MOD;
                    A = (A%MOD+MOD)%MOD;
                    l = n-l+1, r = n-r+1;
                    if (l>r) swap(l, r);
                    //cout << l << ' ' << r << endl;
                    ll B = (ask(1, 1, n, r, 1)-ask(1, 1, n, l-1, 1)+MOD)%MOD;
                    B = B*qp(f[n-r], MOD-2)%MOD;
                    B = (B%MOD+MOD)%MOD;
                    //cout << A << ' ' << B << endl;
                    if (A==B) cout << "Adnan Wins" << endl;
                    else cout << "ARCNCD!" << endl;
                }
                //ck(1, 1, n, 0); cout << endl;
                //ck(1, 1, n, 1); cout << endl;
                //cout << "!" << endl;
            }
        }
        return 0;   
    } 
    

    F略

    G

      (代补)

    H略

    J

    解题思路

      很简单的题,题目就两种操作,一种区间加1,一种询问区间出现最多的数,而且就一个询问,直接差分数组做就行了。

    const int maxn = 1e6+10;                                                               
    const int maxm = 1e6+10;
    int a[maxn], b[maxn], sub[maxn];
    int main() { 
        IOS;
        int __; cin >> __;
        while(__--) {
            int n, m; cin >> n >> m;
            for (int i = 0; i<=n; ++i) sub[i] = 0;
            for (int i = 1; i<=m; ++i) cin >> a[i], --a[i];
            for (int i = 1; i<=m; ++i) {
                cin >> b[i];
                if (abs(b[i])==n) {
                    ++sub[a[i]];
                    --sub[a[i]+1];
                    ++sub[0];
                    --sub[n];
                }
                else if (b[i]>=0) {
                    ++sub[a[i]];
                    --sub[min(n, a[i]+b[i]+1)];
                    if (a[i]+b[i]>=n) {
                        b[i] = (a[i]+b[i])%n;
                        ++sub[0];
                        --sub[b[i]+1];
                    }
                }
                else if (b[i]<0) {
                    ++sub[max(0, a[i]+b[i])];
                    --sub[a[i]+1];
                    if (a[i]+b[i]<0) {
                        b[i] = (a[i]+b[i])+n;
                        ++sub[b[i]];
                        --sub[n];
                    }
                }
            }
            for (int i = 1; i<=n; ++i) sub[i] += sub[i-1];
            int c = 0;
            for (int i = 0; i<n; ++i) {
                if (sub[i]>sub[c]) c = i;
                //cout << sub[i] << endl;
            }
            cout << c+1 << ' ' << sub[c] << endl;
        }   
        return 0;   
    } 
    
    

    K略

    L略

    M

      经典取log,需要注意底数为0的时候会返回-inf,而两个-inf不相等

  • 相关阅读:
    scala之伴生对象的继承
    scala之伴生对象说明
    “Failed to install the following Android SDK packages as some licences have not been accepted” 错误
    PATH 环境变量重复问题解决
    Ubuntu 18.04 配置java环境
    JDBC的基本使用2
    DCL的基本语法(授权)
    ZJNU 1374
    ZJNU 2184
    ZJNU 1334
  • 原文地址:https://www.cnblogs.com/shuitiangong/p/15766678.html
Copyright © 2020-2023  润新知