Spark _Exam_ 20240716

Spark Exam 20240716

Conclusion

DP 和 DP 思想其实是很常用的,如果要算的话,是不是说今天又出了 4 道 DP 呢?

不够理想,虽然是rnk1,但是跟上一届差距太大了。属于是拼死拼活骗分没干过熊猫。

其实如果来看 ABC 都不是很难。理想得分330

刚刚跟 Grimgod 讨论了一下,感觉挺有收获(sto%%%Grimgod%%%orz)

让我们开始分析。

A. 仙客来 (a)

Statement

给定一个只含 \(+,-\) 的正整数算式,请随意在算式里添加括号,使得该式结果最大。

Solution

为啥添加括号可以使得结果变大?因为添加括号再拆掉括号这个过程,如果括号前面是 \(-\) ,就会使得里面的符号变号。那么天真的想法是,把所有 \(-\) 的式子全部拿出来打括号,但是遇到下面这个例子就错了:

\[100 - 99 +1 - 200 \]

显然把中间的 \(+1\) 一起打上括号更优,那么是不是只要打上括号更优就可以了呢?

那么遇到下面这个例子呢?

\[\begin{aligned} &100-99-1+500-200+500-200\\ &\to 100-(99-(1+500)-(200+500)-200)\\ &=100-99+1+500+200+200+500+200 \end{aligned} \]

所以,打几层括号就会变几次,那么无非打一次或者两次或者不打。那么这个时候设 \(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)

posted @ 2024-07-16 22:05  haozexu  阅读(17)  评论(0)    收藏  举报