AtCoder Beginner Contest 281(D,E,F)
AtCoder Beginner Contest 281(D,E,F)
D
这个题大意是给n个数,我们可以任意选取k个数,只要这k个数的和是d的倍数,答案就是d的倍数的最大值
这个是dp
//dp[i][j][k],i代表是从1到i,从中间选取j个数,k是这j个数的和模d的余数k,这个情况的最大和
//关键代码如下
for (int i=1;i<=n;i++)
{
for (int j=1;j<=min(i,k);j++)//选择j个
{
for (int u=0;u<d;u++)//余数
{
//dp[i-1][j][u]是不选择a[i],后面的是选择a[i],如果这一个余数是u,那么上一个就是((u-a[i])%d+d)%d,避免出现负数
dp[i][j][u]=max(dp[i-1][j][u],dp[i-1][j-1][((u-a[i])%d+d)%d]+a[i]);
}
}
}
还有预处理要注意
#include <iostream>
#include <cstring>
using namespace std;
const int maxn=110;
#define int long long
int n,k,d;
int a[maxn],dp[maxn][maxn][maxn];
signed main ()
{
cin>>n>>k>>d;
for (int i=1;i<=n;i++)
{
cin>>a[i];
}
memset(dp,-0x3f,sizeof(dp));
for (int i=0;i<=n;i++)//一个不取的时候余0的时候和为0
{
dp[i][0][0]=0;
}
for (int i=1;i<=n;i++)
{
for (int j=1;j<=min(i,k);j++)//
{
for (int u=0;u<d;u++)//列举取模d的数
{
dp[i][j][u]=max(dp[i-1][j][u],dp[i-1][j-1][((u-a[i])%d+d)%d]+a[i]);
}
}
}
int ans=dp[n][k][0];
if (ans<0)
{
cout<<-1<<'\n';
}
else cout<<ans<<'\n';
system ("pause");
return 0;
}
E
这一个题大意是有n个数,我们可以把这n个数,从i到i+m-1这段的数,选取前k小的数的和,输出
这一个我自己想的不太周全(还是太菜了)
不过通过这个题我学到了好多,还稍稍了解了对顶栈,这个用到了其中的思维(类似)
这一个题呢是通过multiset来存m个数,不过是分开存的,L存的是前k个小的数,R存的是后面的数
我们除第一次外,要进行三个操作
1.每一次都需要删除上一轮的最前面的一个,我们需要从L或者是R中删除这一个,
2.还要加入一个新的数,如果上一个操作是在L 里删除的,那么我们需要加入到R里,反之,加入到L里(如果都在同一个容器里,可能会比较麻烦,因为有要重新找要删除的迭代器了,加入一个就可能会让加入前的迭代器所指向的那个数和我们原本需要的数不同了,不如岔开他们,反正后面都是要维护的,个人想法)
3.进行完前面的操作后,还要维护L是这一次m个数的前k小的数
具体实现看代码
#include <iostream>
#include <string>
#include <set>
#include <algorithm>
using namespace std;
#define int long long
const int maxn=2e5+10;
int n,m,k;
int a[maxn];
signed main ()
{
cin>>n>>m>>k;
for (int i=1;i<=n;i++)
{
cin>>a[i];
}
multiset<int>L,R;
for (int i=1;i<=m;i++)
{
L.insert(a[i]);
}
while (L.size()>k)
{
auto pos=(--L.end());//选择最大的那个
R.insert(*pos);
L.erase(pos);
}
int ans=0;
for (auto x:L)
{
ans+=x;
}
cout<<ans<<" ";
for (int i=2;i+m-1<=n;i++)//要删除的数,i-1,i是开始的位置
{
int sub=a[i-1];//要删除的
int add=a[i+m-1];//要加入的
if (R.find(sub)!=R.end())//这个要删除的数不是那k的小的数
{
R.erase(R.find(sub));//不管是不是在哪一个容器,都要删除,在容器里的都是可以用的(有可能)
L.insert(add);
ans+=a[i+m-1];
}
else //对于这个要删除的,不是在L,就是在R的
{//如果在这次要删除的,如果是上一轮的前k小个,那么ans就要减去这个要删除的数
ans-=sub;
L.erase(L.find(sub));
R.insert(a[i+m-1]);
}
while (L.size()>k)
{
auto pos=(--L.end());
ans-=*pos;//L是保证是前k个小的数,所以我们要维护L里面有k个,多出的就要减去
R.insert(*pos);//把这个数放进R,下一次可能会用到
L.erase(pos);//删掉这个
}
while (L.size()<k)//还有比k小的情况也不要忘记
{
auto pos=R.begin();//如果L不够,就要从R拿,不过我们需要的是前k小的数,所以我们应该拿R中最小的那一个
ans+=*pos;
L.insert(*pos);
R.erase(pos);
}
cout<<ans<<" ";
}
system ("pause");
return 0;
}
F
这个是给我们n个数,我们可以对每一个数异或一个x(任意),保存异或后的最大值,求出那个最大值最小可能是多少
对于这n个数,我们可以看成二进制
对于从0到29位,如果某一位,每个数的这一位都是同一个数(为1,我们可以让x的这一位为1,否则,x的这一位为0),那么最后那个最大值的这一位一定是0,而有出现过两种情况,我们可以选择答案小的那一个
这一个用到了字典树(我还是第一次做字典树的题,不过这个不是字符,而是二进制上的1还是0)
通过分治递推的方式
注意字典树要开的大一点
具体看代码
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
const int maxn=2e5+10;
#define int long long
int n,a[maxn];
int tr[maxn*32][2];
int cnt;
void insert(int x)//字典树的插入
{
int p=0;
for (int i=29;i>=0;i--)
{
int k=(x>>i)&1;
if (tr[p][k]==0)
{
tr[p][k]=++cnt;
}
p=tr[p][k];
}
return ;
}
int solve(int num,int p)//num代表二进制的位数
{
if (num==-1) return 0;//减到-1就没有了
if (tr[p][0]==0)//如果这一位没有出现0,只有1,只有一种数,答案的这一位一定是0
{
return solve(num-1,tr[p][1]);//要么有0,要么是1,反正一定有一个,x的这一位是0,tr[p][1]一定有,也只有这个情况
}
else if (tr[p][1]==0)//同上
{
return solve(num-1,tr[p][0]);//
}
else //0和1都出现了,那么最大值的这一位一定是1,加上这一位的值(1<<num),下一位
{
int res=(1<<num)+min(solve(num-1,tr[p][0]),solve(num-1,tr[p][1]));//这一位选1和选0,的不同情况,选小的
return res;
}
}
signed main ()
{
cin>>n;
for (int i=1;i<=n;i++)
{
cin>>a[i];
insert(a[i]);
}
int ans=solve(29,0);
cout<<ans<<'\n';
system ("pause");
return 0;
}
字典树还要好好加油
其他的也要加油,以前一直不喜欢atcoder,(我有些题老是看不懂,搞得我一肚子气,这次的题好像还都看得懂,觉得我又可以了)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)