CF 1334(edu85) F. Strange Function(线段树,dp)
Posted fzuzyz
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CF 1334(edu85) F. Strange Function(线段树,dp)相关的知识,希望对你有一定的参考价值。
链接:https://codeforces.com/contest/1334/problem/F
题意:定义函数f,对于一个含n个元素的数组arr,首先有一个空的数组c,按顺序对arr数组元素进行操作,若ai大于c数组所有元素,则将arr[i]加入c数组末尾,最后得到的数组c=f(arr)。现在给你一个数组a,长度为n,一个数组b,长度为m,删除a的第i项代价为pi(pi可以为负),求删除a中若干元素,使f(a)=b得最小代价为多少,若没有输出NO。
解题思路:作为edu85的F题,套路和edu84的F题居然一模一样,都是线段数维护dp。不难看出b是个递增序列,答案存在问题其实可以O(n)判断。我们设dp[i][j]为数组a的前i个元素组成的子数组,经过删除后,经过f变换后得到的数组的为b数组前j项时的最小代价。那么转移方程分$a_{i}<b_{j}$,$a_{i}=b_{j}$,$a_{i}>b_{j}$三种情况,若$a_{i}<b_{j}$,第i项元素可以删除也可以不删除,因为该元素不会加入新数组,对结果不产生影响,可以得到$dp[i][j]=dp[i-1][j]+max(0,p_{i})$,而对于$a_{i}>b_{j}$,情况更为简单,$a_{i}$必须删除,因此$dp[i][j]=dp[i-1][j]+p_{i}$,而对于$a_{i}=b_{j}$,情况较为复杂,他可以由dp[i-1][j-1]不删除得到,也可以由dp[i-1][j]进行删除或不删除得到,有三种可能,即$dp[i][j]=min(dp[i-1][j-1]+p_{i},dp[i-1][j],dp[i-1][j]+p_{i})$,因此我们得到了一个时间复杂度为$O(nm)$的dp,但是可以发现,$a_{i}<b_{j}$与$a_{i}>b_{j}$两种情况的j必然是两个连续的区间,并且都有dp[i-1][j]转移,而二者所加的$p_{i}$以及$max(0,p_{i})$也都是常数,可以用线段树维护,而$a_{i}=b_{j}$的情况也只有一个点,单点更新即可。因此直接用线段树进行维护,将二维dp退化为一维的线段树。初始化dp[0]=0,其余为无穷大,随后进行区间修改,时间复杂度$O(nlogm)$
比赛的时候看到这个题几乎是立马发现做法,因为之前的edu84F题看了dls的解说,学会线段树维护dp这个套路,但是只剩10分钟了,没来得及打,有点可惜。
最后贴上AC代码
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef long double ld; typedef unsigned long long ull; typedef pair <ll,ll> pii; #define rep(i,x,y) for(int i=x;i<y;i++) #define rept(i,x,y) for(int i=x;i<=y;i++) #define per(i,x,y) for(int i=x;i>=y;i--) #define all(x) x.begin(),x.end() #define pb push_back #define fi first #define se second #define mes(a,b) memset(a,b,sizeof a) #define mp make_pair #define dd(x) cout<<#x<<"="<<x<<" " #define de(x) cout<<#x<<"="<<x<<" " #define debug() cout<<"I love Miyamizu Mitsuha forever. " const ll inf=1e18; const int maxn=5e5+5; map<ll,ll> pos; ll p[maxn]; ll a[maxn],b[maxn]; class element { public: int l,r; ll plus,val; }; class Tree { public: element tree[maxn<<2]; void build(int id,int l,int r) { tree[id].l=l; tree[id].r=r; tree[id].plus=0; if(l==r) { tree[id].val=inf; return ; } int mid=(l+r)/2; build(id*2,l,mid); build(id*2+1,mid+1,r); } void update(int id,int p,ll num) { if(tree[id].l==p&&tree[id].r==p) { tree[id].val=num; return ; } if(tree[id].l>p||tree[id].r<p) return ; push_down(id); update(id*2,p,num); update(id*2+1,p,num); } void add(int id,int l,int r,ll num) { if(tree[id].l>=l&&tree[id].r<=r) { tree[id].plus+=num; tree[id].val+=num*(tree[id].r-tree[id].l+1); return ; } if(tree[id].l>r||tree[id].r<l) return ; push_down(id); if(tree[id*2].r>=l) add(id*2,l,r,num); if(tree[id*2+1].l<=r) add(id*2+1,l,r,num); } void push_down(int id) { if(tree[id].l!=tree[id].r) rept(j,id*2,id*2+1) { tree[j].val+=tree[id].plus*(tree[j].r-tree[j].l+1); tree[j].plus+=tree[id].plus; } tree[id].plus=0; } ll query(int id,int l,int r) { if(tree[id].l>=l&&tree[id].r<=r) return tree[id].val; if(tree[id].l>r||tree[id].r<l) return 0; ll s=0; push_down(id); if(tree[id*2].r>=l) s+=query(id*2,l,r); if(tree[id*2+1].l<=r) s+=query(id*2+1,l,r); return s; } } dp; int main() { ios::sync_with_stdio(false); cin.tie(0); int n,m; cin>>n; rept(i,1,n) cin>>a[i]; rept(i,1,n) cin>>p[i]; cin>>m; rept(i,1,m) { cin>>b[i]; pos[b[i]]=i; } dp.build(1,0,m); dp.update(1,0,0); rept(i,1,n) { if(pos[a[i]]==0) { if(p[i]<0) { dp.add(1,0,m,p[i]); continue; } ll *pp=lower_bound(b+1,b+1+m,a[i]); ll ppp=pp-b; dp.add(1,0,ppp-1,p[i]); } else { int pp=pos[a[i]]; ll val1=dp.query(1,pp,pp),val2=dp.query(1,pp-1,pp-1); dp.update(1,pp,min(val1+min(0ll,p[i]),val2)); if(pp!=m&&p[i]<0) dp.add(1,pp+1,m,p[i]); dp.add(1,0,pp-1,p[i]); } } ll ans=dp.query(1,m,m); if(ans<=5e14) cout<<"YES "<<ans<<" "; else cout<<"NO "; return 0; }
以上是关于CF 1334(edu85) F. Strange Function(线段树,dp)的主要内容,如果未能解决你的问题,请参考以下文章
Codeforces1539 F. Strange Array(思维,线段树)
Codeforces Round #727 (Div. 2) F. Strange Array(思维,线段树子段和)