SCOI2024day1t3重整山河 题解
我去我好像做不太懂这道题,有无大手子教教/ll
题意
有 \(n\) 只怪兽,每只怪兽有两个属性 \((a_i,b_i)\)。其中 \(a_i\) 表示生命值,\(b_i\) 表示爆炸伤害。每次你选择一只怪兽攻击;一只怪兽死亡后会对所有怪兽造成 \(b_i\) 点伤害。怪兽被攻击/生命值降为 \(0\) 则立刻死亡。需要求出最小攻击次数。
但是现在只给定 \(a_i\),只知道 \(b_i \in [l_i,r_i]\)。你需要对所有 \(b\) 序列,求出最小攻击次数的和。
对 \(M\) 取模。
\(1 \le n \le 20,1 \le l_i \le r_i \le 15,1 \le a_i \le 300,2 \le M \le 10^9+7\)。
解法
1
考虑 \(b\) 序列固定,如何算答案。
注意到你可以化身白金之心,来一手神之时停:钦定一个攻击集合 \(S\),攻击完一个人让它先别炸,等全部攻击完最后一起炸。
然后我们按 \(a_i\) 升序排序,则最终死亡怪兽必然是一个前缀。假设我们从前往后依次考虑每个怪兽是否能够死亡,怪兽 \(i\) 能死亡等价于 \(i \in S \lor \sum_{j=1}^{i-1}b_j+\sum_{t=i+1}^{n}b_t[t \in S] \ge a_i\)。
时间复杂度 \(O(2^nn)\)。
2
下文上式统一指代 \(\sum_{j=1}^{i-1}b_j+\sum_{t=i+1}^{n}b_t[t \in S] \ge a_i\)。
称一个元素【可以自然死亡】等价于【满足上式】。
但是这太区了。经验告诉我们,想要计数,起码解决子问题的复杂度要是多项式的。
注意到上式中我们只关心后缀 \(S\) 中 \(b\) 的和。所以不妨从后往前依次考虑每个元素是否属于 \(S\):如果它满足上式,则你不攻击它它也能自然死亡,所以我们先不攻击它;否则出现了两个选择:攻击它,或攻击后缀尚未被攻击的一些怪兽使得它满足上式子。
记 \(T = \{b_j|j \text{ haven't been attacked so far}\}\),记 \(mx\) 表示 \(T\) 中的最大值。我们进行一个分讨:若 \(b_i \ge mx\),攻击 \(a_i\) 既最大化 \(now\) 又最小化攻击次数,所以是最优的;否则 \(b_i \lt mx\),考虑 \(mx\) 所属的怪兽 \(j\),其生命值和爆炸伤害都偏序怪兽 \(i\),我们肯定优先攻击怪兽 \(j\)。并且可以证明,攻击完怪兽 \(j\) 后 \(i\) 就一定满足上式,不会再取 \(T\) 中的第二个元素攻击。(感性理解就是攻击 \(j\) 不劣于攻击 \(i\),而攻击 \(i\) 的新增攻击次数是 \(1\);理性证明就是考虑 \(i\) 右边第一个未被攻击的怪兽(一定存在),考虑二者式子左边的差,发现至多为 \(b_i\),而我们现在新增 \(b_j \gt b_i\),且 \(a_i\) 还变小了,必然满足限制。)
所以我们得到做法:维护 \(s\) 表示 \(b\) 的前缀和。从后往前扫,用大根堆维护集合 \(T\),记 \(now\) 表示已决策元素的 \(b_i\) 之和。每次先将 \(b_i\) 加入大根堆,然后如果 \(s_{i-1}+now \ge a_i\) 不成立,则决策堆顶元素并弹出。时间复杂度 \(O(n \log n)\)。
std::priority_queue<int> q;
FOR(i,1,n)(bs[i]=bs[i-1])+=b[i];
int ct = 0,now = 0;
REP(i,n,1){
q.push(b[i]);
if(bs[i-1]+now<a[i].a)now+=q.top(),q.pop(),adx(ct,1);
}
3
考虑 \(dp\)。我们无法用多项式的信息记下大根堆。一个比较自然的想法是钦定一个 \(mi\) 表示 \(\ge mi\) 的元素都钦定在未来某个时刻出队。但是写了之后发现不是重就是漏不然就又重又漏。
考虑本质一点的做法。
从后往前依次考虑每个 \(b_i\) 是否 \(\in S\)。记 \(H = \{b_j|j \text{ 可以自然死亡,但是在考虑未来某个元素时被从大根堆里弹出,因此预先钦定} \in S\}\),\(G = \{b_j|j \text{ 可以自然死亡,并且不存在一个未来元素需要将其从大根堆里弹出,因此钦定从头到尾} \notin S\}\)。
如果 \(b_i\) 不能自然死亡,并且后缀中 \(\ge b_i\) 都被击杀/钦定以后击杀了,那么只能当场击杀 \(b_i\);否则 \(b_i\) 能自然死亡,我们需要考虑 \(b_i \in H\) 和 \(b_i \in G\) 的情况。注意到保证 \(H\) 的最小值 \(\ge\) \(G\) 的最大值,我们似乎可以任意决策。
但是这里有个小问题:如果有多个 \(b_j\) 相等,且其中一部分 \(\in S\) 另一部分 \(\in G\),如果直接套用上述条件,在钦定集合大小后,仍然可能出现梦到哪个集合加哪个集合的情况,显然会算重。因此,对于 \(H\) 最小值等于 \(G\) 最大值等于 \(v\) 的情况,需要选择先出现的一段 \(v \in H\),剩下后出现的 \(v \in T\)。
考虑什么时候结算钦定元素。即对于能自然死亡的 \(b_i\),我们选择将其加入 \(G\),并且在 \(H\) 的最小值 \(\ge b_i\) 的前提下,去掉 \(H\) 中的最小值就不能自然死亡时。这就是之前贪心判定时 \(mx \ge b_i\) 的情况。正是为了此时,才有了 \(H\)。这时我们就可以结算并清空 \(H\),准备重新钦定。
那么到这里就可以 \(dp\) 了。
维护七元组 \((i,s,bs,mx,mi,a,b)\) 表示考虑到第 \(i\) 位,钦定 \(s_{i-1} = s\),后缀 \([i,n]\) 中钦定以后击杀/当场击杀元素 \(\sum b_i = bs\),自由选择的元素中(即被考虑到时满足式子)选择不被击杀的元素的最大值为 \(mx\),被钦定以后击杀的元素最小值为 \(mi\),攻击次数总和为 \(a\),方案数为 \(b\)。
转移看代码。
#include <cstdio>
#include <cstdlib>
#include <cctype>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <queue>
#define FOR(i,a,b) for(int i = (a);i <= (b);++i)
#define REP(i,a,b) for(int i = (a);i >= (b);--i)
static char stkk[200];
template<typename T>inline void output(T x){
if(!x)return putchar('0'),void();
if(x<0)x = ~x+1,putchar('-');
int top = 0;
for(;x;stkk[++top]=x%10^48,x/=10);
for(;top;putchar(stkk[top--]));
}
template<typename T>inline void readx(T &x){
x = 0;int y = 1;char c = getchar();
for(;c<48||c>58;c = getchar())if(c=='-')y = -1;
for(;c>=48&&c<=58;c = getchar())x = (x<<1)+(x<<3)+(c^48);
x *= y;
}
const int N = 22,KM = 301,G = 17,nf = 1e9+10;
static int n,m,M;
inline void adx(int &x,int y){
if((x+=y)>=M)x-=M;
}
struct node{
int a,b;//a = sum ct,b = sum num
node& operator+=(const node& A){
adx(a,A.a),adx(b,A.b);
return *this;
}
node& operator*=(const node& A){
adx(a,A.a),adx(a,A.b),adx(b,A.b);
return *this;
}
bool noten()const{
return a||b;
}
void clear(){
a = b = 0;
}
}f[2][20*15+1][20*15+1][17][17];
struct node2{
int a,l,r;
bool operator<(const node2& A)const{
return a<A.a;
}
}a[N];
static int sl[N],sr[N];
inline void ckmax(int &x,int y){
x<y&&(x=y);
}
signed main(){
readx(n),readx(m),readx(M);
FOR(i,1,n)readx(a[i].a),readx(a[i].l),readx(a[i].r);
std::sort(a+1,a+1+n);
FOR(i,1,n)(sl[i]=sl[i-1])+=a[i].l,(sr[i]=sr[i-1])+=a[i].r;
// printf("a::");FOR(i,1,n)printf("%d ",a[i].a);putchar(10);
// printf("l::");FOR(i,1,n)printf("%d ",a[i].l);putchar(10);
// printf("r::");FOR(i,1,n)printf("%d ",a[i].r);putchar(10);
int now = 1,pre = 0;
FOR(s,sl[n],sr[n])f[1][s][0][0][m+1] = (node){0,1};
REP(i,n,1){
// printf(" i:%d\n",i);
std::swap(now,pre);
FOR(s,sl[i],sr[i])FOR(bs,0,sr[n])FOR(mx,0,m)FOR(mi,0,m+1){
const node &nd = f[pre][s][bs][mx][mi];
if(nd.noten()){
// printf(" s:%d bs:%d mx:%d mi:%d nd.a:%d nd.b:%d\n",s,bs,mx,mi,nd.a,nd.b);
FOR(k,a[i].l,a[i].r){
if(k>s)break;
if(s-k+bs>=a[i].a){
int tmi = s-k+bs-mi<a[i].a?m+1:mi;
if(k<=mi)f[now][s-k][bs][std::max(mx,k)][tmi]+=nd;
if(k>mx)f[now][s-k][bs+k][mx][std::min(k,mi)]*=nd;
}
else{
if(k>mx)f[now][s-k][bs+k][mx][mi]*=nd;
}
}
}
}
FOR(s,0,sr[n])FOR(bs,0,sr[n])FOR(mx,0,m)FOR(mi,0,m+1)
f[pre][s][bs][mx][mi].clear();
}
node ans = (node){0,0};
FOR(bs,0,sr[n])FOR(mx,0,m)
ans+=f[now][0][bs][mx][m+1];
output(ans.a),putchar(10);
return 0;
}

浙公网安备 33010602011771号