zoukankan      html  css  js  c++  java
  • 第十二场湖南多校对抗赛总结

    第十二场湖南多校对抗赛总结


    闲来无事写了一发,太菜只会写4题,大家都会的题硬是写不来

    A

    题意:给n个点,把这n个点变成平行x轴且相互挨着,求所有点需要移动的曼哈顿距离之和的最小值,$ n <= 1e5 $。
    思路:
    y轴上都知道是中位数,然后x轴上本弱写不来了,一直想着是左右挪,想不到一个可行的做法。看了题解才知道,其实也是中位数,只不过变了一下形。
    假设最后的结果x轴上的左端点为a
    那么x轴上的贡献 (ansX) = $ sum_{i=0}^{n-1}|Xi - (a + i)| $ = $sum_{i=0}^{n}|Xi - i - a| $

    #include<bits/stdc++.h>
    #define LL long long
    #define P pair<int,int>
    using namespace std;
    
    const int N = 1e5 + 10;
    vector<P> v1,v2;
    
    int main(){
    
        int n;
        while(scanf("%d",&n)==1){
            v1.clear(),v2.clear();
            for(int i = 0;i < n;i++){
                int x , y;
                scanf("%d%d",&x,&y);
                v1.push_back(P(x,y));
                v2.push_back(P(y,x));
            }
            sort(v1.begin(),v1.end());
            sort(v2.begin(),v2.end());
            LL ans = 0,mid;
            if(n % 2 == 0) mid = (v2[n / 2].first + v2[n / 2 - 1].first) / 2;
            else mid = v2[n/2].first;
            for(int i = 0;i < v2.size();i++) ans += abs(mid - v2[i].first);
            for(int i = 0;i < v1.size();i++) v1[i].first -= i;
            sort(v1.begin(),v1.end());
            if(n % 2 == 0) mid = (v1[n / 2].first + v1[n / 2 - 1].first) / 2;
            else mid = v1[n/2].first;
            for(int i = 0;i < v1.size();i++) ans += abs(v1[i].first - mid);
            cout<<ans<<endl;
        }
        return 0;
    }
    
    
    

    B

    题意:长者对小明施加了膜法,使得小明每天起床就像马丁的早晨一样。 今天小明早上醒来发现自己成了一位仓管员。仓库可以被描述为一个n × m的网格,在每个网格上有几个箱子(可能没有)。为了防止倾倒,每个网格上,箱子不应该堆放超过h个。为了满足要求,小明需要搬一些箱子,每一次,他可以把箱子从一个网格到相邻的网格(如果两个网格共享一条边)。 请计算为了满足要求,小明至少需要搬多少次。

    思路: 裸的费用流,相邻点建费用为1的边即可,边有点多,普通的板子似乎有点慢,题解说zkw费用只要40ms,有时间去学习一波。

    C(待补)

    题意:有n个团队,每个团队的工资为$ a_i $,有两种操作
    2 x y 合并x和y团队所在的集合
    1 x 查询团队x所属的集合中 第 (frac{cnt + 1}{2})小的工资

    思路:

    方法一:并查集+线段树合并
    用并查集维护团队的联通关系,用线段树维护每种工资是否出现过。
    首先,把工资离散化一下,如果有相等的工资,我们需要处理一下,最后把工资变成一个1~n的排列
    然后线段树的第x个叶子的值等于0或者1,如果为1,表示这个工资在这个树里出现过。
    最初的时候build出n条链,初始化并查集。
    合并的时候,维护并查集,并把根对应的线段树合并。
    查询的时候,直接在x对应的根对应的线段树里,通过线段树的结构二分到具体第k个叶子为1的节点是谁即可。
    复杂度(O((n+m)logn))

    说得高大上一点,这个权值线段树就是主席树,主席树可以求用来区间第k大,区间内不同数字等等问题。
    复习一下主席树,首先得将所有权值离散化,区间[L,R]就代表权值在[L,R]的结点的一些信息

    • 每插入一个点 最多修改了(logn)个区间,所以这里插入的复杂度为(nlogn)
      所以只需要新开一个根 将这(logn)个区间修改即可,所以空间复杂度为(O(nlogn))
    • (k)大问题的时候,只需要比较左右区间的个数,然后递归查找即可
    • 求区间不同数字的时候,定义pre[i]为在i前面最接近i的满足a[l] = a[i]的l
      这样我们查询区间[L,R]时 其实也就是查询区间内pre[l]<l的数字的个数
    • 主席树合并的时候 至少会合并(1)个区间,最多会合并(n)个区间
      前者最多会发生n-1次,后者最多只会发生一次,所以时间复杂度和空间复杂度同为(O(nlogn))
    #include<bits/stdc++.h>
    #define LL long long
    #define ls(i) seg[i].lc
    #define rs(i) seg[i].rc
    using namespace std;
    
    const int N = 2e5 + 10;
    inline int read(){
        int x = 0;
        char c = getchar();
        while(c < '0' || c > '9') c = getchar();
        while(c >= '0' && c <= '9') x = x * 10 + c - 48,c = getchar();
        return x;
    }
    int pa[N],siz[N];
    int Find(int x){
        return pa[x] == x?x:pa[x] = Find(pa[x]);
    }
    int a[N],b[N];
    struct node
    {
        int lc,rc,cnt;
    } seg[N * 20];
    int n, nn, m, tot;
    int root[N];
    void update(int &cur,int l,int r,int val)
    {
        seg[++tot] = seg[cur];
        cur = tot;
        seg[cur].cnt++;
        if(l >= r) return ;
        int mid = l + r >> 1;
        if(val <= mid) update(seg[cur].lc,l,mid,val);
        else update(seg[cur].rc,mid+1,r,val);
    }
    int kth(int rt,int l,int r,int k)
    {
        if(l == r) return l;
        int cnt = seg[ls(rt)].cnt;
        int mid = l + r>>1;
        if(k<=cnt) return kth(ls(rt),l,mid,k);
        else return kth(rs(rt),mid+1,r,k-cnt);
    }
    void Merge(int &ru,int rv){///合并两颗主席树,其实就是对应区间合并
        if(!ru || !rv){///有一颗主席树不存在该区间,直接返回另一颗的该区间或者返回空
            if(!ru) ru = rv;
            return ;
        }
        seg[ru].cnt += seg[rv].cnt;
        Merge(ls(ru),ls(rv));
        Merge(rs(ru),rs(rv));
    }
    int main(){
        while(scanf("%d%d",&n,&m)==2){
            for(int i = 1;i <= n;i++){
                pa[i] = i;
                b[i-1] =  a[i] = read();
            }
            sort(b,b + n);
            nn = unique(b,b+n) - b;
            for(int i = 1;i <= n;i++) a[i] = lower_bound(b, b + nn, a[i]) - b + 1;
            root[0] = tot = 0;
            for(int i = 1;i <= n;i++){
                siz[i] = 1;
                root[i] = root[0];
                update(root[i],1,nn,a[i]);
            }
            for(int i = 0;i < m;i++){
                int o, u, v, fu, fv;
                o = read(),u = read();
                if(o == 2){
                    v = read();
                    fu = Find(u), fv = Find(v);
                    siz[fu] += siz[fv], pa[fv] = fu;
                    Merge(root[fu],root[fv]);
                }else{
                    int f = Find(u),sz = (siz[f]+1)/2;
                    printf("%d
    ",b[kth(root[f],1,nn,sz)-1]);
                }
            }
        }
        return 0;
    }
    

    D(待补)

    题意:构造一个(n)对括号的合法序列,使得第(x)个左括号对应的右括号(p1)在第(y)左个括号对应的右括号(p2)之后((x <y)),即(p1 > p2),求满足条件的合法序列

    思路: 卡特兰三角
    对于整个序列可分为3个部分:第x个左括号之前,第x个左括号对应的右括号之后,以及这2个括号内。

    首先先要知道一个公式:
    (C_{m}(n,k))表示一个序列中有n个X和k个Y,满足"对于这个序列的任何前缀,Y出现的次数-X的出现的次数< m"的组合数。

    [egin{equation} C_{m}(n,k)= egin{cases} inom{n + k}{k} & 0leq k < m \ inom{n+k}{k}-inom{n+k}{k-m} & mleq k leq n + m - 1\ 0 & k > n + m - 1 end{cases} end{equation} ]

    简单分析一下这个公式是怎么来的
    类比一下n对合法的括号序列的方案数 (inom{n + n}{n} - inom{n + n}{n - 1})
    如何求出不合法的序列呢?
    假设在位置x处出现了前面所有右括号的数量比左括号的数量多1,则这个序列就已经不合法了,再看此时x后面所有的左括号数量会比右括号多1,若我们把后面左右括号数量互换一下,则右括号数量变成了n+1。其实也就意味着对于所有不合法的序列(即某处右括号比左多),互换后面括号数量之后对应着所有n+1个右括号和n-1个左括号组成的序列,即它们的全排列(inom{n + n}{n - 1})
    然后这里对于一个序列中有n个X和k个Y
    其实也就是说某处出现了(cnt_r - cnt_l = m)的情况,后面左括号有(n - cnt_l)个,右括号有(k - cnt_r)个,互换之后右括号共有(n - cnt_l + cnt_r = n + m)个 即(inom{n + k}{n + m} = inom{n + k}{k - m})

    ①对于最后一部分,枚举这一部分内有多少对括号((cnt∈[y-x,N-x])),组合数可以用卡特兰数求解(因为这个括号内部一定是符合规则的括号序列)。
    ②对于前两部分,我们可以枚举第一部分有多少个右括号(右括号数量(cnt_1<x)),这一部分一定要满足(n=x-1,k=cnt_1,m=1)的卡特兰三角,第二部分的右括号数=(N-1-cnt-cnt_1) ,左括号数=(N-x-cnt),那么第二部分要满足(n=N-x-cnt,k=N-1-cnt-cnt_1,m=x-cnt_1)的卡特兰三角

    #include<bits/stdc++.h>
    #define LL long long
    using namespace std;
    const int N = 1010;
    const int mod = 1e9 + 7;
    int C[N][N];
    void init(){
        for(int i = 0;i < N;i++) C[i][i] = C[i][0] = 1;
        for(int i = 1;i < N;i++){
            for(int j = 1;j < i;j++) C[i][j] = (C[i-1][j-1] + C[i-1][j])%mod;
        }
    }
    int cal(int n,int k,int m){/// n个x k个y y-x个数<m的组合数
        if(k < m) return C[n + k][k];
        if(k >= m && k <= n + m - 1) return (C[n+k][k] - C[n + k][k - m] + mod)%mod;
        return 0;
    }
    int main(){
       init();
       int n , x , y,T;
       scanf("%d",&T);
       while(T--){
            scanf("%d%d%d",&n,&x,&y);
            int ans = 0;
            for(int cnt = y - x; cnt <= n - x;cnt++){ ///枚举第x对括号之间的括号对数
                int tmp_mid = cal(cnt,cnt,1);
                for(int cnt1 = 0;cnt1 <= x - 1;cnt1++){ ///枚举x左边的右括号数
                    int tmp_l = cal(x-1,cnt1,1);
                    int tmp_r = cal(n-x-cnt,n-cnt1-cnt-1,x-cnt1);
                    ans += 1LL * tmp_mid * tmp_l % mod * tmp_r % mod;
                    ans %= mod;
                }
            }
            printf("%d
    ",ans);
       }
       return 0;
    }
    

    E(待补)

    小明非常喜欢n串字符串,非常讨厌m串字符串。 他想获得一个字符串S,S包含了所有他喜欢的串,且不包含任何一个他讨厌的串。 小明懒得思考了,要你帮他写个程序求出这个串。

    Input
    每个文件只有一组数据。 第一行两个整数(n,m(1 leq n+m ≤15,n≥1,m≥0)) 接下来n行,每行一个字符串Ai,表示小明喜欢的 接下来m行,每行一个字符串Bi,表示小明讨厌的 ((sum|Ai|) + (sum|Bi|)≤300) 字符串中只有小写字母从a到j

    Output
    输出一个满足条件的最短字符串即可,字符串中只允许出现小写字母从a到j。 如果存在多组解,可以输出任意解。 如果不存在这样的字符串,输出"-"

    思路: 将字符串都插入到字典树中,建立AC自动机,这样可以预处理中从root到每个点结尾的串包含了哪些字符串,可以用状压表示。然后我们在Trie图上做最短路即可,dp[j][s]表示从root出发,Trie图上走到结点j,经过的字符串的集合为s的最短路径,由于边的权值为1,所以就变成了bfs,即某个状态一旦访问过,就不需要再访问了,这样复杂度就变成了$O(10 * (sum|Ai|) + (sum|Bi|) * 2^{n+m} ) approx 10^7 $,这是上限,实际上达不到这么大。

    #include<bits/stdc++.h>
    #include<ext/pb_ds/assoc_container.hpp>
    #define LL long long
    #define P pair<string,int>
    #define ls(i) seg[i].lc
    #define rs(i) seg[i].rc
    using namespace std;
    using namespace __gnu_pbds;
    const int SIZE = 10;
    const int MAXNODE = 310;
    const int MAXN = MAXNODE * (1<<15);
    int fa[MAXN],alp[MAXN],cur[MAXN],mask[MAXN],vis[MAXNODE][1<<15];
    int n , m,s1,s2;
    struct AC{
        int ch[MAXNODE][SIZE];
        int f[MAXNODE],last[MAXNODE],val[MAXNODE];
        int sz;
        void init(){
            sz = 1;
            memset(ch[0], 0, sizeof ch[0]);
            memset(vis,0,sizeof(vis));
        }
        int idx(char c){
            return c - 'a';
        }
        void _insert(char* s, int v){
            int u = 0,len = strlen(s);
            for(int i = 0;i < len;i++){
                int c = idx(s[i]);
                if(!ch[u][c]){
                    memset(ch[sz], 0, sizeof ch[sz]);
                    val[sz] = 0;
                    ch[u][c] = sz++;
                }
                u = ch[u][c];
            }
            val[u] |= (1<<v);
        }
        void getFail(){
            queue<int> q;
            f[0] = 0;
            for(int c = 0;c < SIZE;c++){
                int u = ch[0][c];
                if(u){
                    f[u] = 0;
                    q.push(u);
                    last[u] = 0;
                }
            }
            while(!q.empty()){
                int r = q.front();q.pop();
                for(int c = 0;c < SIZE;c++){
                    int u = ch[r][c];
                    if(!u){
                        ch[r][c] = ch[f[r]][c];
                        continue;
                    }
                    q.push(u);
                    int v = f[r];
                    while(v && !ch[v][c]) v = f[v];
                    f[u] = ch[v][c];
                    last[u] = val[f[u]]?f[u]:last[f[u]];
                    val[u] |= val[last[u]];
                }
            }
        }
        void Print(int u){
            if(fa[u] != -1){
                Print(fa[u]);
                printf("%c",alp[u] + 'a');
            }
        }
        bool bfs(){
            int tot, p, u, s, c;
            queue<int> q;
            fa[0] = -1,tot = cur[0] = mask[0] = 0;
            vis[0][0] = 1;
            q.push(tot++);
            while(!q.empty()){
                p = q.front();q.pop();
                u = cur[p], s = mask[p];
                if(s == s1){
                    Print(p);
                    return true;
                }
                for(int i = 0;i < SIZE;i++){
                    c = ch[u][i];
                    if(val[c] & s2) continue;///不能包含不喜欢子串
                    if(vis[c][s | val[c]]) continue;
                    vis[c][s | val[c]] = 1;
                    fa[tot] = p,mask[tot] = val[c] | s,alp[tot] = i,cur[tot] = c;
                    q.push(tot++);
                }
            }
            return false;
        }
    
    }ac;
    int main()
    {
        char text[310];
        while(scanf("%d%d",&n,&m)==2){
            ac.init();
            s1 = (1<<n) - 1,s2 = (1<<(n+m)) - (1<<n);
            for(int i = 0;i < n + m;i++) {
                scanf("%s",text);
                ac._insert(text,i);
            }
            ac.getFail();
            if(!ac.bfs()) printf("-");
            printf("
    ");
        }
        return 0;
    }
    

    F

    思路: $ dp[i][j] $表示前i个字符第i个选字母j的最小费用
    $ dp[i][j] = cost(i,j) + $ (min){(dp[i-1][k])}
    (用mi[i]表示)(min){(dp[i-1][k])} 降低复杂度 避免TLE

    G

    题意:将数字1-n按字典序排列,求字典序第k小,$ n <= 1e6 $
    思路:
    方法一:字典树查第k小+离线操作
    这种做法太骚了,直接把查询离线,从小到大插入到字典树中,顺便维护每个节点的size。那么我们能很容易的查找出第k大的路径,路径即对应了我们要查找的答案,最后再一次性输出答案即可。
    这种做法非常的无脑,但是缺点在创建字典树需要大量的内存,不过这题数据范围给的非常大。
    方法二:我在做这题的时候并没有想到离线,数据量太大,所以就没用字典树。实际完全可以模拟计算出每个点的size。
    假设当前在前缀为s的地方,我们计算出走到它的右边即前缀为s+1的地方需要cnt次
    若cnt <= k 那么 k -= cnt,s++
    否则 k -= cnt ,s *= 10 答案肯定在前缀为s的子树中
    重复上述两个步骤直到k = 0, s就是答案
    现在关键就是如何求cnt了, 把字典树画出来后,能够直观看出来其实就是每一层右边的减去左边的

    #include<bits/stdc++.h>
    #define LL long long
    #define P pair<int,int>
    using namespace std;
    
    const int N = 1e5 + 10;
    int cal(int n,int x){
        int y = x + 1, ans = 0;
        while(x <= n){
            ans += min(y,n + 1) - x;
            x *= 10, y *= 10;
        }
        return ans;
    }
    int findkth(int n,int k){
        int s = 1;
        k--;
        while(k){
                int cnt = cal(n, s);
                if(cnt <= k) k -= cnt, s++;
                else k--,s *= 10;
        }
        return s;
    }
    int main(){
    
        int T, n, k;
        cin>>T;
        while(T--){
            scanf("%d%d",&n,&k);
            printf("%d
    ",findkth(n,k));
        }
    }
    

    H

    题意:
    楼下的面包店畅销的面包都快卖完了,只剩下了两种买的人少的面包。 第一种面包卖W1元,能增加H1点能量 第二种面包卖W2元,能增加H2点能量 现在小明身上只有C元,两种面包都有无限多个
    那么小明最多能通过买面包增加多少点能量?
    C, H1, H2, W1, W2(1 ≤ C, H1, H2, W1, W2 ≤ (10^9))

    思路:这道题我yy了这么一种做法,就是尽快多取性价比高的,然后再慢慢分一些给另一种,然后答案取最优,然而WA。赛后我又想了想,我两种都尽可能的多取,然后再慢慢的分另一种,再设置一个上界$ 10 ^ 5 $,然后就A了。
    去看正解,刚好(10^5 > sqrt{C}),所以可以说歪打正着和正解差不多。

    #include<bits/stdc++.h>
    #define LL long long
    using namespace std;
    
    LL solve(int c,int h1,int w1,int h2,int w2){
        LL cnt = c / w1;
        int x = min(100000,(int)cnt);
        LL ans = 0;
        for(int i = 0;i <= x;i++){
            ans = max(ans, (cnt - i) * h1 + (c - (cnt - i) * w1) / w2 * h2);
        }
        return ans;
    }
    int main(){
    
        int c,h1,h2,w1,w2;
        while(scanf("%d%d%d%d%d",&c,&h1,&h2,&w1,&w2)!=EOF){
            if(h1 == h2){
                    if(w1 > w2) swap(w1, w2);
                    cout<<1LL * c / w1 * h1<<endl;
            }else{
                cout<<max(solve(c,h2,w2,h1,w1),solve(c,h1,w1,h2,w2))<<endl;
            }
        }
        return 0;
    }
    
    

    J

    题意:他有一条纸带,上面有n个数字,第i个数字为Ai。 他想把纸带选三个位置p1, p2, p3(p1 < p2 < p3)把纸带剪成4个每条长度至少为1的4条纸带。 分别为[1, p1-1], [p1+1, p2-1], [p2+1, p3-1], [p3+1, n],使得4个部分中数字之和相等。
    $ n <= 10^5,0<= A[i] <= 10 ^ 9$

    思路:本场最水的题了,特殊处理一下全为0的情况。
    $ sum[i] $ 表示1到i的前缀和, 拿map记录一下(sum[i])出现的最左的位置。然后只要枚举p1即可,再判断是否合法即可。

    #include<bits/stdc++.h>
    #define LL long long
    using namespace std;
    
    const int N = 1e5 + 10;
    LL sum[N];
    int a[N];
    map<LL,int> mp;
    int main(){
        int n;
        while(scanf("%d",&n)==1){
            sum[0] = 0,mp.clear();
            for(int i = 1;i <= n;i++){
                scanf("%d",a + i);
                sum[i] = sum[i-1] + a[i];
                if(!mp[sum[i]]) mp[sum[i]] = i;
            }
            if(sum[n] == 0){
                printf("2 4 6
    ");
                continue;
            }
            int flag = 0;
            for(int p1 = 2;p1 + 5 <= n;p1++){
                LL res = sum[p1-1];
                LL x1 = res + sum[p1];
                int p2 = mp[x1] + 1;
                if(p2 == 1 || p2 == p1) continue;
                x1 = sum[p2] + res;
                int p3 = mp[x1] + 1;
                if(p3 == 1 || p3 == p2) continue;
                if(sum[n] - sum[p3] != res) continue;
                printf("%d %d %d
    ",p1,p2,p3);
                flag = 1;
                break;
            }
            if(!flag) printf("-1
    ");
        }
    }
    
  • 相关阅读:
    CentOS7安装mysql-8
    zabbix监控规划及实施
    集群技术
    自动化脚本-配置LVS(DR模式)
    Pacemaker+ISCSI实现Apache高可用-配置
    创建集群corosync
    我的第一个python程序——猜数字
    质量报告
    新需求测试与回归测试
    冒烟测试
  • 原文地址:https://www.cnblogs.com/jiachinzhao/p/6942493.html
Copyright © 2011-2022 走看看