BSOJ 3425:分班 DP+单调队列

29 篇文章 0 订阅
7 篇文章 0 订阅

3425 – 【模拟试题】分班
Description
这里写图片描述
Input
  本题目有多组数据。第一行有一个整数case(<=10),表示有case组数据。
  接下来对于每组数据的第一行有4个正整数,依次是M,N,A,B。
  第二行有M个正整数,用空格隔开,分别是X[1],X[2] —–X[M]。
  第三行有N个正整数,用空格隔开,分别是G[1],G[2] —–G[N]。
Output
  对于每组数据,要输出三个正整数,以空格隔开,不同数据之间要换行。
  三个正整数分别为sigma,class,last。分别表示最小的评价指数。在评价指数最小的情况下,安排的教室的最小数目。在评价指数,安排教师数目最小的情况下,最后一个教室的人数的最小数目。
Sample Input
1
10 3 1 4
16 11 12 13 10 15 16 17 18 14
4 5 1
Sample Output
186 3 4
Hint
【样例解释】
这里写图片描述
前4个,后4个,中间2个。

求的是sigma((xi-ave)^2)*G[g[i]],ave和xi都是常数,也就将问题简化为一个分配问题。
设f[i][j]为前i个学生,分j班,且当前为第j班的最小评价分数,容易写出DP方程:
f[i][j]=min{f[k][j-1]+G[j]*(sumx[i]-sumx[k])};
(i-B<=k<=i-A)
这是一个O(mn^2)的递推式,只拿得到40分。
考虑优化,使用单调队列,更新f[i][j]的时候一步得到f[k][j-1]-G[j]*sumx[k]的最小值,注意这里用单调队列不仅仅维护f[k][j-1],因为后面还有一项与k有关,故要全部带进去,否则是错误的,对于第2、3问,根据贪心(误?),更小解出现时即更新其答案即可。至此问题得解。

hint:
1. 这里的单调队列只与j-1的各项有关,也即每次j变化,要把头尾指针清零。
2. 递推的方向为上一个班->当前班(j-1->j),可以使用滚动数组,并且DP第一位一定为j。

滚动数组太丑了..考试的时候作死写一个然后惨遭爆零..

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#define ll long long
#define INF 1e18
//#include<cmath>
using namespace std;
inline ll read()
{
    ll bj=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')bj=-1;
        ch=getchar();
    }
    ll ret=0;
    while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
    return ret*bj;
}
ll n,m,a,b,x[10005]={0},G[204]={0},sum[10004]={0},ave=0;
ll f[10001][220]={0},q[100005]={0},head=0,tail=0,minn;
void reset()
{
    memset(q,0,sizeof(q));
    memset(x,0,sizeof(x));
    memset(G,0,sizeof(G));
    memset(sum,0,sizeof(sum));
    ave=0,minn=INF,head=1,tail=0;
}
void init()
{
    reset();
    m=read();n=read();a=read();b=read();
    for(ll i=1;i<=m;i++)x[i]=read(),ave+=x[i];
    for(ll i=1;i<=n;i++)G[i]=read();
    ave/=m;
    for(ll i=1;i<=m;i++)sum[i]=sum[i-1]+(x[i]-ave)*(x[i]-ave);
}
void DP()
{
    ll cla,last,stu;
    for(int i=0;i<=10000;i++)for(int j=0;j<=200;j++)f[i][j]=INF;
    f[0][0]=0;
    for(ll j=1;j<=n;j++)
    {
        head=1,tail=0;
        for(ll i=1;i<=m;i++)
        {
            if(i-a>=0)
            {
                if(f[i-a][j-1]!=INF)
                {
                    while(f[i-a][j-1]-sum[i-a]*G[j]<=f[q[tail]][j-1]-sum[q[tail]]*G[j]&&head<=tail)tail--;
                    q[++tail]=i-a;
                }
            }
            while(q[head]<i-b&&head<=tail)head++;
            if(head>tail)continue;
            f[i][j]=f[q[head]][j-1]+(sum[i]-sum[q[head]])*G[j];
            if(i==m)stu=q[head];
        }
        if(f[m][j]<minn)
        {
            minn=f[m][j];
            cla=j;
            last=m-stu;
        }
    }
    printf("%lld %lld %lld\n",minn,cla,last);
}
int main()
{
    ll tim=read();
    while(tim--)
    {
        init();
        DP();
    }
return 0;
}
/*
1
10 3 1 4 
16 11 12 13 10 15 16 17 18 14
4 5 1

1
5 1 4 5
49684 8970 96420 53727 12295
815

*/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值