一套有难度的题,据说平均水平一百多,我感觉…差不多吧。
静下心来搞程序。
T1.
题意:对一个数给操作,分别是获得这个数乘以某个数的收益且成比例减小此数,或相反。求最后的最大收益。
分析:反向DP,因为当前决策会对后来产生影响,所以如果正向的话显然是不能存的,然后可以发现,现在的能力值对后面的影响是成正比的,并且这个无法用单价的提高来替换——毕竟单价本身不影响后面的收益,不过想到这里我们就可以发现,如果倒着推就可以排除能力值的影响,直接把能力值的影响体现在单价上——毕竟,现在的能力值不会影响之前的单价,而且最后的能力值对结果无影响。所以,就得到了反向推最高单价的做法。
#include
#include
using namespace std;
int n,typ[100000+5];
double a[100000+5],k,c,w,p;
int main()
{
freopen("explo.in","r",stdin);
freopen("explo.out","w",stdout);
scanf("%d %lf %lf %lf",&n,&k,&c,&w);
for(int i=1;i<=n;i++)scanf("%d %lf",&typ[i],&a[i]);
for(int i=n;i>=1;i--){
if(typ[i]==1)
p=max(p,p*(1-k/100)+a[i]);
else
p=max(p,p*(1+c/100)-a[i]);
}
printf("%.2lf",p*w);
return 0;
}
T2:
题意:给定一个数列,求该数列在某一区间的所有子序列的和摸以一个数的最小值。
分析:
80分做法:
由于被模数比较小,就可以考虑抽屉原理,即只要区间长度大于p就直接输出0,否则就暴力加一个小剪枝(这个剪枝可以再过一组随机数据,就是得到答案0了直接退出),一共80分;
100分做法:
还是区间大于p就直接输出0,然后我们会发现剩下的30分前缀和暴力拿不完,接着分析,发现我们如果对于这个区间做一个模意义上的前缀和(即先前缀和再摸,不一定单增),就可以利用set的lower bound 和upper bound(因为数据必须放进去就有序,然后又要能取任意数,不写平衡树就只能用set或者map了),查看比当前正在枚举的数小的最大的数(且该数一定在正被枚举的数左边,因为边枚举边插入),这样就可以把第二个p变成logp;
为什么要选择比正在枚举的数小的最大数呢,首先,我们想象一个数轴,由前缀和的定义可知,必须是正枚举数减去其左边的数,否则结果就不对了(这是很显然的,你把一个数取相反数以后加上p,和这个数本身肯定不一样),那么为什么要比它大呢,在这个数轴AB上值域是0到p-1,我们想象一个点M为当前点,那么另一个点N若比M小,则答案是MN,反之,则答案为AM+BN(因为M-N后要+P),而AM>=MN,所以用小值一定更优,若不幸地找不到小值,则用当前最大值,目的是为了减小BN,毕竟,也许虽然正在枚举的点找不到比它更小的,但却足以更新最小值(这是很可能发生的));
80分程序:
#include
#include
using namespace std;
long long a[500005],f[500005];
int lef,rig,p,n,m;
int main()
{
freopen("seq.in","r",stdin);
freopen("seq.out","w",stdout);
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
f[i]=f[i-1]+a[i];
}
for(int i=1;i<=m;i++){
scanf("%d %d %d",&lef,&rig,&p);
if(rig-lef+1>p)printf("0\n");
else {//分斥要考虑全面啊。
long long mini=2e9;
for(int j=lef;j<=rig;j++){
if(!mini)break;
for(int k=j;k<=rig;k++){
mini=min(mini,(f[k]-f[j-1])%(long long)p);
if(!mini)break;
}
}
printf("%I64d\n",mini);
}
}
return 0;
}
100分程序:(hzw手写的平衡树,set由于有些情况未考虑成功被卡常熟,我这个程序在学校电脑上仍是80分,要跑1.2-1.6s的样子,但我还没有那个能力学习平衡树,所以我就只能写到这个程度了,思路是这样的。
#include
#include
#include
#include
using namespace std;
long long a[500005],f[500005],p;
set<int>ff;
int lef,rig,n,m;
int main()
{
freopen("seq.in","r",stdin);
freopen("seq.out","w",stdout);
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%I64d",&a[i]);
f[i]=f[i-1]+a[i];
}
for(int i=1;i<=m;i++){
scanf("%d %d %I64d",&lef,&rig,&p);
if(rig-lef+1>p)printf("0\n");
else {//分斥要考虑全面啊。
long long mini=2e9;
long long mini2=f[lef-1]%p,maxn=f[lef-1]%p;
ff.clear();
ff.insert(f[lef-1]%p);
for(int j=lef;j<=rig;j++){
if(f[j]%pelse
mini=min(mini,f[j]%p-*(--ff.upper_bound(f[j]%p)));
ff.insert(f[j]%p);
mini2=min(mini2,f[j]%p);
maxn=max(maxn,f[j]%p);
}
printf("%I64d\n",mini);
}
}
return 0;
}
注意边界处理,ub是大于的第一个,lb是大于等于的第一个;
T3:
题意:给定一可能有复权的有向图,你可以给所有路径加上一个数,然后走1到n的最短路,要求加上这个t之后,1到n的路要是>=0条件下的最小值。
分析:首先,1到n的最短路随t增加不下降,所以这里的t可以二分。
我一直不擅长写二分这次居然一遍过了感觉准度有提升开心。
然后最短路就SPFA好写也不慢,判负环用cnt记松弛次数,要注意的是若负环不在1到N的所有路上是不会影响的,若在则会导致没有最小路,这里比较难处理,我是把负环上的所有路(即所有松弛次数超过N的路,可以简单证明这个方法可以修改负环上的所有路)的f设为-INF,这样1到N的路就一定是负数,也就一定可以排除这种情况了,我的程序60行,std写了100+,感觉黄学长真勤劳….
#include
#include
#include
#include
#include
#define clr(x) memset(x,0,sizeof(x))
using namespace std;
int f[105],fr[105],tov[10000+5],des[10000+5],wor[10000+5],cnt[105];
int t,n,m,sta,fin,mini,ans,mid;
int main()
{
freopen("earth.in","r",stdin);
freopen("earth.out","w",stdout);
scanf("%d",&t);
for(int i=1;i<=t;i++){
clr(fr);mini=2e9;
scanf("%d %d",&n,&m);
for(int j=1;j<=m;j++){
scanf("%d %d %d",&sta,&fin,&wor[j]);
tov[j]=fr[sta];fr[sta]=j;des[j]=fin;
}
int lef=-1e5,rig=1e5;
while(lef<=rig){
ans=2e9;clr(cnt);
mid=(lef+rig)/2;
memset(f,127,sizeof(f));
queue<int>q;
q.push(1);f[1]=0;
while(!q.empty()){
int u=q.front();q.pop();
if(cnt[u]>n){
ans=-1;
break;
}
for(int j=fr[u];j;j=tov[j])
if(f[des[j]]>f[u]+wor[j]+mid){
f[des[j]]=f[u]+wor[j]+mid;
cnt[des[j]]++;
q.push(des[j]);
}
}
if(ans>=0)ans=f[n];
if(ans>0){
rig=mid-1;
mini=min(mini,ans);
}
else if(ans<0)lef=mid+1;
else {
mini=0;break;
}
}
if(mini!=2e9)printf("%d\n",mini);
else printf("-1\n");
}
return 0;
}