AtCoder Beginner Contest 410
A题
知识点:枚举
题意:现在有\(N\)场比赛,第\(i\)场比赛允许年龄为\(A_i\)或者更小的马参赛,在这\(N\)场比赛中,问一匹\(K\)岁的马可以参加多少场比赛。
解题:直接枚举每一场比赛,比较\(A_i\)与\(K\)的大小即可,答案\(ans+=K\leq a[i]? 1:0\)。
B题
知识点:模拟
题意:现在有\(N\)个空盒子,要放入\(Q\)个球,放球按照序列\(X=(X_1,X_2,\dots,X_Q)\)把球放入盒中。具体来说,对于第\(i\)个球,会执行以下操作:
- 如果\(X_i\geq 1\):把这个球放入编号为\(X_i\)的盒子。
- 如果\(X_i=0\):把这个球放入当前球数量最少的盒子,若有多个这样的盒子,选择编号最小的那个。
要找出每个球放入了那个盒子。
约束条件
-
所有输入值都是整数。
-
\(1 \leq N \leq 100\)
-
\(1 \leq Q \leq 100\)
-
\(0 \leq X_i \leq N\)
解题:开一个\(ans[n]\)记录每个球放入的位置,每次按照题意模拟即可,因为\(N,Q\)均小于等于100,时间复杂度为\(O(NQ)\)是可以过的。
int get(vector<int> cnt,int n){
int res=1,mn=inf;
for(int i=1;i<=n;i++){
if(cnt[i]<mn){
res=i;
mn=cnt[i];
}
}
return res;
}
void solve(){
int n,q;cin >> n >> q;
vector<int> a(q+1);
for(int i=1;i<=q;i++) cin >> a[i];
vector<int> ans(q+1,0),cnt(n+1,0);
for(int i=1;i<=q;i++){
if(a[i]!=0){
cnt[a[i]]++;
ans[i]=a[i];
}else{
int id_min=get(cnt,n);
cnt[id_min]++;
ans[i]=id_min;
}
}
for(int i=1;i<=q;i++){
cout << ans[i] << " ";
}
cout <<endl;
}
C题
知识点:模拟,思维
问题描述
有一个长度为 \(N\) 的整数序列 \(A\),初始时 \(A_i = i\)。需要处理总共 \(Q\) 个以下类型的查询:
- 类型 1:将 \(A_p\) 的值修改为 \(x\)。
- 类型 2:输出 \(A_p\) 的值。
- 类型 3:重复执行 “将 \(A\) 的第一个元素移动到末尾” 操作 \(k\) 次。形式化地说,就是把 \(A\) 替换为 \((A_2, A_3, \ldots, A_N, A_1)\),重复执行 \(k\) 次。
约束条件
- 所有输入值均为整数。
- \(1 \leq N \leq 10^6\)
- \(1 \leq Q \leq 3 \times 10^5\)
- 所有查询满足以下约束:
- \(1 \leq p \leq N\)
- \(1 \leq x \leq 10^6\)
- \(1 \leq k \leq 10^9\)
解题:我们可以拿一个变量tot记录循环左移的次数,同时对\(N\)取模。之后每次查询位置\(p\)时,查询的是将数组移动后的位置\(p\),由于我们并不移动数组,那么需要求出移动前的位置\(p_1\),有\(p_1=(p+tot) \mod N\),所以是查询\(p_1\)位置。因为在模\(N\)下,减法有逆运算,因此是可以通过记录左移次数,通过逆运算得到原来位置,询问位置\(p\)是左移之后的位置,那么对应的原来位置只需要将\(p\)向右移\(tot\)。即\(p_1\)和\(p\)是一一映射的。注意如果算出来的原来下标为0,需要特判变为n。时间复杂度为\(O(N)\)。
void solve(){
int n,q;cin >> n >> q;
vector<int> a(n+1);
for(int i=1;i<=n;i++) a[i]=i;
int sum=0;
while(q--){
int op,p,k,x;
cin >> op;
if(op==1){
cin >> p >> x;
int id=(p+sum)%n;
if(id==0) id=n;
a[id]=x;
}else if(op==2){
cin >> p;
int id=(p+sum)%n;
if(id==0) id=n;
cout << a[id] << endl;
}else{
cin >> k;
sum=(sum+k)%n;
}
}
}
D题
知识点:最短路,异或
问题陈述
存在一个有 \(N\) 个顶点和 \(M\) 条边的有向图,顶点编号从 \(1\) 到 \(N\),边编号从 \(1\) 到 \(M\)。第 \(i\) 条边是从顶点 \(A_i\) 指向顶点 \(B_i\)、权重为 \(W_i\) 的有向边。
请找出从顶点 \(1\) 到顶点 \(N\) 的一条路径(walk)中,所包含边的权重的按位异或(XOR)的最小值。
约束条件
- \(2 \leq N \leq 1000\)
- \(0 \leq M \leq 1000\)
- \(1 \leq A_i, B_i \leq N\)
- \(0 \leq W_i < 2^{10}\)
- 所有输入值均为整数
解题:由于到顶点\(N\)的每一条路径都可能成为答案,所以需要记录从1到每个顶点的异或值都要记录,同时值域为\(2^{10}\),即每个点都要维护一个set记录从1到达这个点的异或值。之后跑spfa求出到每个点的所有异或值,只要没出现过,就要加入队列中。时间复杂度为\(O(N\times 2^{10})\leq 1e6\),是可行的。
vector<pair<int,int>> g[maxn];
int dp[maxn];//dp[i]表示从顶点1到顶点i的最小路径异或值
set<int> re[maxn];//re记录从1到每个点的所有异或值
int n,m,vis[maxn];
void spfa(int s){
re[s].insert(0);
queue<int> q;
q.push(s);
vis[s]=1;
while(q.size()){
int u=q.front();
q.pop();
vis[u]=0;
for(auto[v,w]:g[u]){
for(int ww:re[u]){
int nw=ww^w;
if(!re[v].count(nw)){
re[v].insert(nw);
if(!vis[v]){
q.push(v);
vis[v]=1;
}
}
}
}
}
}
void solve(){
cin >> n >> m;
for(int i=1;i<=m;i++){
int u,v,w;cin >> u >> v >> w;
g[u].push_back({v,w});
}
//跑spfa算法,从1出发去更新每一个顶点
spfa(1);
int ans=inf;
for(int i:re[n]){
ans=min(i,ans);
}
if(ans==inf) cout << -1 << endl;
else cout << ans << endl;
}
E题
知识点:多限制dp,状态压缩,决策树剪枝
问题陈述
高桥即将玩一个游戏,他要按顺序与 \(N\) 只怪物战斗。初始时,高桥的生命值是 \(H\),魔法值是 \(M\)。
对于第 \(i\) 只怪物,他可以选择以下行动之一:
- 不用魔法战斗:只有当他的生命值至少为 \(A_i\) 时才能选择。选择后,他的生命值会减少 \(A_i\),且该怪物被击败。
- 用魔法战斗:只有当他的魔法值至少为 \(B_i\) 时才能选择。选择后,他的魔法值会减少 \(B_i\),且该怪物被击败。
当所有 \(N\) 只怪物都被击败,或者他无法进行任何行动时,游戏结束。在游戏结束前,他最多能击败多少只怪物?
约束条件
- \(1 \leq N \leq 3000\)
- \(1 \leq H, M \leq 3000\)
- \(1 \leq A_i, B_i \leq 3000\)
- 所有输入值均为整数
解题:
定义\(dp[i][h][m]\)表示当前生命剩余值为\(h\),魔力剩余值为\(m\),打败前\(i\)只怪物是否可行。
初始化\(dp[0][H][M]=1\),其他全为0。
状态转移:对于第\(i\)关:
统计答案,从后往前枚举\(i\),再枚举\(h\)和\(m\),遇到可行状态那么输出这个\(i\)。时间复杂度为\(O(NMH)\),状态数达到了1e9,会TLE和MLE。所以这样是错误的。考虑如何优化?状态空间一共有1e9个,即(i,h,m),我们可以转化思路,对于到达某一个关卡\(x\),如果剩余\(m\)相同,那么我们只会要\(h\)最大的那个,因为更小的\(h\)一定不会更优,所以考虑固定\(m\),定义\(dp[i][m]\)表示剩余魔力值为\(m\),打败前\(i\)个怪兽剩余的最大生命值。初始化\(dp[0][M]=H\),其他全部为-1。
状态转移:对于第\(i\)关:
之后依然从后往前枚举\(i\),枚举\(m\),判断\(dp[i][m]\geq 0\),那么输出\(i\)。时间复杂度为\(O(NM)\),1e6是可以过的。
void solve(){
int n,H,M;cin >> n >> H >> M;
vector<int> A(n+1),B(n+1);
for(int i=1;i<=n;i++){
cin >> A[i] >> B[i];
}
//dp[i][m]表示击败前i只怪物,剩余魔力值m下能够保留的最大生命值
vector<vector<int>> dp(n+1,vector<int>(M+1,-1));//-1表示状态不可达
dp[0][M]=H;
for(int i=1;i<=n;i++){
for(int m=0;m<=M;m++){
//当前怪物普攻
if(dp[i-1][m]!=-1&&dp[i-1][m]>=A[i]){
dp[i][m]=max(dp[i][m],dp[i-1][m]-A[i]);
}
//当前怪物使用魔法
if(m+B[i]<=M&&dp[i-1][m+B[i]]!=-1){
dp[i][m]=max(dp[i][m],dp[i-1][m+B[i]]);
}
}
}
int ans=0;
for(int i=n;i>=0;i--){
for(int m=0;m<=M;m++){
if(dp[i][m]>=0){
ans=i;
i=-1;
break;
}
}
}
cout << ans << endl;
}
E题是真可以,这种多限制的dp可以转化为检查其中几个限制下某个限制的最值来判断是否可行,进行状态压缩。

浙公网安备 33010602011771号