Spark _Exam_ 20240716
Spark Exam 20240716
Conclusion
DP 和 DP 思想其实是很常用的,如果要算的话,是不是说今天又出了 4 道 DP 呢?
不够理想,虽然是rnk1,但是跟上一届差距太大了。属于是拼死拼活骗分没干过熊猫。
其实如果来看 ABC 都不是很难。理想得分330
刚刚跟 Grimgod 讨论了一下,感觉挺有收获(sto%%%Grimgod%%%orz)
让我们开始分析。
A. 仙客来 (a)
Statement
给定一个只含 \(+,-\) 的正整数算式,请随意在算式里添加括号,使得该式结果最大。
Solution
为啥添加括号可以使得结果变大?因为添加括号再拆掉括号这个过程,如果括号前面是 \(-\) ,就会使得里面的符号变号。那么天真的想法是,把所有 \(-\) 的式子全部拿出来打括号,但是遇到下面这个例子就错了:
显然把中间的 \(+1\) 一起打上括号更优,那么是不是只要打上括号更优就可以了呢?
那么遇到下面这个例子呢?
所以,打几层括号就会变几次,那么无非打一次或者两次或者不打。那么这个时候设 \(f_{i,0/1/2}\) 就可以转移了。
但是本人愚钝,没有这么做:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
typedef long long ll;
ll n,a[N];
typedef pair<ll,ll> pll;
#define MP(x,y) make_pair(x,y)
#define sum(x) (x.first)
#define head(x) (x.second)
#define meta(x) (x.first)
#define typ(x) (x.first)
#define len(x) (x.second)
#define dat(x) (x.second)
deque<pair<pair<char,ll>,pll> > d;
int main(){
cin>>n;
cin>>a[1];
char type='+';ll sum=a[1],hd=a[1],l=1;
for(int i=2;i<=n;i++){
char op;int x;
cin>>op>>x;
if(op!=type)
d.emplace_back(MP(MP(type,l),MP(sum,hd))),
sum=(op=='-'?-x:x),hd=x,type=op,l=1;
else sum+=(op=='-'?-x:x),l++;
}
d.emplace_back(MP(MP(type,l),MP(sum,hd)));
ll ans=0;
while(!d.empty()){
auto ln=d.front();
if(typ(meta(ln))=='+'){
ans+=sum(dat(ln));
d.pop_front();
continue;
}
ll res=-sum(dat(ln))-2ll*head(dat(ln)),tot=1;
for(int i=1,j=2;i<d.size() and j<d.size();i+=2,j+=2){
ll mg=0;
if(len(meta(d[i-1]))>=2 or i-1>0){
mg=sum(dat(d[i]))-sum(dat(d[j]));
}else mg=-sum(dat(d[i]))-sum(dat(d[j]));
ll sj=sum(dat(d[i]))-sum(dat(d[j]))-2ll*head(dat(d[j]));
if(mg>sj) res+=mg,tot+=2;
else break;
}
ans+=res;
while(tot--) d.pop_front();
}
cout<<ans;
}
也是成功切掉。
B. 白兰花 (b)
Statement
给定无向图 \(G\) ,对于路径 \(p(v_1,v_2,v_3,\dots,v_k)\) ,定义权值 \(f(p)=\sum w(v_i,v_{i+1})-\max w(v_i,v_{i+1})+\min w(v_i,v_{i+1})\)
对于所有 \(i\in(1,n]\) ,求 \(\min_{p(1\to i)} f(p)\) 。
\(|V|,|E|\le 2\times 10^5,0<|w(u,v)\in E|\le 10^9\)
Solution
这个权值的意思就是去掉最大边,换成最小边。可以把它拆成两个操作,一个是去掉一条边,一个是算两次一条边。这样,考虑直接的 dp,就是保存当前去掉的最大值是多少,以及算两次的最小值是多少。这样,状态就太多了。
但是这个存储的最大值和最小值好像没什么用。因为对于一条路径,这个存储的去掉和保留都必须是路径的极值才会保证最小,否则是不可能是最小的,换句话说这个值是多少应该是确定的,现在只是不确定路径。
这样这个条件肯定在迭代的时候就会自动满足。
所以考虑 \(f_{i,0/1,0/1}\) 表示当时到 \(i\) 是否选了去除、重复的边。只要是最优方案,计算出的值必然是合法的权值。
C. 月见草 (c)
Statement
若干个人,要求把这些人分成若干组,组之间是不可区分的,人与人之间是可以区分的,组的内部是无序的,并对于第 \(i\) 个人,要求他所在的组人数必须不超过 \(a_i\) ,求方案数。
\(1\le n\le 3000\)
Solution
先考虑打暴力,那么就是枚举拆分数。为了使得组与组之间不可区分,就应该限定后一个拆分数必须大于前一个拆分数(钦定一个顺序以避免重复计算)。考虑怎么把人分进去,由于人之间有一些限制,而且已经知道每一组具体要分多少人,这样,有些人就必须在大小较小的组内部,其他人(指阈值比较高)的人可以随便放,否则,这个方案就不合法。
发现每个人能够进入的组都是一个前缀,考虑 DP 及时放入这些人,发现如果知道已经放了 \(i\) 个人,又知道有多少人的大小限制是必须放在前面的,就能够算出使用了多少个后面心胸比较宽广的人,就知道当前有哪些人能够放进来,这个使用一个组合数计算即可。
看起来复杂度是 \(\mathcal O(n^3)\) 的呢。
但是发现还有很多情况都是不行的,比如当前组大小为 \(j\) ,可是剩下的人数已经不够了,显然非法。排除这些情况后,对于状态 \((i,j)\) 表示已经放了 \(i\) 个人,当前组大小为 \(j\) ——它的后继状态为 \(\mathcal O(\frac{n-i}j)\) 个 ,那么总共状态数是调和级数级别,总共复杂度 \(\mathcal O(n^2\log n)\)
D. 茉莉花 (d)
本文来自博客园,作者:haozexu,转载请注明原文链接:https://www.cnblogs.com/haozexu/p/18306227

浙公网安备 33010602011771号