关于一些在 Atcoder 上边找到的有意思的题目
经过 2025 HE 最强 Oier RETF 的讲课,我开始去写一些 Atcoder 的题目,发现很多题目确实很有意思,我做的题目都偏简单,基本上都是 1200-2400 这个范围的,再高我很难做出来了,而且我做题极其不稳定,所以想随便写一写。
之前写过不少好题,有空慢慢写。
[持续更新]
ABC393E
给定了 \(N\) 个数字。
询问从 \(1~N\),每次若必须选择一个数字,总共选择 \(K\) 个,这些数的 gcd 最大是多少。
\(A[i]\le 10^6;n,k\le 1.2*10^6\)
我们观察答案上下界,发现不会超过 \(1e6\),我们考虑从 1 开始枚举。
枚举的过程中我们求出来每一个数字的情况就行。
对于我们已经枚举到了这个 gcd 为 val, 我们用 \(cnt[x]\) 统计 \(x\) 在 \(N\) 个数字中的出现次数。
我们枚举这个 val 的所有倍数,判断有几个,如果大于 k,我们就尝试将这些倍数的答案取 max 于这个 gcd。
代码下
#include <bits/stdc++.h>
#define int long long
using namespace std;
namespace BaiBaiShaFeng{
const int MN=2e6+116;
int n, a[MN], cnt[MN], k, ans[MN];
void sol(){
cin>>n>>k; for(int i=1; i<=n; ++i) cin>>a[i];
for(int i=1; i<=n; ++i) cnt[a[i]]++;
for(int i=1; i<MN; ++i){
int sum=0;
for(int j=i; j<MN; j+=i) sum+=cnt[j];
if(sum>=k) for(int j=i; j<MN; j+=i)
ans[j]=max(ans[j],i);
}
for(int i=1; i<=n; ++i) cout<<ans[a[i]]<<'\n';
return;
}
}
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
BaiBaiShaFeng::sol(); return 0;
}
ABC390F
给了一个长度为 \(N\) 的序列 \(A\)。
我们规定 \(f(L,R)\) 代表着我们先取出来序列中的 \(A_L\) 到 \(A_R\) 这一部分。
对这些选出来的序列做以下操作,这个函数值就是最小的操作次数。
选定 \(l,r\),前提是序列中仍然在 \([l,r]\) 范围内每个位置都有出现次数。
删除 \([l,r]\) 范围内的所有次数。
询问我们 \(\sum_{l=1}^{N}\sum_{r=l}^{N}f(l,r)\)。
不难发现,这个问题就是问我们一个区间中有多少个联通块,我们考虑枚举位置,统计它的左边有多少个地方可以是区间左端点,右边的地方有多少可以是右端点,为了避免算重的问题,所有值的考虑以第一次为准,考虑没有后趋的情况,这样是不会重复的。
我们维护pre[i]和nxt[i]。
pre[i] 代表的是在 \(i\) 左侧最大的位置满足它是 \(A[i]\) 或者 \(A[i]+1\) 。
nxt[i] 代表的是在 \(i\) 右侧最小的位置满足它是 \(A[i]+1\)。
这样我们算出一共有几个也很简单
就是 (i-(pre[i]+1)+1)*((nxt[i]-1)-i+1)。
直接做就好啦
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MN=1e6+116;
int n, a[MN], pos[MN], pre[MN], nxt[MN], ans;
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n; for(int i=1; i<=n; ++i) cin>>a[i];
for(int i=1; i<=n+1; ++i) pos[i]=0;
for(int i=1; i<=n; ++i){
pre[i]=max(pos[a[i]],pos[a[i]+1]);
pos[a[i]]=i;
}
for(int i=1; i<=n+1; ++i) pos[i]=n+1;
for(int i=n; i>=1; --i){
nxt[i]=pos[a[i]+1];
pos[a[i]]=i;
}
for(int i=1; i<=n; ++i) ans+=(nxt[i]-i)*(i-pre[i]);
cout<<ans<<'\n';
return 0;
}
ABC364F
大概题意很简单。
一共有 \(N+Q\) 个点,给定了 \(Q\) 个区间 \([l_i,r_i]\), 每个区间有一个值 \(c_i\)。
第 \(i\) 个区间对应第 \(i\) 个点。
第 \(i\) 个点有一条连向区间 \(N+[l_i,r_i]\) 的权值为 \(c_i\)。
我们按照 \(c_i\) 从小到大,枚举每一个区间。
我们使用并查集维护每一个已经连好的联通块的右端点,这样的时间复杂度就是正确的。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MN=1e6+116;
int father[MN], n, q, ans=0;
int find(int x){
if(father[x]!=x) father[x]=find(father[x]);
return father[x];
}
struct Node{
int val, l, r;
bool operator < (const Node &o)const{
return val<o.val;
}
}save[MN];
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>q; for(int i=1; i<=n+q; ++i) father[i]=i;
for(int i=1; i<=q; ++i) cin>>save[i].l>>save[i].r>>save[i].val;
sort(save+1,save+q+1);
for(int i=1; i<=q; ++i){
int val=save[i].val, l=save[i].l, r=save[i].r;
ans+=val; while(find(l)!=find(r)){
ans+=val; father[find(l)]=find(l)+1;
}
}
int cnt=0;
if(find(1)!=find(n)) cout<<-1<<'\n';
else cout<<ans<<'\n';
return 0;
}
ABC366F
个人认为比较好的题目。
给定 \(N\) 个一次函数 \(f_1, f_2, \ldots, f_N\),其中 \(f_i(x) = A_i x + B_i\)。
对于由 \(K\) 个 \(1\) 到 \(N\) 之间互不相同的整数构成的长度为 \(K\) 的数列 \(p = (p_1, p_2, \ldots, p_K)\),请你求出 \(f_{p_1}(f_{p_2}(\ldots f_{p_K}(1)\ldots ))\) 能取得的最大值。
\(N\le 2\times 10^5\)
\(K\le 10\)
这个大家很容易想到动态规划吧,明显的,我们没有一个直接策略使得全局是最优的...我们发现有的函数可以造成很大影响,但是我们并不清楚它在那个位置才能发挥最大效益。
所以我们考虑动态规划,但是看着庞大的数据范围我们陷入了沉思...
这个状态自然是好设计的,按照考虑到第 \(i\) 个函数为阶段,附加一维到现在选择了 \(j\) 个作为状态。
\(dp[i][j]\) 就是考虑到第 \(i\) 个数字时,选择了 \(j\) 的最大值。
这个看上去没什么问题,事实上也没有什么问题。
唯一的问题在于我们并不知道怎么进行转移了......
因为我们新加入一个时,当前状态最佳的并不是直接按照这个新加入的运算一次,有可能是套在里边才会更大。
这可怎么办啊.... 如果我们可以保证每新考虑一个函数,直接在后边运算就是最优该有多好...
诶!那我们想一个办法保证直接在后边运算最优不就行了。
这种问题感觉想国王游戏一样,我们试一试临项交换。
我们不妨假设当前 \(i>j\) 且 \(f_j(f_i(x))<f_i(f_j(x))\),之后我们把这个不等式拆开。
\(A_j(A_ix+B_i)+B_j<A_i(A_jx+B_j)+B_i\)
乘开消去相同项后得到 \(A_jB_i+B_j<A_iB_j+B_i\)
之后观察式子尝试把拥有相同项的移到同侧。
\(B_j-A_iB_j<B_i-A_jB_i\)
\(B_j(A_i-1)>B_i(A_j-1)\)
我们可以按照这个排序,这样就可以保证每一次是最优的。
剩下的就不必多说了。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MN=4e5+415;
const int MK=20;
int n, k, dp[MN][MK];
struct Node{
int a, b;
}save[MN];
bool cmp(Node i, Node j){
return j.b*(i.a-1)<i.b*(j.a-1);
}
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>k; dp[0][0]=1;
for(int i=1; i<=n; ++i) cin>>save[i].a>>save[i].b;
sort(save+1,save+n+1,cmp);
for(int i=1; i<=n; ++i){
dp[i][0]=dp[i-1][0];
for(int j=1; j<=k; ++j){
dp[i][j]=max(dp[i-1][j],dp[i-1][j-1]*save[i].a+save[i].b);
}
}
cout<<dp[n][k]<<'\n';
return 0;
}
ABC159F
给定一个长度为 \(N\) 的整数序列 \(A_1, A_2, \ldots, A_N\) 和一个正整数 \(S\)。
对于所有满足 \(1 \leq L \leq R \leq N\) 的整数对 \((L, R)\),定义 \(f(L, R)\) 如下:
- \(f(L, R)\) 表示满足 \(L \leq x_1 < x_2 < \cdots < x_k \leq R\) 且 \(A_{x_1} + A_{x_2} + \cdots + A_{x_k} = S\) 的整数序列 \((x_1, x_2, \ldots, x_k)\) 的个数。
请计算所有满足 \(1 \leq L \leq R \leq N\) 的整数对 \((L, R)\) 的 \(f(L, R)\) 之和。由于答案可能非常大,请输出其对 \(998244353\) 取模的结果。
- 输入均为整数。
- \(1 \leq N \leq 3000\)
- \(1 \leq S \leq 3000\)
- \(1 \leq A_i \leq 3000\)
这个好像看起来不太可做?
计数问题先别着急开搞,我们发现可以枚举 \(L,R\) 跑背包,但是复杂度是万万不可的。
既然我们似乎没有办法从 \(L,R\) 下手,我们只剩下考虑一个和为 \(S\) 的子序列的贡献了。
我们发现如果知道这个子序列的起始位置和结束位置,我们叫做 \(l,r\),那么显然它可以贡献 \(l(n-r+1)\)
看似可做了一些。
如果我们对于每一个位置都维护下以它为终点的,左边那些 \(l\) 的总和,我们不就很简单的可以直接计算了嘛?
这个一看就是一个 dp 好乏。
我们假设 \(dp[i][j]\) 表示到目前的 \(i\) 为止,一共的和为 \(j\) 的,统共的 \(l\) 的和。
这个东西不就是一个 0/1 背包么?
从 i-1 考虑放一个不可重复利用的 \(a[i]\),最后吧和是 \(a[i]\) 的直接加上 \(i\)。
考虑到这个地方就好做了。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod=998244353;
const int MN=3555;
int dp[MN], n, s, a[MN], ans=0;
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>s; for(int i=1; i<=n; ++i) cin>>a[i];
for(int i=1; i<=n; ++i){
for(int j=s; j>a[i]; --j)
dp[j]=(dp[j]+dp[j-a[i]])%mod;
dp[a[i]]=(dp[a[i]]+i)%mod;
ans=(ans+(n-i+1)*dp[s]%mod)%mod;
dp[s]=0;
}
cout<<ans<<'\n';
return 0;
}
ABC385F
神秘题
一个小小贪心,不太好想。
考虑每一次仅仅取相邻的答案。
只有下一个比当前的高度小才是有意义的。
讨论再往下会发现所有的贡献都来自相邻的。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MN=1e6+116;
long double ans=-1e9;
int n;
struct Node{
long double x, h;
bool operator <(const Node &o)const{return x<o.x;}
}sav[MN];
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n; for(int i=1; i<=n; ++i) cin>>sav[i].x>>sav[i].h;
sort(sav+1,sav+n+1);
for(int i=2; i<=n; ++i){
ans=max(ans,sav[i].h-(sav[i].h-sav[i-1].h)/(sav[i].x-sav[i-1].x)*sav[i].x);
}
if(ans>=0) cout<<fixed<<setprecision(10)<<ans<<'\n';
else cout<<-1<<'\n';
return 0;
}

浙公网安备 33010602011771号