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
    ");
        }
    }
    
  • 相关阅读:
    request.getParameter() 、 request.getInputStream()和request.getReader() 使用体会
    HTTP之Content-Length
    关于spring3中No Session found for current thread!and Transaction的配置和管理(转)
    Java数据类型和MySql数据类型对应一览
    Spring MVC 解读——View,ViewResolver(转)
    LeetCode 441. Arranging Coins
    LeetCode 415. Add Strings
    LeetCode 400. Nth Digit
    LeetCode 367. Valid Perfect Square
    LeetCode 326. Power of Three
  • 原文地址:https://www.cnblogs.com/jiachinzhao/p/6942493.html
Copyright © 2011-2022 走看看