zoukankan      html  css  js  c++  java
  • 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不相等

  • 相关阅读:
    java 验证码
    时间日期转换+两个日期相减
    java创建文件和目录
    java获取文件名的三种方法
    获取当前日期前100天的日期
    字符串去重
    Java,数据库中的数据导入到Excel
    两个list<object> 比较 得到相同数据 差异数据
    Springmvc中@RequestParam传值中文乱码解决方案
    将src非空的属性注入到des中
  • 原文地址:https://www.cnblogs.com/shuitiangong/p/15766678.html
Copyright © 2011-2022 走看看