[CSP模拟测试43、44]题解
状态极差的两场。感觉现在自己的思维方式很是有问题。
(但愿今天考试开始的一刻我不会看到H I J)
A
考场上打了最短路+贪心,水了60。
然而正解其实比那30分贪心好想多了。
进行n次乘法后的结果一定可以化成$S\times b^n + m\times a$的形式,并且$m$是b的若干次幂(带系数)之和。
也就是说,$m=\frac{T-S\times b^n}{a}$可以写成$b$进制数,当然前提是$T-S \times 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$能代表的数,那方程里为什么要再$\times 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\n",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; }