【C++心路历程25】课堂讲义【dp加单调队列】

    xiaoxiao2021-03-25  94

    【问题描述】

      高二数学《课堂讲义》总共有n道题目要抄,编号1..n,抄每道题所花时间不一样,抄第i题要花 a[i] 分钟。由于 xxx还要准备IOI,显然不能成天写课堂讲义。xxx决定只用不超过 t 分钟时间抄这个,因此必然有空着的题。每道题要么不写,要么抄完,不能写一半。一段连续的空题称为一个空题段,它的长度就是所包含的题目数。这样应付自然会引起x老师的愤怒。x老师发怒的程度(简称发怒度)等于最长的空题段长度。

      现在,xxx想知道他在这 t 分钟内写哪些题,才能够尽量降低x老师的发怒度。由于xxx很聪明,你只要告诉他发怒度的数值就可以了,不需输出方案。

    【输入格式】

      第一行为两个整数n,t,代表共有n道题目,t分钟时间。   以下一行,为n个整数,依次为a[1], a[2],… a[n],意义如上所述。

    【输出格式】

      仅一行,一个整数w,为最低的发怒度。

    【输入样例】

    17 11 6 4 5 2 5 3 4 5 2 3 4 5 2 3 6 3 5

    【输出样例】

    3

    【样例解释】

      分别写第4,6,10,14题,共用时 2+3+3+3=11 分钟。空题段:1-3(长为3),5-5(长为1),7-9(长为3),11-13(长为3),15-17(长为3 )。   所以发怒度为3。可以证明,此数据中不存在使得发怒度<=2的作法。

    【数据范围】

    30%数据 n<=2000,0

    void solve() { int x=1,y=n,ans=n; while(x<=y) { int m=(x+y)/2; run(m); long long tmp=//空题段不超过m的情况下,完成题目所需的最小时间; if(tmp>t)//完成不了 x=m+1; else { y=m-1; ans=m; } } printf("%d",ans); }

    现在的关键是处理 tmp,即空题段不超过m的情况下,完成题目所需的最小时间; 这里可以采用dp 方程: //f(i)表示在前i道题中必选择第i道题做 && 最小空题段数不超过m时 的最小时间 //f(i)=min{f(j) || i-m-1<=j<=i-1}+a[i]

    朴素的dp:

    //solve30 //f(i)表示在前i道题中必选择第i道题做 && 最小空题段数不超过m时 的最小时间 //f(i)=min{f(j) || i-m-1<=j<=i-1}+a[i] #include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #include<queue> using namespace std; const long long inf=40000000000000005; int n,t; int a[100005]; long long f[100005]; void run(int m) {//f(i)=min{f(j) || i-m-1<=j<=i-1}+a[i] f[0]=0; for(int i=1;i<=m;i++) f[i]=a[i]; for(int i=m+1;i<=n+1;i++) f[i]=inf; long long t; for(int i=1;i<=n+1;i++) { t=inf; for(int j=i-m-1;j<=i-1;j++) { t=min(t,f[j]); } f[i]=t+a[i]; } } void solve() { int x=1,y=n,ans=n; while(x<=y) { int m=(x+y)/2; run(m); long long tmp=f[n+1];//空题段不超过m的情况下,完成题目所需的最小时间; if(tmp>t)//完成不了 x=m+1; else { y=m-1; ans=m; } } printf("%d",ans); } int main() { // freopen("in.txt","r",stdin); scanf("%d%d",&n,&t); for(int i=1;i<=n;i++) scanf("%d",&a[i]); a[n+1]=0; solve(); return 0; }

    时间复杂度分析:O(n * n *log2 n) 显然不能通过全部数据。 分析方程 f(i)=min{f(j) || i-m-1<=j<=i-1}+a[i] 实际上实在窗口长度为m时找一个f(j)的最小值,因此可以用单调序列优化。

    //solve100

    void run(int m) {//f(i)=min{f(j) || i-m-1<=j<=i-1}+a[i] f[0]=0; for(int i=1;i<=m;i++) f[i]=a[i]; for(int i=1;i<=n+1;i++) f[i]=inf; long long t; rear=0,front=0; q[rear++]=(data){0,0}; for(int i=1;i<=n+1;i++) { while(front!=rear && i-q[front].id-1>m) front++; f[i]=q[front].v+a[i]; while(front!=rear && q[rear-1].v>f[i]) rear--; q[rear++]=(data){f[i],i}; } }

    时间复杂度:O(n*log2 n *log2 n)(每个数据进队一次出队一次)

    转载请注明原文地址: https://ju.6miu.com/read-14852.html

    最新回复(0)