洛谷 P2389 电脑班的裁员 解题报告
題意:
給定一段長為N的序列,選取其中的至多M段使這些子段和最大。
當N=1000時,我們可以采用動態規劃解法
令\(dp[i][j][k]\)代表當前選至位置\(i\)處于第\(j\)段當前是否選取(1選0不選)
則轉移為
\(dp[i][j][0]=max(dp[i-1][j][1],dp[i-1][j][0])\)
\(dp[i][j][1]=max(dp[i-1][j-1][0],dp[i-1][j][1])+score[i]\)
其中\(i\)的一維可以滾動掉
Code:
#include <cstdio> #include <cstring> int max(int x,int y){return x>y?x:y;} const int N=503; const int inf=0x3f3f3f3f; int dp[N][N][2],n,k,score[N]; void init() {scanf("%d%d",&n,&k);for(int i=1;i<=n;i++)scanf("%d",score+i);memset(dp,-0x3f,sizeof(dp));dp[1][0][0]=0,dp[1][1][1]=score[1]; } void work() {for(int i=2;i<=n;i++)for(int j=0;j<=k;j++){dp[i][j][0]=max(dp[i-1][j][1],dp[i-1][j][0]);dp[i][j][1]=max(dp[i-1][j-1][0],dp[i-1][j][1])+score[i];}int ans=-inf;for(int i=0;i<=k;i++)ans=max(ans,max(dp[n][i][0],dp[n][i][1]));printf("%d\n",ans); } int main() {init();work();return 0; }當N=100,000時,我們可以采用貪心解法
注意到每次選取時,一段連續的正數或者連續的負數,要么我們不取選它,要么全部選走。
我們先縮個點,使序列變成正負數交錯的,其中,第一項和最后一項的負數我們一定不去選擇,故舍去。
設當前有\(k\)個正數。
則當\(M>=k\)時,直接選取\(k\)個正數即可。
當\(M<=k\)時,對每個正數,我們有兩種選擇,放棄它,或者跨過負數選擇它。
則當選中一個負數\(a\)時,我們損失了\(-a\),當放棄一個正數\(b\)時,我們損失了\(b\)
則不管哪種情況,我們只需要找到絕對值最小的一個數,選擇它或者放棄它
采用二叉堆維護這個最小值
當這個最小值被操作時,其實等價于新產生了一個權值為 它和它旁邊的兩個元素的權值和 的新元素,這時候產生的新數列一定會少一個正數
值得一提的是,當第一項和最后一項是負數的時候,不一定一定減少一個正數,所以我們根據貪心不去選擇這些點即可
合并元素可以采用鏈表進行維護
Code:(實在是不好看QAQ)
#include <cstdio> #include <cstring> #include <iostream> #include <queue> #define ll long long #define P pair <ll,ll> using namespace std; const int N=1000010; ll a[N],b[N],n0,k,n,pre[N],suc[N],cnt,used[N]; ll labs(ll x){return x>0?x:-x;} void init() {scanf("%lld%lld",&n0,&k);for(int i=1;i<=n0;i++)scanf("%lld",a+i);int k=0;a[0]=-1;while(a[k]<0) k++;for(int i=k;i<=n0;i++){if(a[i]*a[i-1]>0)b[n]+=a[i];elseb[++n]=a[i];}while(n&&b[n]<0) n--;for(int i=1;i<=n;i++){pre[i]=i-1;suc[i]=i+1;}suc[0]=1;suc[n]=0; } priority_queue <P,vector <P >,greater <P > > q; P p; void work() {for(int i=1;i<=n;i++){if(b[i]>0) cnt++;p.first=labs(b[i]);p.second=i;q.push(p);}while(cnt>k){int pos=q.top().second;q.pop();if(used[pos]) continue;if(!suc[pos]&&b[pos]<0){suc[pre[pos]]=suc[pos];pre[suc[pos]]=pre[pos];continue;}if(!pre[pos]&&b[pos]<0){pre[suc[pos]]=pre[pos];suc[pre[pos]]=suc[pos];continue;}used[pre[pos]]=used[suc[pos]]=1;b[pos]+=b[pre[pos]]+b[suc[pos]];cnt--;if(pre[pos]){pre[pos]=pre[pre[pos]];suc[pre[pos]]=pos;}if(suc[pos]){suc[pos]=suc[suc[pos]];pre[suc[pos]]=pos;}p.first=labs(b[pos]),p.second=pos;q.push(p);}int now=suc[0];ll ans=0;while(now){if(b[now]>0) ans+=b[now];now=suc[now];}printf("%lld\n",ans); } int main() {init();work();return 0; }當N=1,000,000時,我們可以將算法近似優化到\(O(N)\)
具體大家可以參考出題人的題解
我在這里解釋一下這個近似,原題解沒有說道找到第\(k\)值的問題
主要就是利用基于快速排序思想的STL函數\(nth\_element\),可以在近似\(O(n)\)的復雜度找到第\(k\)值
關于第k值
代碼我沒寫,感覺不太好寫
轉載于:https://www.cnblogs.com/butterflydew/p/9280322.html
總結
以上是生活随笔為你收集整理的洛谷 P2389 电脑班的裁员 解题报告的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 无人驾驶汽车之争本田为何未战先败
- 下一篇: spring framework源码下载