关于一些在 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;
}
posted @ 2025-08-25 10:13  BaiBaiShaFeng  阅读(18)  评论(0)    收藏  举报
Sakana Widget右下角定位