220403 考试爆炸记 (T1T2)
T1
最大约数和
先放一下我考时的代码。(没想到能A过洛谷的数据,可能是洛谷的数据有点弱)
#include<bits/stdc++.h> using namespace std; int n,ans,R; struct node{ double v; int num,sum; }a[1010]; //a[i] 编号和价值 bool cmp(node a,node b){ return a.v>b.v; if(a.v==b.v) a.sum>b.sum; } int main() { for(int i=1;i<=1000;i++){ a[i].num=i; a[i].v=0; } a[1].v=0,a[2].v=0.5,a[3].v=1.0/3,a[4].v=0.75; a[5].v=0.2,a[6].v=1,a[7].v=1.0/7,a[8].v=7.0/8; a[9].v=4.0/9,a[10].v=0.8; a[1].sum=0,a[2].sum=1,a[3].sum=1,a[4].sum=3; a[5].sum=1,a[6].sum=6,a[7].sum=1,a[8].sum=7; a[9].sum=4,a[10].sum=8; for(int i=11;i<=1000;i++){ ans=0; int k=sqrt(i); for(int j=1;j<=k;j++){ if(i%j==0){//i能被j整除 /*if(j!=(i/j) && j!=i && ((i/j)!=i)) ans+=j;ans+=(i/j); a[i].sum+=(j+(i/j));*/ if(j==(i/j)){ ans+=j;a[i].sum+=j; } else if(j==1){ ans++,a[i].sum++; } else{ ans+=(j+(i/j)); a[i].sum+=(j+(i/j)); } } } double ans1=ans; a[i].v=(ans1/i); } sort(a+1,a+1001,cmp); R=1,ans=0; scanf("%d",&n); while(n>0 && R<=1000){ if(n>=a[R].num){ n-=a[R].num; ans+=a[R].sum; //cout<<a[R].num<<" "<<a[R].v<<" "<<a[R].sum<<endl; } R++; } cout<<ans; return 0; }
模拟了一下整个过程,要选数,那我们肯定要选对答案贡献最大的数,我用i这个数的因数和除以i本身,表示他的贡献率。求完所有数后,按这个贡献率降序排序,对于n这个数,求解就行了。
那么接下来我们来看一下正解吧:
考时我竟然没有想到0|1背包的做法。。。。。。。。。
#include<bits/stdc++.h> using namespace std; int m,s,f[1005][1005]; int ysh[1005]; void makesum(int m){//预处理1...m每个数的因数和 ysh[1]=0;//1的因数和除他自身是0 for(int i=2;i<=m;i++){ s=1; for(int j=2;j<=i/2;j++)//从2开始 if(i%j==0) s+=j; ysh[i]=s;//得到因数和 } } int main(){ cin>>m; makesum(m); memset(f,0,sizeof(f)); //0|1背包 for(int i=1;i<=m;i++)//相当于枚举每个物品 for(int x=1;x<=m;x++)//x是容量 if(x>=i) f[i][x]=max(f[i-1][x-i]+ysh[i],f[i-1][x]);//i可以选择放 else f[i][x]=f[i-1][x];//i不能选 cout<<f[m][m]; }
T2
最佳序列
这道题数据有点弱啊,打考时的这个暴力就可以拿60pts了。复杂度O(n*n);
正解:二分+单调队列
首先看这道题,求平均值的最大值没有显眼的规律,所以我们二分求这个平均值。
那么这道题转变为一个判定性问题,用单调队列可将这个判定优化到O(n),总复杂度nlogn。
假设我们现在枚举的值为x, 那么我们对于原来的a数组求一个前缀和数组sum,那么我们就是要找到满足sum[r] - sum[l] >= x * (r - l) 并且L <= r - l <= R,我们将上面的式子移项,(sum[r] - r * x) - (sum[l] - l * x)>= 0那么我们可以通过将a[i] à a[i] - x来消掉sum[r] - r * x为sum[r],那么我们现在将a[i]全部减去x,重新求sum数组,那么我们现在要做的是求取满足sum[r] - sum[l] >= 0并且L <= r - l <= R的r和l,那么我们枚举右端点r,那么可行的l为(r - R ~ r - L),我们只需要判断这个区间中的sum的最小值是否比sum[r]小即可,那么我们相当于要求一段长度固定的区间中的最小值,并且需要移动区间加入下一个位置或者去掉上一个位置,这个不就是标准的单调队列了吗,直接扫一遍就可以判断了,所以算法就是二分枚举x,判断是否可行。
问题变为每个数ai选择之前[i−R,i−L][i−R,i−L]的一个数aj,使得ai≥aj,这是一个经典的单调队列优化DP。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=200010; 4 int n,L,R; 5 int a[maxn],Max; 6 double sum[maxn]; 7 8 bool check(double x){ 9 static int q[maxn]; 10 int head=1,tail=0; 11 for(int i=1;i<=n;i++) sum[i]=1.0*a[i]-x+sum[i-1]; 12 for(int i=L;i<=n;i++){ 13 while(head<=tail && sum[q[tail]]>=sum[i-L]) tail--; 14 q[++tail]=i-L; 15 while(head<=tail && i-q[head]>R) head++; 16 if(sum[i]>=sum[q[head]]) return true; 17 } 18 return false; 19 } 20 21 int main(){ 22 scanf("%d%d%d",&n,&L,&R); 23 for(int i=1;i<=n;i++){ 24 scanf("%d",&a[i]); 25 Max=max(Max,a[i]); 26 } 27 double l=0,r=Max; 28 for(int i=1;i<=50;i++){ 29 double mid=(l+r)/2; 30 check(mid)?l=mid:r=mid; 31 } 32 printf("%.4f",l); 33 }