梦熊联盟 -- 云智计划 -- 模拟赛 #8 div2:T1/T2/T4

A. 斯堪的纳维亚(Scandinavia)

题目大意

给你一个只有 \(123\) 的数组 \(a\),让你找最左边是 \(1\) 最右边是 \(3\),最终中间全部都是 \(2\)\(2\) 的个数至少为一有多少种方案?

解法

注意到是求方案数可以大致可以想到应该是DP。设 \(dp_{i,1/2/3}\) 表示从 \(1\)\(i\) ,目前最后一个数是 \(1/2/3\) 有多少种方案。可以得到转移方程:

\(dp_{i,1}=dp_{i-1,1}+(a_i==1)\)

\(dp_{i,2}=dp{i-1,2}+(a_i==2) \times (dp_{i-1,1}+dp_{i-1,2})\)

\(dp_{i,3}=dp_{i-1,3}+(a_i==3) \times (dp_{i-1,2})\)

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e6+5,mod=1e9+7;
int dp[maxn][3],a[maxn];
signed main()
{
	freopen("Scandinavia.in","r",stdin);
	freopen("Scandinavia.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int n;cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	for(int i=1;i<=n;i++)
	{
		dp[i][1]=dp[i-1][1]+(a[i]==1);
		dp[i][2]=dp[i-1][2]+(a[i]==2)*(dp[i-1][1]+dp[i-1][2]);
		dp[i][3]=dp[i-1][3]+(a[i]==3)*dp[i-1][2];
		dp[i][1]%=mod,dp[i][2]%=mod,dp[i][3]%=mod;
	}
	cout<<dp[n][3];
	return 0;
}

B. 丰饶之神(Freyr)

题目大意

给你一棵树,树上每个节点代表一个水坝。根据当前水坝的储水量及这个水坝的最大储水量,一旦大于等于最大储水量,整个就会崩溃。所有水像根节点流动。现在问你要将最根节点上面的水坝冲垮至少需要降多少水?(你只能选一个点,但是你降水的量是无限的)

解法

因为这一格的水满了之后就会流到他的上一个节点,再加上每一个节点本身它就有积水。所以我们可以设当前我们要降雨的格子为 \(x\)。设最强大的水坝的编号为 \(y\)(也可以是根结点的那个水坝)。
现在我们要把 \(y\) 冲垮。这样水就可以流到根节点了,而从垮塌的最小代价为:

\(\max(c_y-\sum_{p\in path(x,0)}u_p\)其中 \(path(x,0)\) 表示从 \(x\) 走到根节点,另外:\(u_0=0\)

设:\(s_i=\sum_{p\in path(x,0)}u_p\),那么刚才的 \(c_y-\sum_{p\in path(x,0)}u_p\) 就可以变为 \(c_y+sum_{d_y}-sum_x\),而\(c_y+sum_{d_y}\)是在 \(y\) 时就可以算出来的。所以设 \(\max\{c_y+sum_{d_y}\}\)\(f_x\)。那么上一段的式子就可以变为:

\(\max(f_x-s_x,c_x-u_i)\)

对其取 \(\min\) 即可。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5;
int c[maxn],u[maxn],dp[maxn],f[maxn],ans=1e18+5;//dp是上面的s
vector<int> g[maxn];
void dfs(int x,int fa)
{
	dp[x]=dp[fa]+u[x];
	f[x]=max(f[fa],dp[fa]+c[x]);
	ans=min(ans,max(c[x]-u[x],f[x]-dp[x]));
	for(auto to:g[x]) dfs(to,x);
}
signed main()
{
	freopen("Freyr.in","r",stdin);
	freopen("Freyr.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int n;cin>>n>>c[0];
	for(int i=1;i<=n;i++)
	{
		int fa;cin>>fa>>c[i]>>u[i];
		g[fa].push_back(i);
	}
	dfs(0,0);
	cout<<ans;
	return 0;
}

D. 负重越野(CrossCountry)

题目大意

给你N个人的数据包括体重及速度。一个人可以背另外一个人,但是背起的人不能背别人,一个人也只能背一个人。当人 \(i\) 背起人 \(j\) 时速度会发生如下变化。

  1. \(w_i\) 大于 \(w_j\):不发生任何变化。
  2. 否则速度将会变化成:\(v_i+w_i-w_j\)。如果变为负数,则说明\(i\)背不起 \(j\)

求最慢的人最大速度是多少?

解法

到最慢的最大,秒选二分。

那么二分肯定就是二分最慢的人最大速度,为 \(x\)

现在就可以把所有人分成两类,速度小于\(x\)的和速度大于等于 \(x\) 的。现在问题就变成了如何将这些第一类人分配给第二类人?

现在这样子就是一道经典的贪心问题。设目前找到的第二类人为 \(i\)。找到的第一类人为 \(j\)。则他只需要满足条件 \(w_j+v_j\ge w_i+x\),然后再分配过去即可。

这样子的话我们可以用二分,时间复杂度为: \(O(n\log^2n)\)。然而这道题可以使用双指针,时间复杂度为:\(O(n\log n)\)

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e5+5;
int a[maxn],b[maxn],id1[maxn],id2[maxn],n;
bool check(int x)
{
	int now=1;
	for(int i=1;i<=n;i++)
	{
		if(a[id1[i]]>=x) continue;
		while(now<=n&&(a[id2[now]]<x||a[id2[now]]+b[id2[now]]-b[id1[i]]<x)) now++;
		if(now>n) return 0;
		now++;
	}
	return 1;
}
signed main()
{
	freopen("CrossCountry.in","r",stdin);
	freopen("CrossCountry.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int t;cin>>t;
	while(t--)
	{
		cin>>n;
		for(int i=1;i<=n;i++) cin>>a[i]>>b[i],id1[i]=id2[i]=i;
		sort(id1+1,id1+1+n,[&](int x,int y){ return b[x]<b[y]; });
		sort(id2+1,id2+1+n,[&](int x,int y){ return a[x]+b[x]<a[y]+b[y]; });
		int l=1,r=1e9,ans=-1;
		while(l<=r)
		{
			int mid=(l+r)>>1;
			if(check(mid)) ans=mid,l=mid+1;
			else r=mid-1;
		}
		cout<<ans<<'\n';
	}
	return 0;
}
posted @ 2025-02-07 21:08  Engle_Chen  阅读(15)  评论(0编辑  收藏  举报