梦熊联盟 -- 云智计划 -- 模拟赛 #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\) 时速度会发生如下变化。
- 若 \(w_i\) 大于 \(w_j\):不发生任何变化。
- 否则速度将会变化成:\(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;
}