• 2017 ACM/ICPC Asia Regional Shenyang Online 部分刷题记录


    cable cable cable

    • 题意: M个灯,K个盒子,求最少要连多少条线,使任选K个盒子每个灯都能装下
    • 思路: 每个灯要连(M-K+1)个 总共M*(M-K+1)

    happy happy happy

    • 题意: 左右取数,孩子每次都去左右两边最大的那个,父亲想让孩子赢(大于父亲)且最小化分差
    • 思路: 限时搜索,先dp预处理出l,r区间最大分差和最小分差,然后(2^n)搜索,但每进入一层都要利用dp数组更新一次答案
    #include<bits/stdc++.h>
    #define pii pair<int,int>
    using namespace std;
    const int inf = 0x3f3f3f3f;
    const int N = 1e2+10;
    
    
    
    int n,a[N],mx[N][N],mn[N][N],ans;
    int st,lim = 1000;
    void init(){
        memset(mx,-0x3f,sizeof mx);
        memset(mn,0x3f,sizeof mn);
        for(int i=1;i<=n+1;++i) mx[i][i-1] = mn[i][i-1] = 0;
        for(int l=n;l>0;--l){
            for(int r=l;r<=n;++r){
                int ll = l,rr = r,son;
                if(a[ll]>=a[rr]) son = a[ll++]; // 儿子取走左边的
                else son = a[rr--];             // 取走右边的
                mx[l][r] = max(mx[l][r],mx[ll][rr-1]+a[rr]-son); // 取左边
                mx[l][r] = max(mx[l][r],mx[ll+1][rr]+a[ll]-son); // 取右边
                mn[l][r] = min(mn[l][r],mn[ll][rr-1]+a[rr]-son);
                mn[l][r] = min(mn[l][r],mn[ll+1][rr]+a[ll]-son);
            } 
        }
    }
    void dfs(int l,int r,int dif){
        if(l>r){    // 搜索到终点
            if(dif<0) ans = max(ans,dif); // dif为当前爸爸比儿子少的
            return ;
        }
        if(mn[l][r]+dif>=0 || mx[l][r]+dif<=ans)    return ; // 可行性剪枝与最优化剪枝
        if(mx[l][r]+dif<0){
            ans = max(ans,mx[l][r]+dif);    
            return ;
        }
        if(clock()-st>lim)  return ;    // 超时停止搜索
        int son;
        if(a[l]>=a[r])  son = a[l++];   // 儿子优先拿大的
        else son = a[r--];
        dfs(l+1,r,dif+a[l]-son);    // 取左边
        dfs(l,r-1,dif+a[r]-son);   // 取右边
    }
    
    int main(){
        while(scanf("%d",&n)==1){
            memset(a,0,sizeof a);
            for(int i=1;i<=n;++i){
                scanf("%d",&a[i]);
            }
            init();
            st = clock();
            ans = -inf; // 爸爸比儿子少拿的 最后要取反
            dfs(1,n,0);
            if(ans == -inf)    puts("The child will be unhappy...");
            else    printf("%d
    ",-ans);
        }
        
        return 0;
    }
    

    array array array

    • 题意: 一个序列,每次可以选最多k个数抹去,问能否变成一个非递增或非递减的序列
    • 思路: dp求最长非递增l1,非递减序列的长度l2,max(l1,l2)+k>=n 就可以

    number number number

    • 题意: 求不能被k个斐波那契数组合出(可重复)的最小整数.
    • 思路: 找规律可得为第k*2+3个斐波那契数,矩阵递推.
    #include<bits/stdc++.h>
    #define ll long long
    #define pii pair<int,int>
    using namespace std;
    const int inf = 0x3f3f3f3f;
    const int MOD = 998244353;
    
    struct mat{
        ll a[2][2];
        mat operator *(mat b){
            mat res; res.a[0][0] = res.a[0][1] = res.a[1][0] = res.a[1][1] = 0;
            for(int i=0;i<2;++i){
                for(int k=0;k<2;++k){
                    for(int j=0;j<2;++j){
                        res.a[i][j] =  (a[i][k] * b.a[k][j] %MOD + res.a[i][j])%MOD; 
                    }
                }
            }
            return res;
        }
        void init(){
            a[0][0] = a[0][1] = a[1][0] = 1; a[1][1] = 0;
        }
    };
    mat qpow(mat c,ll b){
        mat res;    res.a[0][0] = res.a[1][1] = 1; res.a[1][0] = res.a[0][1] = 0;
        while(b){
            if(b%2) res = res*c;
            b>>=1;
            c = c*c;
        }
        return res;
    }
    int main(){
        ll k;
        while(scanf("%lld",&k)!=EOF){
            mat res;    res.init();
            res = qpow(res,k*2+3);
            printf("%lld
    ",(res.a[1][0]-1+MOD)%MOD);
        }
        return 0;
    }
    

    gems gems gems

    • 题意: n堆石子从左往右拿,每次可以拿k或k+1堆(k为上一次拿的),A想最大化差异,B想最小化差异,问最后的差异是多少.
    • 思路: 可以直接当做每个人都想最大化自己取到的石子,dp[i][j]表示还剩i堆石子,上一个人取了j堆时先手比后手多拿了多少石子
      (dp[i][j] = max(sum[i] - sum[i-j] - dp[i-j][j],sum[i] - sum[i-j-1] - dp[i-j-1][j+1](i>j)))
      因为n为2e4,每次拿k+1,k最大也只能取到sqrt(n).
    #include<bits/stdc++.h>
    using namespace std;
    #define lson(p) (p<<1)
    #define rson(p) (p<<1|1)
    #define ll long long
    const int N = 2e4+10;
    int n,sum[N],v[N],f[N][202];
    void gmax(int &a,int b){
        if(b>a) a = b;
    }
    int main(){
        int t; scanf("%d",&t);
        while(t--){
            scanf("%d",&n);
            for(int i=n;i>=1;--i)// 反向dp,从后往前拿,所以输入要倒序
                scanf("%d",&v[i]);
            for(int i=1;i<=n;++i){
                sum[i] = sum[i-1] + v[i];
            }
            int m = sqrt(n*2);
            while((1+m)*m >n*2) --m;
            for(int i=1;i<=n;++i){  // 还剩多少堆
                int top = min(i,m);
                for(int j=1;j<=top;++j){    // 当前这轮拿j堆
                    f[i][j] = sum[i] - sum[i-j] - f[i-j][j];    // 下次拿j堆
                    if(i>j) gmax(f[i][j],sum[i] - sum[i-j-1] - f[i-j-1][j+1]);  // 下次拿j+1堆
                }
            }
            printf("%d
    ",f[n][1]); // 反向dp f[n][1] 为答案
        }
        return 0;
    }
    

    思路来源

    transaction transaction transaction

    • 题意: 一棵树,可以选择一个点出发,走到终点获得(v[tp]-v[sp] - w),终点价值-起点价值-路上花费,求最大收益
    • 思路: 树形dp,0代表到这个点的最小花费,1代表卖出去的收益.(其实没必要那么麻烦,自己写挫了)
    #include<bits/stdc++.h>
    #define pii pair<int,int>
    using namespace std;
    const int inf = 0x3f3f3f3f;
    const int N = 1e5+10;
    
    struct node{
        int u,v,w,nxt;
    }e[N*2];
    int head[N],tot;
    int dp[N][2],a[N],n;    // 0 买 1 卖
    void add(int u,int v,int w){
        e[tot] = (node){u,v,w,head[u]};     head[u] = tot++;
        e[tot] = (node){v,u,w,head[v]};     head[v] = tot++;
    }
    void init(){
        tot = 0; memset(head,-1,sizeof head);
        memset(dp,0,sizeof dp);
    }
    void dfs(int u,int fa){
        for(int i=head[u];~i;i=e[i].nxt){
            if(e[i].v == fa)    continue;
            dp[e[i].v][0] = max(-a[e[i].v],dp[u][0]-e[i].w); // 儿子买 
            dp[e[i].v][1] = a[e[i].v] + dp[u][0] - e[i].w;   // 儿子卖给父亲
            dfs(e[i].v,u);
            dp[u][1] = max(dp[e[i].v][0]+a[u] -e[i].w,dp[u][1]);
            dp[u][0] = max(dp[e[i].v][0]-e[i].w,dp[u][0]);
        }
    }
    int main(){
        int t; scanf("%d",&t);
        while(t--){
            scanf("%d",&n);
            for(int i=1;i<=n;++i)   scanf("%d",&a[i]);
            init();
            int u,v,w;
            for(int i=1;i<n;++i){
                scanf("%d%d%d",&u,&v,&w);
                add(u,v,w);
            }
            dp[1][0] = -a[1];
            dfs(1,0);
            int ans = 0;
            for(int i=1;i<=n;++i){
                ans = max(ans,dp[i][1]);
            }
            printf("%d
    ",ans);
        }
    }
    

    ping ping ping

    • 题意: 一颗树,其中有些点是走不通的,给出Q个点对表示两点之间走不通,问最少有几个点是走不通的
    • 思路: 贪心,每个点对,都让他们的lca走不通(lca可以控制最多的点),且先从lca较深的开始(较深的可以切断向较浅去的路径),用树剖求出dfs序及维护lca,L[u]R[u]表示这个点掌控的范围(不在lr区间内的点要到l~r必走u),用BIT维护区间修改,单点查询,每次割点就是L[u]+1,R[u]-1,而只要query(u)!=0则说明这个点已经有祖先被割掉了(因为是按lca深度由深到浅,不会出现先割祖先,再割后代,所以被割过就肯定走不通了);
    #include<bits/stdc++.h>
    using namespace std;
    #define lson(p) (p<<1)
    #define rson(p) (p<<1|1)
    #define ll long long
    const int N = 5e5+10;
    
    
    struct Edge{
        int u,v,w,nxt;
    }e[N*2];
    
    int head[N],tot;
    int top[N],fa[N],deep[N],num[N],L[N],R[N],son[N]; // 重儿子
    int pos;
    
    void addedge(int u,int v,int w=0){e[tot] = (Edge){u,v,w,head[u]}; head[u] = tot++;}
    
    struct node{
        int u,v,la;
        bool operator <(node b){
            return deep[la] > deep[b.la];
        }
    };
    
    vector<node> ve;
     //第一遍dfs   处理fa,num,deep,son
    void dfs1(int u,int pre,int d){
        deep[u] = d;
        fa[u] = pre;
        num[u] = 1;
        for(int i=head[u];~i;i=e[i].nxt){
            int v = e[i].v;
            if(v!=pre){
                dfs1(v,u,d+1);
                num[u] += num[v];
                if(son[u] == -1 || num[v] > num[son[u]])
                    son[u] = v;
            }
        }
    }
    // 第二遍dfs  处理 top,p,fp
    void dfs2(int u,int sp){
        top[u] = sp;        
        L[u] = ++pos;
        if(son[u]== -1){
            R[u] = pos ;
            return ;
        }
        dfs2(son[u],sp);    // 当前链继续走重儿子
        for(int i=head[u];i!=-1;i=e[i].nxt){
            int v = e[i].v;
            if( v!= son[u] && v!=fa[u])
                dfs2(v,v);  // 以自己为链首的新链
        }
        R[u] = pos ;
    }
    
    int n,m; 
    struct BIT{
        int a[N];
        void init(){
            memset(a,0,sizeof a);
        }
        int lowbit(int x){
            return x&(-x);
        }
        void update(int x,int pos){
            for(int i=pos;i<=n;i+=lowbit(i))    a[i] += x;
        }
        int query(int pos){
            int res = 0;
            for(int i=pos;i;i-=lowbit(i))   res += a[i];
            return res;
        }
    }bt;
    int lca(int x,int y){
        while(top[x]!=top[y]){
            if(deep[top[x]] > deep[top[y]]) x = fa[top[x]];
            else y = fa[top[y]];
        }
        return deep[x] < deep[y] ? x:y;
    }
    
    
    void init(){
        memset(head,-1,sizeof(head));memset(son,-1,sizeof(son));
        bt.init();ve.clear();tot = 0;pos = 0;
    }
    int main(){
        while(scanf("%d",&n)==1){
            int u,v,ans=0;
            init();
            for(int i=1;i<=n;++i){
                scanf("%d%d",&u,&v);
                addedge(u,v);
                addedge(v,u);
            }
            dfs1(0,0,0); // 题目默认0为根节点
            dfs2(0,0);
    
            scanf("%d",&m);
            for(int i=1;i<=m;++i){
                scanf("%d%d",&u,&v);
                ve.push_back({u,v,lca(u,v)});
            }
            sort(ve.begin(),ve.end());
            for(auto item:ve){
                u = item.u, v = item.v;
                if(bt.query(L[u]) || bt.query(L[v]))    continue; // 已经被割掉的点覆盖
                bt.update(1,L[item.la]) ; bt.update(-1,R[item.la]+1); // 割去他们的lca
                ans++;
            }
            printf("%d
    ",ans);
        }
        return 0;
    }
    

    card card card

    • 题意: 两个序列a,b((sum{a}=sum{b})),每次向后走一步,sum+a[i]-b[i],能将最前面的一堆放到最后面,问要移多少堆才能拿到最后一堆
    • 思路: 相对位置是不变的,直接求a[i]-b[i]的前缀和,最小即为答案
    #include<bits/stdc++.h>
    #define pii pair<int,int>
    using namespace std;
    const int inf = 0x3f3f3f3f;
    const int N = 1e6+10;
    
    int a[N],dp[N];
    int n;
    int main(){
        while(scanf("%d",&n)==1){
            int val;
            for(int i=1;i<=n;++i){
                scanf("%d",&a[i]);
            }
            for(int i=1;i<=n;++i){
                scanf("%d",&val);   a[i]-=val;
            }
            int ans = 0,pos=0;
            for(int i=1;i<=n;++i){
                dp[i] = dp[i-1] + a[i];
                if(dp[i]<ans){// 前缀和为负数是移不到最后的,要把最小的那个对应的堆数移到后面,则剩下的肯定全是正的
                    ans = dp[i];
                    pos = i;
                }
            }
            printf("%d
    ",pos);
        }
    }
    
  • 相关阅读:
    [leedcode 77] Combinations
    [leedcode 76] Minimum Window Substring
    [leedcode 75] Sort Colors
    [leedcode 74] Search a 2D Matrix
    [leedcode 73] Set Matrix Zeroes
    [leedcode 71] Simplify Path
    [leedcode 70] Climbing Stairs
    [leedcode 69] Sqrt(x)
    [leedcode 67] Add Binary
    HDU1027-Ignatius and the Princess II(全排列问题next_permutation函数的使用)
  • 原文地址:https://www.cnblogs.com/xxrlz/p/11688526.html
Copyright © 2020-2023  润新知