[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;
}
原文地址:https://www.cnblogs.com/Rorschach-XR/p/11535610.html