题解:CF2048F Kevin and Math Class
题目传送门
题目大意
给出两个序列
解题思路
我们注意到一个性质,对于
那么该怎么预处理出每个
现在我们考虑该怎么动规,由于
第一类是节点为叶子节点,这种情况最好处理,我们只用处理自己这个节点的
if(!l[u]&&!r[u]){
dp[u][0]=a[u];
for(int i=1;i<=63;i++)dp[u][i]=dp[u][i-1]/b[u];
return ;
}
第二类是只有一个儿子的节点,这种情况我们先处理它和它儿子之间的大小关系,然后再根据处理后的数组进行转移。就相当于先算出它儿子自己要先操作多少次再和它一起操作多少次。
if(!l[u]||!r[u]){
ll v=l[u]?l[u]:r[u];
dfs(v);
for(int i=0;i<=63;i++)dp[u][i]=max(dp[v][i],a[u]);
for(int i=1;i<=63;i++)dp[u][i]=min(dp[u][i],dp[u][i-1]/b[u]);
return ;
}
最后一种情况就是这个节点有两个节点,那么就要算出左边的儿子要自己操作多少次,右边的要自己操作多少次再和自己一起操作。这里可以用一个类似背包的思路去计算。
dfs(l[u]),dfs(r[u]);
for(int i=0;i<=63;i++)
for(int j=0;j<=i;j++)
dp[u][i]=min(dp[u][i],max(a[u],max(dp[l[u]][j],dp[r[u]][i-j])));
for(int i=1;i<=63;i++)dp[u][i]=min(dp[u][i],dp[u][i-1]/b[u]);
最后记得多测清空。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=201010;
ll n,a[N],b[N],st[N],l[N],r[N],dp[N][64],top;
void dfs(ll u){
if(!l[u]&&!r[u]){
dp[u][0]=a[u];
for(int i=1;i<=63;i++)dp[u][i]=dp[u][i-1]/b[u];
return ;
}
if(!l[u]||!r[u]){
ll v=l[u]?l[u]:r[u];
dfs(v);
for(int i=0;i<=63;i++)dp[u][i]=max(dp[v][i],a[u]);
for(int i=1;i<=63;i++)dp[u][i]=min(dp[u][i],dp[u][i-1]/b[u]);
return ;
}
dfs(l[u]),dfs(r[u]);
for(int i=0;i<=63;i++)
for(int j=0;j<=i;j++)
dp[u][i]=min(dp[u][i],max(a[u],max(dp[l[u]][j],dp[r[u]][i-j])));
for(int i=1;i<=63;i++)dp[u][i]=min(dp[u][i],dp[u][i-1]/b[u]);
return ;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
ll T;
cin>>T;
while(T--){
top=0;
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i],a[i]--;
for(int i=1;i<=n;i++)cin>>b[i],l[i]=r[i]=0;
st[++top]=1;
for(int i=2;i<=n;i++){
while(top&&b[i]<b[st[top]])l[i]=st[top],top--;
if(top)r[st[top]]=i;
st[++top]=i;
}
for(int i=1;i<=n;i++)for(int j=0;j<=63;j++)dp[i][j]=1e18;
dfs(st[1]);
for(int i=0;i<=63;i++){
if(!dp[st[1]][i]){
cout<<i<<"\n";
break;
}
}
}
return 0;
}