1. 程式人生 > 實用技巧 >CodeForces833 B. The Bakery 線段樹維護dp

CodeForces833 B. The Bakery 線段樹維護dp

題目連結:https://vjudge.net/problem/CodeForces-833B


題意:
給長度為n的陣列a,和一個整數k
要求把陣列分成連續的k段,每段的權值是該段中不同數的個數,
輸出最大權值和。
資料範圍:n<=35000,k<=min(n,50),1<=a(i)<=n

題解:
很容易想到暴力dp的方式
dp[i][j]:前1-i個數分成了j個段
那麼dp[i][j]=max(dp[k][j-1]+val[k+1][i]) //val[i][j]表示區間i-j中不同數的個數
這個方法的複雜度就是O(n^2*k),很顯然不可行

那麼我們可以把找max(dp[k][j-1]+val[k+1][i])這個過程使用線段樹優化掉


我們可以從1-n的數分成j段建立一顆線段樹,也就是對dp[x][j](1<=x<=n)建一顆線段樹維護這n個數的最大值
然後對dp[i][j]的求解可以從維護dp[x][j-1]這一顆樹中查詢區間【1,(i-1)】的最大值

但是有一點不對,因為dp[i][j]由dp[k][j-1]和val[k+1][i]兩部分構成,所以只有dp[k][j-1]最大並不一定可以得到
dp[i][j]最大,所以我們就要想辦法處理一下維護dp[x][j]的線段樹,讓線段樹維護這兩部分的和

對於下面的一組數(下標從1開始)
7 8 1 7
第二個7的有效區域是[2,4],那麼我們可以在原有維護dp[x][j]的線段樹基礎上,線段樹在[2,4]這個區間的值都加1


這樣的話就相當於讓線段樹維護了兩部分的和



/*
題意:
給長度為n的陣列a,和一個整數k
要求把陣列分成連續的k段,每段的權值是該段中不同數的個數,
輸出最大權值和。
資料範圍:n<=35000,k<=min(n,50),1<=a(i)<=n

題解:
很容易想到暴力dp的方式
dp[i][j]:前1-i個數分成了j個段
那麼dp[i][j]=max(dp[k][j-1]+val[k+1][i]) //val[i][j]表示區間i-j中不同數的個數
這個方法的複雜度就是O(n^2*k),很顯然不可行

那麼我們可以把找max(dp[k][j-1]+val[k+1][i])這個過程使用線段樹優化掉
我們可以從1-n的數分成j段建立一顆線段樹,也就是對dp[x][j](1<=x<=n)建一顆線段樹維護這n個數的最大值
然後對dp[i][j]的求解可以從維護dp[x][j-1]這一顆樹中查詢區間【1,(i-1)】的最大值

但是有一點不對,因為dp[i][j]由dp[k][j-1]和val[k+1][i]兩部分構成,所以只有dp[k][j-1]最大並不一定可以得到
dp[i][j]最大,所以我們就要想辦法處理一下維護dp[x][j]的線段樹,讓線段樹維護這兩部分的和

對於下面的一組數(下標從1開始)
7 8 1 7
第二個7的有效區域是[2,4],那麼我們可以在原有維護dp[x][j]的線段樹基礎上,線段樹在[2,4]這個區間的值都加1
這樣的話就相當於讓線段樹維護了兩部分的和

*/ #include <cstdio> #include <cstring> #include <iostream> #include<algorithm> #include <queue> #include <map> using namespace std; typedef long long ll; const int maxn=5e4+10; const int mod=1000000007; const int INF=0x3f3f3f3f; const long long ll_INF=0x3f3f3f3f3f3f3f3fll; int tree[maxn<<2],dp[maxn][55],v[maxn],pre[maxn],mark[maxn],lazy[maxn<<2]; void push_up(int rt) { tree[rt]=max(tree[rt<<1],tree[rt<<1|1]); } void build(int rt,int L,int R,int x) { tree[rt]=lazy[rt]=0; if(L==R) { //對於dp[j][i]的值由dp[k][i]+val[k+1][j]得到,且要保證k<j,所以用dp[L-1][x-1]來給第L位置賦值 tree[rt]=dp[L-1][x-1]; //給n個節點賦初始值 return; } int mid=(L+R)>>1; build(rt<<1,L,mid,x); build(rt<<1|1,mid+1,R,x); push_up(rt); } void push_down(int rt) { if(lazy[rt]) { lazy[rt<<1]+=lazy[rt]; //上一個節點的lazy值儲存的是它子節點的偏移量 lazy[rt<<1|1]+=lazy[rt]; tree[rt<<1]+=lazy[rt]; tree[rt<<1|1]+=lazy[rt]; lazy[rt]=0; } } void update(int rt,int L,int R,int LL,int RR) { if(LL<=L && RR>=R) { lazy[rt]++; tree[rt]++; return ; } push_down(rt); int mid=(L+R)/2; if(LL<=mid)update(rt<<1,L,mid,LL,RR); if(RR>mid)update(rt<<1|1,mid+1,R,LL,RR); push_up(rt); } int query(int rt,int L,int R,int LL,int RR) { if(LL<=L && RR>=R) { return tree[rt]; } push_down(rt); int mid=(L+R)>>1,ans=0; if(LL<=mid) ans=max(ans,query(rt<<1,L,mid,LL,RR)); if(RR>mid) ans=max(ans,query(rt<<1|1,mid+1,R,LL,RR)); return ans; } int main() { int n,k; scanf("%d%d",&n,&k); for(int i=1; i<=n; ++i) scanf("%d",&v[i]); for(int i=1; i<=n; ++i) { pre[i]=mark[v[i]]+1; mark[v[i]]=i; } for(int i=1; i<=k; ++i) { build(1,1,n,i); for(int j=1; j<=n; ++j) { update(1,1,n,pre[j],j); dp[j][i]=query(1,1,n,1,j); } } printf("%d\n",dp[n][k]); return 0; }