zoukankan      html  css  js  c++  java
  • 8月18日考试 题解(动态规划+并查集+分块+提交答案题)

    考的比昨天好,至少做对了一道题。T3很多白给部分分没看,感觉巨亏。

    T1 蓝蓝的棋盘

    题目大意:给定一个长度为$n$的序列。两个人轮流移动棋子,棋子一开始在$0$。每次可以移动的范围为$[p+1,min (p+m,n)]$。两个人都按最优策略走。最优策略指自己的分减去对方的分最大。求先手的人的分数减去后手的人的分数。

    一开始没看懂样例。看明白以后大力DP,刚了两个小时也没做出来。发现正着DP有后效性,因为你现在的策略可能不是最优的,但可能影响到后面的策略。不如倒着DP。

    设$f[i]$表示从$i$出发到$n$的最大差值。假设另一个人的位置在$j$。两个人的差值是相反的,即在$j$的差值对于$i$的是$-f[j]$;因为从$j$转移过来,所以还要加上$a[j]$。所以有转移方程:$f[i]=maxlimits_{i<jleq min (n,i+m)} (f[i],a[j]-f[j])$

    发现是一个线性DP的式子,很容易想到用单调队列优化。然而我单调队列写挂了QAQ,于是用的线段树。常数大了一点。

    代码:

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    const int inf=1e18;
    int f[500005],n,m,a[500005];
    int maxx[500005*4];
    inline int read()
    {
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
        while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    inline void update(int index,int l,int r,int pos,int x)
    {
        if (l==r) {maxx[index]=x;return;}
        int mid=(l+r)>>1;
        if (pos<=mid) update(index*2,l,mid,pos,x);
        else update(index*2+1,mid+1,r,pos,x);
        maxx[index]=max(maxx[index*2],maxx[index*2+1]);
    }
    inline int query(int index,int l,int r,int ql,int qr)
    {
        if (ql<=l&&r<=qr) return maxx[index];
        int mid=(l+r)>>1,res=-inf;
        if (ql<=mid) res=max(res,query(index*2,l,mid,ql,qr));
        if (qr>mid) res=max(res,query(index*2+1,mid+1,r,ql,qr));
        return res;
    }
    signed main()
    {
        n=read();m=read();
        for (int i=1;i<=n;i++) a[i]=read();
        f[n]=0;f[n-1]=a[n];
        update(1,1,n,n,a[n]-f[n]);
        update(1,1,n,n-1,a[n-1]-f[n-1]);
        for (int i=n-2;i>=0;i--)
        {
            f[i]=query(1,1,n,i+1,min(i+m,n));
            update(1,1,n,i,a[i]-f[i]);
        }
        printf("%lld",f[0]);
        return 0;
    }

    T2 淘淘的集合

     给定$n$个集合$a[1],a[2],cdots ,a[n]$,初始值为$0$。有4种操作:

    1.合并$x,y$两个集合。如果$x,y$同属一个集合则忽略。

    2.将$x$所在集合内所有数加上$y$。

    3.$a[l]$到$a[r]$变为$0$。

    4.求$sumlimits_{i=l}^r a[i]$。

    保证事件4的$sum (r-l+1)leq 10^7$。

    暴力的$n^2$做法不难想到。暴力合并然后直接查询即可。上面的保证意味着我们可以暴力查询答案。

    正解则是并查集+分块。并查集维护的是集合的合并:每次合并集合时采用启发式合并,可以证明复杂度是$nlog n$的。对于2操作,我们对每个集合打一个tag,合并的时候就把tag加到原数中去,查询就是原数的值加上tag的值。对于3操作我们分块维护:给每个位置打上时间戳标记,这样就变成了区间加。进行4操作时现在的值减去打上时间戳标记时的值就是答案。

    对于1操作和2操作我们离线操作。时间复杂度$O(10^7+sqrt n)$。

    代码:

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    const int maxn=200005;
    int val[maxn],tag[maxn],fa[maxn];//set
    vector<int> v[maxn];
    int a[maxn],tim[maxn],block,tot;//block
    int n,m,op[maxn],opx[maxn],opy[maxn],ans[maxn];
    struct node
    {
        int id,t,x;
        bool operator < (node w) const{return t<w.t;}
    }tmp[maxn*5];vector<node> g[maxn];
    int cntq,lq;
    struct Node
    {
        int id,t,l,r;
        bool operator <(Node w) const{return t<w.t;}
    }q[maxn];//lq2=cntq
    inline int read()
    {
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
        while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    inline int getpos(int x){return (x-1)/block+1;}
    inline void build()
    {
        block=sqrt(n);tot=n/block;
        if (n%block) tot++;
        for (int i=1;i<=tot;i++) tim[i]=-1;
    }
    inline void rebuild(int id)
    {
        if (tim[id]==-1) return;
        for (int i=(id-1)*block+1;i<=min(id*block,n);i++) 
            a[i]=tim[id];
        tim[id]=-1;
    }
    inline void update(int l,int r,int v)
    {
        int id;
        if (getpos(l)==getpos(r))
        {
            id=getpos(l);rebuild(id);
            for (int i=l;i<=r;i++) a[i]=v;
            return;
        }
        rebuild(id=getpos(l));
        for (int i=l;i<=min(id*block,n);i++) a[i]=v;
        rebuild(id=getpos(r));
        for (int i=(id-1)*block+1;i<=r;i++) a[i]=v;
        for (int i=getpos(l)+1;i<id;i++) tim[i]=v;
    }
    inline int ask(int x){return tim[getpos(x)]==-1?a[x]:tim[getpos(x)];}
    inline int find(int x)
    {
        if (x==fa[x]) return x;
        return fa[x]=find(fa[x]);
    }
    inline void merge(int x,int y)
    {
        x=find(x),y=find(y);
        if (x==y) return;
        if (v[x].size()<v[y].size()) swap(x,y);fa[y]=x;
        for (int i=0;i<v[y].size();i++) val[v[y][i]]+=tag[y];tag[y]=0;
        for (int i=0;i<v[y].size();i++)
            val[v[y][i]]-=tag[x],v[x].push_back(v[y][i]);
        v[y].clear();
    }
    inline int query(int x){return val[x]+tag[find(x)];}
    inline void add(int x,int v){x=find(x);tag[x]+=v;}
    signed main()
    {
        n=read();m=read();
        build();
        for (int i=1;i<=n;i++) fa[i]=i,v[i].push_back(i);
        for (int i=1;i<=m;i++)
        {
            op[i]=read(),opx[i]=read(),opy[i]=read();
            if (op[i]==3) update(opx[i],opy[i],i);
            if (op[i]==4)
            {
                cntq++;
                q[cntq].id=cntq;q[cntq].t=i;
                q[cntq].l=opx[i];q[cntq].r=opy[i];
                for (int j=opx[i];j<=opy[i];j++)
                {
                    if (!ask(j)) continue;
                    lq++;
                    tmp[lq].id=cntq,tmp[lq].t=ask(j),tmp[lq].x=j;
                    g[tmp[lq].t].push_back(tmp[lq]);
                }
            }
        }
        sort(q+1,q+cntq+1);
        int now=1;
        for (int i=1;i<=m;i++)
        {
            if (op[i]==1) merge(opx[i],opy[i]);
            if (op[i]==2) add(opx[i],opy[i]);
            for (int j=0;j<g[i].size();j++) 
                ans[g[i][j].id]-=query(g[i][j].x);
            while(now<=cntq&&q[now].t<=i)
            {
                for (int j=q[now].l;j<=q[now].r;j++)
                    ans[q[now].id]+=query(j);
                now++;
            }
        }
        for (int i=1;i<=cntq;i++) printf("%lld
    ",ans[i]);
        return 0;
    }

    T3 蓝蓝的程序

    提交答案的SB题。

    Subtask1:给定一个长度为$n$的序列。求$(sumlimits_{i=1}^n a[i])+max limits_{1leq ileq n}(a[i])+min limits_{1leq ileq n}(a[i])$

    硬搞即可。

    Subtask2:给定一个矩阵。求二维前缀和之和。

    考虑每个元素的贡献。类比二维树状数组,每个元素能产生$(n-i+1)*(n-j+1)$次贡献。$n^2$硬搞即可。

    Subtask3:给定一个矩阵,求二维前缀和的前缀和之和。

    还是考虑每个元素的贡献。设$f(n)=frac{n(n+1)}{2}$,则每个元素能产生$f(n-i+1)*f(n-j+1)$次贡献。

    Subtask4:直接输出$n$。很显然。

    Subtask5:求$sumlimits_{i=1}^n i$

    高中数学必修五

    Subtask6:求$sumlimits_{i=1}^n i^2$

    之前背过通项公式:$frac{n(n+1)(2n+1)}{6}$

    Subtask7:求$sumlimits_{i=1}^n i^3$

    $frac{n^2(n+1)^2}{4}$

    Subtask8:求$sumlimits_{i=1}^n i^4$

    通项公式:$frac{n^8-1}{15}$。当然也可以拉格朗日插值做(我不会QAQ

    Subtask9:给定一个$01$矩阵。问不含$0$的子矩阵个数。

    单调栈。

    Subtask10:给定一个$01$矩阵,问不含$0$的子矩阵的面积之和。

    单调栈。上述两个子任务我都不会QAQ

    上述子任务有70分是可做的,考试的时候没看这个题太TM亏了。

  • 相关阅读:
    logstash multiple piplines 配置方式
    filter-mutate过滤插件
    redis主从复制
    redis sentinel(哨兵)
    mongodb replica-set
    Linux入门篇(五)——Shell(一)
    Linux入门篇(四)——Vim的使用与Bash
    Linux入门篇(三)——文件与目录
    Linux入门篇(二)——文件
    Linux入门篇(一)——基本命令
  • 原文地址:https://www.cnblogs.com/Invictus-Ocean/p/13523935.html
Copyright © 2011-2022 走看看