CF1066D Boxes Packing 题解

题意简化:

N N N个数,第i个数为 a i a_{i} ai,你需要选出至多 M M M个子序列,使得每一个子序列的和都<=k,并且这些子序列在原序列里面必须连续且靠右。问能选出多少个数。

1.暴力

我们可以枚举一个数 i i i(1<= i i i<= N N N),表示前i个数不选,选第 i + 1 i+1 i+1到第 N N N个数,再利用如下的代码进行判断。如果返回true,那么输出 n − i n-i ni。时间复杂度 O ( N 2 ) O({N}^{2}) O(N2)

bool check(int i)
{
 //从第i+1个数选到第N个数
 //tmp表示当前选的数的和
 //sum表示已经选了多少个子序列 
 int tmp=0,sum=0;
 for(int j=i+1;j<=n;j++)
 {
  if(tmp+a[j]>=k)//如果当前子序列的和>=k 
  {
   sum++;//进入下一个子序列 
   tmp=a[j];//把这个子序列的初始和记为a[j] 
  }
  else tmp+=a[j];//否则,就可以选第j个数,把当前子序列的和加上a[j] 
 }
 if(sum>m) return 0;//如果子序列的个数>m,方案不可行,返回false 
 return 1;//否则返回true 
}

当然,一看数据范围:1<= N N N<= 2 ∗ 1 0 5 {2*10^{5}} 2105,就知道 O ( N 2 ) O({N}^{2}) O(N2)肯定要炸。所以我们需要一种更快的方法。

2.线性

这道题正着做时间复杂度要爆炸。

我们数学老师说过一句话:“正若难,则反。”

所以我们可以反着做。

从第 N N N个数开始倒推(设当前的数为 t t t),把第 t t t个数加入当前这个子序列,如果第 t t t个数加入后子序列>= M M M个,那么前 t t t个数就是要抛弃的数。当然如果数字选完了也要退出。时间复杂度为 O ( N ) O(N) O(N),足够通过本题。

代码如下:

#include
using namespace std;
int n,m,k,a[200000+10],sum,t;
int box[200000+10];
int main()
{
 scanf("%d%d%d",&n,&m,&k);
 for(int i=1;i<=n;i++) scanf("%d",&a[i]);
 for(int i=1;i<=n;i++) sum+=a[i];
 t=n;//将t初始化为$N$ 
 while(m!=0&&t!=0)//当子序列还没有用尽或者是数字还没有选完的时候 
 {
  box[m]+=a[t];//将第M个子序列的和加上a[t] 
  if(box[m]>k)//如果第M个子序列的和>k,切换到下一个子序列 
  {
   m--;//下一个子序列 
   box[m]=a[t];//初始化新子序列的和=a[t] 
  }
  t--;//指向下一个数 
 }
 if(m==0) t++;//如果子序列选完了,那么第t个数就不能加入子序列里面,需要t++,因为第t+1个数是可以放进子序列 
 printf("%d\n",n-t);//输出选择的个数 
 return 0;
}

你可能感兴趣的:(题解)