比赛链接:这里
OP
E的知识点好像需要补,赛时想到的都爆TLE
A 股神
贪心 / DP
思路
朴素的贪心思想:
次日若比此日股价高,且没有买入,则所有金币买入;
次日若比此日股价低,且有买入,则全部卖出;
注意输出时转换成金币输出;
注意浮点误差;
注意第0天也可以进行买入操作。
DP方法可以参考ph大佬的题解。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll M=1e9+7;
const int INF=0x3f3f3f3f;
int main()
{
double gp[502],w=1,g=0;
int n,i;
cin>>n;
for(i=1;i<=n;i++)scanf("%lf",&gp[i]);
gp[0]=1;//第0天股价
for(i=0;i<n;i++)
{
if(g==0)
{
if(gp[i+1]>gp[i])
{
g=w/gp[i];
w=0;
}
}
else
{
if(gp[i+1]<gp[i])
{
w=g*gp[i];
g=0;
}
}
}
if(g>0)w+=g*gp[n];//转换为金币,防浮点误差
printf("%.2lf",w);
return 0;
}
B 酒馆决斗
模拟
思路
排序后进行模拟,注意每次出招都有可能造成对方减员,而不是每回合;
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll M=1e9+7;
const int INF=0x3f3f3f3f;
struct qd
{int a,b;}qua[100005],shp[100005];
bool cmp(qd x,qd y)//排序
{
if(x.a==y.a)return x.b>y.b;
return x.a>y.a;
}
int main()
{
int n,m,i,j;
cin>>n>>m;
for(i=1;i<=n;i++)scanf("%d%d",&qua[i].a,&qua[i].b);
for(i=1;i<=m;i++)scanf("%d%d",&shp[i].a,&shp[i].b);
sort(qua+1,qua+1+n,cmp);
sort(shp+1,shp+1+m,cmp);
i=j=1;
for(;i<=n&&j<=m;)
{
shp[j].b-=qua[i].a;
if(shp[j].b<=0)j++;
if(j>m)break;
qua[i].b-=shp[j].a;
if(qua[i].b<=0)i++;
}
if(i<=n)printf("Quasrain");else printf("SHP");
printf(" wins, left ");
printf("%d",max(m-j+1,n-i+1));
return 0;
}
C 求 k 整除最大元素和
DP
思路
建立dp方程 d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ ( j − g % k + k ) % k ] + g , d p [ i − 1 ] [ j ] ) dp[i][j]=max(dp[i-1][(j-g\%k+k)\%k]+g,dp[i-1][j]) dp[i][j]=max(dp[i−1][(j−g%k+k)%k]+g,dp[i−1][j]), d p [ i ] [ j ] dp[i][j] dp[i][j]代表有前 i 个数时, m o d ( k ) = j mod( k) = j mod(k)=j 时的最大和;
[ ( j − g % k + k ) % k ] [(j-g\%k+k)\%k] [(j−g%k+k)%k]是为了防止计算后的余数变负或溢出;
赛时没找到简单的压缩至一维dp的方法(实际上应该有),而开[102][100005]会MLE;
因为处理 [i] 时只需要 [i] 与 [i-1] 行数数据;
所以通过开 [2][100005] ,并在 [1] 每一行处理完后,将数据更新[0]内,以节省内存;
数据最大和为1e14,可以放心大胆地用 ll 进行dp;
注意dp初始值的设定。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll M=19260817,N=1e9+9;
const int INF=0x3f3f3f3f;
ll dp[2][102]={0};
int main()
{
int n,k,i,j,g;
cin>>n>>k;
for(i=1;i<k;i++)dp[0][i]=-1e14-1;//初值
for(i=1;i<=n;i++)
{
scanf("%d",&g);
for(j=0;j<k;j++)
{
dp[1][j]=max(dp[0][(j-g%k+k)%k]+g,dp[0][j]);
}
for(j=0;j<k;j++)//更新数据至dp[0]
{
dp[0][j]=dp[1][j];
}
}
printf("%lld",(dp[0][0])>0?dp[0][0]%M:0);
return 0;
}
D 取快递
贪心
思路
以a为排序依据,降序排列;
从用时最长的开始,直至超能力时间和大于该快递用时,或该快递的超能力用时大于标准用时时,跳出贪心;
跳出后比较标准用时与超能力用时和的最大值输出。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll M=1e9+7;
const int INF=0x3f3f3f3f;
struct qd
{int a,b;}qdb[100005];//快递表
bool cmp(qd x,qd y)
{
if(x.a==x.b)return x.b<y.b;
return x.a<y.a;
}
int main()
{
int n,i,j,tb=0;
cin>>n;
for(i=1;i<=n;i++)scanf("%d%d",&qdb[i].a,&qdb[i].b);
sort(qdb+1,qdb+1+n,cmp);
for(i=n;i>=1;i--)
{
if(qdb[i].b>qdb[i].a)break;//此快递超能力用时大于标准用时
else
{
if(tb+qdb[i].b<=qdb[i].a)tb+=qdb[i].b;
else break;//超能力取此快递前超能力用时和大于此快递标准用时
}
if(tb>=qdb[i].a)break;//超能力取此快递后超能力用时和大于此快递标准用时
}
printf("%d",max(tb,qdb[i].a));
return 0;
}
数组与指令
原题被删除了,在这里大概描述一下:
题目描述
有一长度为n的数组,初始值全为0,现对其进行m此操作:
每次操作遵循op a b
的输入形式
若op为1,则对第a位的数加b;
若op为2,则输出数组[a,b]区间上的非零数个数;
若op为3,则输出数组[a,b]区间上数字之和;
输入共m+1行,第一行输入n,m;
接下来m行输入m次操作;
对于每一次操作2或操作3,输出一行答案;
数据范围:
1
<
=
n
<
=
1
e
9
1<=n<=1e9
1<=n<=1e9
1
<
=
m
<
=
1
e
5
1<=m<=1e5
1<=m<=1e5
o
p
∈
{
1
,
2
,
3
}
op\in\{1,2,3\}
op∈{1,2,3}
1
<
=
a
<
=
b
<
=
1
e
9
1<=a<=b<=1e9
1<=a<=b<=1e9
大概是这个样的,目前还没什么好想法。
F 幸运数字
暴力模拟
思路
暴力模拟即可。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll M=1e9+7;
const int INF=0x3f3f3f3f;
int main()
{
int m,i,j,ans=0;
cin>>m;
for(i=1;i<=m;i++)
{
int sum=0;
j=i;
while(j)
{
sum+=j%10;
j/=10;
}
if(i%sum==1)ans++;
}
cout<<ans;
return 0;
}
G 有限小数
进制转换
思路
可以参考浮点数进制转换;
我先将获得的A进制数转换为10进制,再进行处理,方便操作与理解。
对于进制转换,我们可以不关心转换成的B进制数具体是什么,只关心是否有剩余;
所以我们就可以暴力地进行足够多位的转换,直接看结果是否有剩余即可。
关于进一步优化可以参考ph大佬的题解。
PS:ph大佬在进制转换的字符串遍历方向错了的情况下依然过掉了9/10,%%%。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll M=19260817,N=1e9+9;
const int INF=0x3f3f3f3f;
ll ctoten(string& n,ll a)//change to ten
{
//cout<<n<<endl;
ll ans=0,ma=1;
for(int i=n.length()-1;i>=0;i--)
{
ans+=(n[i]>='0'&&n[i]<='9')?(n[i]-'0')*ma:(n[i]-'A'+10)*ma;
ma*=a;
}
//printf("%lld\n",ans);
return ans;
}
int main()
{
ll a,b,nu,nd,i;//numberup,numberdown
string up,down;
cin>>up>>down>>a>>b;
nu=ctoten(up,a);
nd=ctoten(down,a);
if(nu>=nd)nu-=nd;
//printf("%lld %lld\n",nu,nd);
for(i=1;i<=10000000;i++)//1e7位
{
nu*=b;
nu%=nd;
}
if(!nu)printf("Yes");//分子为0即无剩余
else printf("No");
return 0;
}
H 子数组和
滑窗
思路
从右界累加至和不小于 k ,再从左界累减至不大于k;
若此时和值为k,则输出左右界;
否则即再执行上面的操作;
若遍历完都没有和为k的情况,则输出-1;
注意处理边界。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll M=1e9+7;
const int INF=0x3f3f3f3f;
int main()
{
ll n,a[1003],k,i,j,sum;
cin>>n>>k;
for(i=1;i<=n;i++)scanf("%lld",&a[i]);
i=1,j=0,sum=0;
for(;i<=n&&j<=n;)
{
while(sum<k&&j<=n)j++,sum+=a[j];//移动右界
while(sum>k&&i<=n)sum-=a[i],i++;//移动左界
if(sum==k)break;
}
if(j==n+1||i==n+1)printf("-1");//边界溢出则无子数组满足条件
else printf("%lld %lld",i,j);
return 0;
}
ED
码字较急,难免有疏漏与错误,欢迎评论区讨论与反馈。