2024.10.3

First Post:

Last Update:

Word Count:
1.8k

Read Time:
7 min

模拟赛

数据范围:

值域很大,无法加到状态里。那么考虑每个操作对答案产生的贡献。

我们把攻击力拆开来算。

直接攻击,那么贡献就是

设这回合后一共攻击 次。

加攻击力,那么在这以后每次攻击伤害都会加 ,贡献就是

加增量,那么设后续攻击的回合是 ,那么贡献为

把这个东西稍微拆一下,得到

那么我们只需要记录攻击次数和攻击回合的编号和即可。

但有个问题,我们现在的贡献和未来回合的行动有关。

所以要倒过来做。那么转移也要相应的变一下。

表示第 个回合到最后一个回合,一共攻击了 次,攻击回合的编号和为 的最大值。

转移为

转移时可以把第一维压掉,但第二维要倒序枚举。

复杂度

Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include"bits/stdc++.h"
#define re register
#define int long long
using namespace std;
const int maxn=110,inf=1e18;
int n;
int a[maxn],b[maxn],c[maxn],f[maxn][maxn*maxn];
inline void solve(){
cin>>n;
for(re int i=1;i<=n;++i) cin>>a[i]>>b[i]>>c[i];
reverse(a+1,a+n+1);reverse(b+1,b+n+1);reverse(c+1,c+n+1);
memset(f,-0x3f,sizeof f);
f[0][0]=0;
for(re int i=1;i<=n;++i){
for(re int j=i-1;j>=0;--j){
for(re int k=0;k<=(i-1)*i/2;++k){
f[j+1][k+i]=max(f[j+1][k+i],f[j][k]+a[i]);
f[j][k]=max(f[j][k],f[j][k]+max((i*j-k)*b[i],j*c[i]));
}
}
}
int ans=-inf;
for(re int i=0;i<=n;++i) for(re int j=0;j<=n*(n+1)/2;++j) ans=max(ans,f[i][j]);
cout<<ans<<'\n';
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int T;cin>>T;while(T--) solve();
return 0;
}

Removing Leaves *2300

又读错题被硬控了。

一开始没看见 ,以为随便删。

考虑贪心。

我们用 set 存每个节点,然后每次找到叶子最多的那个点,然后删 个。找不到就结束。

这为什么是对的?

感性理解一下,我们这么做只会让能删的点越来越多,而且不会有本来能删的点因为操作而不能删除,所以这么做操作数就是最大的。

复杂度

Voting (Hard Version) *2400

看完题就想到了一个假贪心:先按 降序再按 升序,每次买 最大 最小的。

显然不对。我们不一定只从 最大的里面买。

那改成每次买 最小的呢?

还是不对。因为不一定买最小的就优。例如这组样例

1
2
3
2
1 1
2 2

显然只需要 即可。

那应该怎么做?

我们依旧按 降序排序。

对于当前的 ,设 值小于 的人数为 ,大于 且被买的人数为

假设我们已经把 的人全搞定了,那么只需要让 ,就能搞定 了。

但如果不满足,我们就要从 的人里买人(因为 是固定的),直到

满足该条件后, 就被我们搞定了。

那么我们只需要保证 的人都被搞定就行了。

然后再处理 的那些人。这是一个子问题,和上述步骤完全一致。

买人贪心买最便宜的,用个堆维护一下即可。

复杂度

Happy Life in University *2300

怎么现在啥数据结构都不会了/kk。

对于这种 lca 的问题,常见套路是枚举 lca,把贡献都在 lca 处统计(枚举 lca 的意思是考虑每个点作为 lca 时的情况)。

而这种 lca 的路径问题又能跟子树弄上关系。

设当前考虑到以 为根的子树。

那么我们只需要维护 到子树内节点的路径即可,答案就是拿两条路径拼起来。

假设已经搜完了子树,回溯到了

那么 会对所有路径产生自己颜色的贡献。

但显然会算重,那么考虑去重。

因为每个点只需要在离他最近的且颜色和他相同的祖先处减去重复,所以只会操作 次。

那么直接去重就是对的。

所以我们的操作就只有子树加和子树查,线段树维护。

答案用最大值和次大值拼一下即可。

复杂度

Wine Factory (Easy Version) *2300

讲一个很有意思的做法。

我们考虑有多少水被浪费了。

表示从 流向 的水的量。

转移为

这东西可以用 矩乘表示。

然后线段树维护矩阵即可。单点修改也是简单的。

复杂度

Mark and Professor Koro *2300

感觉和 [HBCPC2024] Enchanted 非常的像啊,套路也是一样。

对于这种两个相同的数合并成+1的数的操作,我们可以把它看成二进制意义下的加法,然后进行维护。

但这个题不能直接把数的二进制用整数表示,值域太大了。

我们用线段树维护值域上每个数出现次数,不难发现每个数出现次数只会为 0/1。

那么修改可以看成删一个数再加上一个数。

删和加是类似的,先考虑加。设加的那个数是

如果 那一位是 0,那么不用进位,直接给这一位 +1 即可。

否则会进位。但不难发现,这东西是一次区间覆盖和一次单点修改。

删数同理。

如果 那一位是 1,那么不用退位,直接给这一位 -1 即可。

否则会退位。依旧是一次区间覆盖和一次单点修改。

那么答案就是最右边的 1 的位置,这个可以线段树上二分找到。

具体的,线段树上维护区间里 1 的个数。

加法进位:找到 后第一个为 0 的位置 ,然后把 区间赋值为 0,把 赋值为 1.

减法进位:找到 后第一个为 1 的位置 ,然后把 区间赋值为 1,把 赋值为 0.

找位置都可以线段树上二分。

复杂度