• [CSP模拟测试43、44]题解


    状态极差的两场。感觉现在自己的思维方式很是有问题。

    (但愿今天考试开始的一刻我不会看到H I J)

    A

    考场上打了最短路+贪心,水了60。

    然而正解其实比那30分贪心好想多了。

    进行n次乘法后的结果一定可以化成$S imes b^n + m imes a$的形式,并且$m$是b的若干次幂(带系数)之和。

    也就是说,$m=frac{T-S imes b^n}{a}$可以写成$b$进制数,当然前提是$T-S imes b^n mod a=0$。

    那么这个b进制数的系数之和其实就是加法操作的次数,这个很好理解。

    枚举乘法次数,然后得到相应的$m$后直接$b$进制拆解,注意要从高次开始。

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    using namespace std;
    typedef long long ll;
    ll S,T,a,b,ans=1e15;
    int main()
    {
        scanf("%lld%lld%lld%lld",&S,&T,&a,&b);
        for(ll n=0,now=1;S*now<=T;n++,now*=b)
        {
            ll m=T-S*now,res=0;
            if(m%a)continue;
            m/=a;
            ll c=now;
            while(m)res+=m/c,m%=c,c/=b;
            res+=n;
            ans=min(ans,res);
        }
        cout<<ans<<endl;
        return 0;
    }
    

    B.

    首先可以想到一个比较暴力的dp:$f[i][j]$表示前$i$个变量乘积为$j$的方案数,枚举上一个结果和当前变量的值$O(p^2)$转移即可。

    正解只理解了思想,但转移方程仍然不是很懂。

    打表很容易发现一个性质:若$gcd(a,P)=gcd(b,P)$,那么$f[i][a]=f[i][b]$。

    显然出题人居心叵测,把两个状态转移方程的条件写反了23333。

    不过话说$varphi(frac{P}{a})$的含义是$a$能代表的数,那方程里为什么要再$ imes a$呢?辣鸡博主不是很明白。

    C.

    设特殊加速器的使用次数为$x$,总费用为$y$,那么$y$关于$x$的函数显然是单谷的,所以可以三分。

    考虑如何在已知$x$的情况下快速求出费用。可以对每个点预处理包含它的区间的最右端点,这个直接开个数组对每个$l_i$标记一下就行。

    每次计算的时候用差分的思想实现区间减法,不过直接暴力循环似乎也可过??

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    using namespace std;
    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;
    }
    const int N=1e5+5;
    typedef long long ll;
    int n,m,t,a[N],s[N],L[N],R[N],rm[N],maxh,p[N],v[N];
    ll ans=1e15;
    ll cacl(int x)
    {
        for(int i=1;i<=n;i++)
            p[i]=max(0,a[i]-x);
        ll res=1LL*t*x;
        int yet=0;
        for(int i=1;i<=n;i++)
        {
            yet-=v[i];v[i]=0;
            p[i]=max(0,p[i]-yet);
            res+=p[i];
            yet+=p[i];
            v[rm[i]+1]+=p[i];
        }
        return res;
    }
    
    int main()
    {
        //freopen("9.in","r",stdin);
        n=read();m=read();t=read();
        for(int i=1;i<=n;i++)
            a[i]=read(),maxh=max(maxh,a[i]);
        for(int i=1;i<=m;i++)
        {
            L[i]=read();R[i]=read();
            s[L[i]]=max(s[L[i]],R[i]);
        }
        int now=0;
        for(int i=1;i<=n;i++)
        {
            if(s[i])now=max(now,s[i]);
            rm[i]=now;
            if(now==i)now=0;
        }
        int l=0,r=maxh;
        for(int i=1;i<=n;i++)
            if(!rm[i])l=max(l,a[i]);
        //cout<<l<<endl;
        while(l<=r)
        {
            int mid1=l+(r-l)/3,mid2=l+(r-l)*2/3;
            ll val1=cacl(mid1),val2=cacl(mid2);
            if(val1>=val2)l=mid1+1,ans=min(ans,val2);
            else r=mid2-1,ans=min(ans,val1);
    
        }
        cout<<ans<<endl;
        return 0;
    }
    

    D.

    暴力的基础上加个剪枝就可以。考虑极限情况,如果当前的$gcd$乘上$(n-i+1)$都没法更新ans($i$为枚举的左端点),那么直接break即可。

    实测卡不掉。zkt巨巨证了复杂度,可以做到$O(n log n)$。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    int n,a[100005],st[100005][21],lg[100005]={-1};
    ll ans;
    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;
    }
    
    int gcd(int x,int y)
    {
        if(!y)return x;
        return gcd(y,x%y);
    }
    int main()
    {
        n=read();
        for(int i=1;i<=n;i++)
            a[i]=read();
        //ini();
        for(int i=1;i<=n;i++)
        {
            int now=a[i];
            for(int j=i;j<=n;j++)
            {
                now=gcd(now,a[j]);
                if(now==1){ans=max(ans,1LL*(n-i+1));break;}
                if(1LL*now*(n-i+1)<ans)break;
                ans=max(ans,1LL*(j-i+1)*now);
            }
    
        }
    
        cout<<ans<<endl;
        return 0;
    }
    

    E.

    贪心搜索乱搞水过了。严谨的贪心抽时间补。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N=1e5+5;
    int n;
    ll a[N][3],ans=1e15;
    double tim;
    void dfs(int x,ll rmax,ll rmin,ll bmax,ll bmin)
    {
        if(x>1&&(rmax-rmin)*(bmax-bmin)>ans)return ;
        if(x>n)
        {
            ans=min(ans,(rmax-rmin)*(bmax-bmin));
            if((clock()-tim)/1e6>=1.5)printf("%lld
    ",ans),exit(0);
            return ;
        }
        dfs(x+1,max(rmax,a[x][0]),min(rmin,a[x][0]),max(bmax,a[x][1]),min(bmin,a[x][1]));
        dfs(x+1,max(rmax,a[x][1]),min(rmin,a[x][1]),max(bmax,a[x][0]),min(bmin,a[x][0]));
        return ;
    }
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            for(int j=0;j<=1;j++)
                scanf("%lld",&a[i][j]);
            if(a[i][0]<a[i][1])swap(a[i][0],a[i][1]);
        }
        tim=clock();
        dfs(1,0,1e15,0,1e15);
        cout<<ans<<endl;
        return 0;
    }
    

    F.

    线段树优化dp。

    状态定义有些不好想:$f[i][j]$表示进行到第$i$次操作,一个指针在$pos[i]$(题目里给的),另一个在$j$时的最小费用。

    $f[i][j]=min (f[i-1][j]+|p_i-p_{i-1}|)$

    $f[i][p[i-1]]=min (f[i-1][j]+|p_i-j|)$

    暴力转移是$O(n^2)$的。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    int read()
    {
        int x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
        return x*f;
    }
    const int N=1e5+5;
    int n,q,p1,p2;
    int pos[N];
    ll ans=1e15,dp[2005][2005];
    int abss(int x)
    {
        return x>0?x:-x;
    }
    int main()
    {
        n=read();q=read();p1=read();p2=read();
        for(int i=1;i<=q;i++)pos[i]=read();
        memset(dp,0x3f,sizeof(dp));
        dp[1][p1]=abss(pos[1]-p2),dp[1][p2]=abss(pos[1]-p1);
        for(int i=2;i<=q;i++)
        {
            for(int j=1;j<=n;j++)
                dp[i][pos[i-1]]=min(dp[i-1][j]+1LL*abss(pos[i]-j),dp[i][pos[i-1]]),
                dp[i][j]=min(dp[i-1][j]+1LL*abss(pos[i]-pos[i-1]),dp[i][j]);
    
        }
    
        for(int i=1;i<=n;i++)
            ans=min(ans,dp[q][i]);
        cout<<ans<<endl;
        return 0;
    }
    

    我们注意到转移1可以用线段树区间加实现,对于转移2先拆绝对值,维护$f[i][j]+j$和$f[i][j]-j$的最小值即可。

    用到的操作:区间修改,区间查询,单点修改。

    注意单点修改要放在区间加后面啊!

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1e5+5;
    typedef long long ll;
    const ll inf=0x3f3f3f3f3f3f3f3f;
    int read()
    {
        int x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
        return x*f;
    }
    int n,q,p1,p2;
    int pos[N];
    ll dp[N];
    int abss(int x)
    {
        return x>0?x:-x;
    }
    #define ls(k) (k)<<1
    #define rs(k) (k)<<1|1
    ll a[N<<2],b[N<<2],lz[N<<2];
    void up(int k)
    {
        a[k]=min(a[ls(k)],a[rs(k)]);
        b[k]=min(b[ls(k)],b[rs(k)]);
    }
    void down(int k,int l,int r)
    {
        int mid=l+r>>1;
        lz[ls(k)]+=lz[k];
        a[ls(k)]+=lz[k];b[ls(k)]+=lz[k];
        lz[rs(k)]+=lz[k];
        a[rs(k)]+=lz[k];b[rs(k)]+=lz[k];
        lz[k]=0;
    }
    void build(int k,int l,int r)
    {
        if(l==r)
        {
            if(l==p1)a[k]=abss(pos[1]-p2)+p1,b[k]=abss(pos[1]-p2)-p1;
            else if(l==p2)a[k]=abss(pos[1]-p1)+p2,b[k]=abss(pos[1]-p1)-p2;
            else a[k]=b[k]=inf;
            return ;
        }
        int mid=l+r>>1;
        build(ls(k),l,mid);
        build(rs(k),mid+1,r);
        up(k);
    }
    void add(int k,int l,int r,int L,int R,ll val)
    {
        if(l>r)return ;
        if(L<=l&&R>=r)
        {
            a[k]+=val;b[k]+=val;
            lz[k]+=val;
            return ;
        }
        if(lz[k])down(k,l,r);
        int mid=l+r>>1;
        if(L<=mid)add(ls(k),l,mid,L,R,val);
        if(R<mid)add(rs(k),mid+1,r,L,R,val);
        up(k);
    }
    void update(int k,int l,int r,int pos,ll val,int op)
    {
        if(l==r)
        {
            if(!op)a[k]=min(a[k],val);
            else b[k]=min(b[k],val);
            return ;
        }
        if(lz[k])down(k,l,r);
        int mid=l+r>>1;
        if(pos<=mid)update(ls(k),l,mid,pos,val,op);
        else update(rs(k),mid+1,r,pos,val,op);
        up(k);
    }
    
    ll qmin(int k,int l,int r,int L,int R,int op)
    {
        if(L<=l&&R>=r)return op?b[k]:a[k];
        if(lz[k])down(k,l,r);
        int mid=l+r>>1;
        ll res=inf;
        if(L<=mid)res=min(res,qmin(ls(k),l,mid,L,R,op));
        if(R>mid)res=min(res,qmin(rs(k),mid+1,r,L,R,op));
        return res;
    }
    ll getans(int k,int l,int r)
    {
        if(l==r)return a[k]-l;
        int mid=l+r>>1;
        if(lz[k])down(k,l,r);
        return min(getans(ls(k),l,mid),getans(rs(k),mid+1,r));
    }
    
    int main()
    {
        n=read();q=read();p1=read();p2=read();
        for(int i=1;i<=q;i++)pos[i]=read();
        build(1,1,n);
        for(int i=2;i<=q;i++)
        {
            ll val1=qmin(1,1,n,pos[i],n,0)-pos[i],val2=qmin(1,1,n,1,pos[i],1)+pos[i];
            add(1,1,n,1,n,abss(pos[i]-pos[i-1]));
    
            update(1,1,n,pos[i-1],min(val1,val2)+pos[i-1],0);
            update(1,1,n,pos[i-1],min(val1,val2)-pos[i-1],1);
        }
        cout<<getans(1,1,n)<<endl;
        return 0;
    }
    
  • 相关阅读:
    推荐一款作图工具
    web应用中幂等性的学习
    读书笔记:重构原则
    /usr/bin/ld: cannot find -lc错误原因及解决方法
    ar命令学习
    Linux下C语言编程中库的使用
    idea实战技巧
    intelj idea中除了Find Usage外的另一种查找级联调用的方法
    jenkins构建,拉取不到最新版本代码,报clock of the subversion server appears to be out of sync
    服务器出现大量close_wait,我们来说说到底是怎么回事?(以tomcat为例)
  • 原文地址:https://www.cnblogs.com/Rorschach-XR/p/11535610.html
Copyright © 2020-2023  润新知