首页 > 技术文章 > 洛谷2300 合并神犇

Le-mon 2018-09-07 11:37 原文

题目传送门

一句话题意

给你一个数列,每次可以把相邻两个数合并,求把这个数列变成不下降序列最少需要的操作次数。

Solution

因为洛谷数据比较水,所以这个题目有两种做法:
1.\(O(n^2)\):直接DP,f[i]表示前 i 个数最少合并的次数。g[i]表示前 i 个数在满足合并了f[i]次的条件下最后一组的总和最小是多少,sum[i]是前缀和。
转移的话直接枚举i是从前面哪一次转移过来就行了,\(n^2\)过2e5是真的骚,但是由于数列随机生成,几个数之和很容易会超过g[j],所以差不多第二重循环只会循环几次,差不多相当于常数,详情请看代码。
2.\(O(n)\):这种方法需要使用单调队列因为很明显上一种方法中g数组具有单调性,所以可以用单调队列维护。

Coding

\(O(n^2)\):

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6;
long long p[N],f[N],sum[N],Min[N];
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&p[i]);
        sum[i]=sum[i-1]+p[i];
    }
    for(int i=1;i<=n;i++)
    {
        int j;
    	for(j=i-1;j>=0;j--)
    		if(sum[i]-sum[j]>=Min[j]) break;
    		f[i]=f[j]+i-j-1;
    		Min[i]=sum[i]-sum[j];
    }
    cout<<f[n];
    return 0;
}

\(O(n)\):

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long lol;
lol sum[1000001],pre[1000001],f[1000001];
int q[1000001],head,tail,n;
int main()
{int i;
  cin>>n;
  for (i=1;i<=n;i++)
    {
      scanf("%lld",&sum[i]);
      sum[i]+=sum[i-1];
    }
  head=0;tail=1;
  q[0]=0;
  for (i=1;i<=n;i++)
    {
      while (head+1<tail&&sum[i]>=sum[q[head+1]]+pre[q[head+1]]) head++;
      f[i]=f[q[head]]+1;
      pre[i]=sum[i]-sum[q[head]];
      //cout<<q[head]<<endl;
      while (head<tail&&sum[q[tail-1]]+pre[q[tail-1]]>sum[i]+pre[i]) tail--;
      q[tail++]=i;
    }
  cout<<n-f[n];
}

推荐阅读