洛谷2300 合并神犇
一句话题意
给你一个数列,每次可以把相邻两个数合并,求把这个数列变成不下降序列最少需要的操作次数。
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];
}