题解——[六省联考2017]期末考试(模拟+递推)
要写数据分治,但这道题真的水
Description
有n 位同学,每位同学都参加了全部的m 门课程的期末考试,都在焦急的等待成
绩的公布。
第i 位同学希望在第ti 天或之前得知所. 有. 课程的成绩。如果在第ti 天,有至少一
门课程的成绩没有公布,他就会等待最后公布成绩的课程公布成绩,每等待一天就会产
生C 不愉快度。
对于第i 门课程,按照原本的计划,会在第bi 天公布成绩。
有如下两种操作可以调整公布成绩的时间:
将负责课程X 的部分老师调整到课程Y,调整之后公布课程X 成绩的时间推迟
一天,公布课程Y 成绩的时间提前一天;每次操作产生A 不愉快度。增加一部分老师负责学科Z,这将导致学科Z 的出成绩时间提前一天;每次操
作产生B 不愉快度。
上面两种操作中的参数X; Y; Z 均可任意指定,每种操作均可以执行多次,每次执
行时都可以重新指定参数。
现在希望你通过合理的操作,使得最后总的不愉快度之和最小,输出最小的不愉快
度之和即可。
Input
从文件exam.in 中读入数据。
第一行三个非负整数A; B;C ,描述三种不愉快度,详见【问题描述】;
第二行两个正整数 n m ,分别表示学生的数量和课程的数量;
第三行n 个正整数ti ,表示每个学生希望的公布成绩的时间;
第四行m 个正整数bi ,表示按照原本的计划,每门课程公布成绩的时间。
Output
输出到文件exam.out 中。
输出一行一个整数,表示最小的不愉快度之和。
in.1
100 100 2
4 5
5 1 2 3
1 1 2 3 3
out.1
6
数据范围与约定
一些性质
1.由题意得,其实影响最后答案的科目一定是排在最后面的科目。
2.如果我们将最后同一时刻上的科目往前移动,而没有移动在这一时刻上的所有科目,那么这之前的移动是无效的。
3.当不考虑特殊条件时,如果 A < B 那么优先选 A 操作,再选 B。否则全部选 B 操作即可。
思考
如果我们枚举时刻 i ,考虑将所有在时刻 i 之后的课程提到 i 时刻来,那么对于时刻 i 而言,这一定是当前时刻的最优解。
发现数据很大,所以我们要预处理出将所有在时刻 i 之后的科目提到 i 时刻后,学生的不满意度 和 提前操作的不满意度 。
由于 A 操作的次数在每个时刻 i 是有限的(不能把科目延迟到 i 之后),我们也要预处理出来。
主要分为两部分:
1.预处理出我们需要的数组。
2.O(n)枚举时间,取最小答案即可(包括一些特判)
处理:
1.我们用2个桶 t[ ] 和 d[ ] 分别记录在 i 时刻学生的时间上限为i的数量和结束科目的数量
顺便记录一下最后一科的时间,作为枚举边界。
for( int i = 1 ; i <= N ; ++i )m1 = (int)read() , t[ m1 ]++ ;
for( int i = 1 ; i <= M ; ++i )m1 = (int)read() , las = max( (ll)m1 , las ) , d[ m1 ]++ ;
然后我们枚举时刻 i ,直接预处理出上述三个需要的数组。记录方法为先累加贡献值,再累加个数,自己想想其实很简单的。复杂度 O(n)。
ll tot = 0 , val = 0 ;//∑C
if( flag != 3 )
for( int i = 1 ; i <= las ; ++i )
val += tot*C , tot += t[ i ] , c[ i ] = val ;
tot = 0 , val = 0 ;
for( int i = 1 ; i <= las ; ++i )//A的上限
val += tot , tot += d[ i ] , used[ i ] = val ;
tot = 0 , val = 0 ;
for( int i = las ; i >= 1 ; --i )//需要提前的次数
val += tot , tot += d[ i ] , cha[ i ] = val ;
2.预处理完成之后,我们枚举时间,求出学生的不满意度和提前操作的不满意度之和即可。
特殊点处理:
1 2 点不能使用 A,B ,直接输出 c[ las ]
3 4 点不能使用 B , 全部使用 A , A不够直接 break
13 14 点不能预处理 C , 也不能使用 C , 直接求出提前到最早时间要求处的答案即可。
时间复杂度
综上所述,预处理和递推模拟都是严格 O(n),而且数据分治,复杂度已经优化,在数据更新后20个点只跑了134ms,比大部分数据更新前的人都跑的快。
#include<bits/stdc++.h>
using namespace std ;
#define ll long long
const int MAXN = 100005 ;
inline ll read(){
ll s=0 ; char g=getchar() ; while(g>'9'||g<'0')g=getchar() ;
while(g>='0'&&g<='9')s=s*10+g-'0',g=getchar() ; return s ;
}
ll t[ MAXN ] , d[ MAXN ] , used[ MAXN ] , cha[ MAXN ] , c[ MAXN ] ;
ll A , B , C , N , M , ans , las ;//las记录最后一科出来的时间
int flag = 0 , flag2 = 0 ;
int check(){
if( A < B )flag2 = 1 ;
if( A == 1e9 && B == 1e9 )return 1 ;
if( A != 1e9 && B == 1e9 )return 2 ;
if( C == 1e16 )return 3 ;
return 0 ;
}
void prepare(){
ll tot = 0 , val = 0 ;//∑C
if( flag != 3 )
for( int i = 1 ; i <= las ; ++i )
val += tot*C , tot += t[ i ] , c[ i ] = val ;
tot = 0 , val = 0 ;
for( int i = 1 ; i <= las ; ++i )//A的上限
val += tot , tot += d[ i ] , used[ i ] = val ;
tot = 0 , val = 0 ;
for( int i = las ; i >= 1 ; --i )
val += tot , tot += d[ i ] , cha[ i ] = val ;
}
void work(){
ll now ; ans = c[ las ] ;
for( int i = las ; i >= 1 ; --i ){
now = c[ i ] ;
if( flag2 ){//优先用A
if( used[ i ] >= cha[ i ] )now += cha[ i ]*A ;
else now += used[ i ]*A + ( cha[ i ] - used[ i ] )*B ;
}
else
now += cha[ i ]*B ;
ans = min( ans , now ) ;
}
cout<<ans ;
}
void work2(){//仅使用 A C
ll now ; ans = c[ las ] ;
for( int i = las ; i >= 1 ; --i ){
now = c[ i ] ;
if( used[ i ] >= cha[ i ] )now += cha[ i ]*A ;
else break ;
ans = min( ans , now ) ;
}
cout<<ans ;
}
void work3(){//仅使用 A B
ans = 0LL ; //上限1e10
ll fir = 0LL ;
for( int i = 1 ; i <= las ; ++i )if( t[ i ] ){ fir = i ; break ; }
if( flag2 ){//优先用A
if( used[ fir ] >= cha[ fir ] )ans += cha[ fir ]*A ;
else ans += used[ fir ]*A + ( cha[ fir ] - used[ fir ] )*B ;
}
else
ans += cha[ fir ]*B ;
cout<<ans ;
}
int main(){
freopen("exam.in","r",stdin);
freopen("exam.out","w",stdout);
A = read() , B = read() , C = read() , N = read() , M = read() ;
int m1 , m2 ;
for( int i = 1 ; i <= N ; ++i )m1 = (int)read() , t[ m1 ]++ ;
for( int i = 1 ; i <= M ; ++i )m1 = (int)read() , las = max( (ll)m1 , las ) , d[ m1 ]++ ;
flag = check() ;
prepare() ;
if( flag == 1 ){cout<<c[ las ] ; return 0 ; }
if( flag == 2 ){ work2() ; return 0 ; }
if( flag == 3 ){ work3() ; return 0 ; }
work() ;
return 0 ;
}