洗盘子
Time Limits: 1000 ms Memory Limits: 65536 KB
Description
有N(1<=N<=40000)个奶牛到FJ的餐厅吃饭,餐厅里有M(1<=M<=N)种菜,每头牛有自己喜欢的菜的编号P_i(1<=P_i<=M),每头牛只吃自己喜欢的这道菜。
牛儿们在外面排着队进来,按照排队顺序一批一批进来,每批可以同时进来任意头牛,每一批吃完(注意包括最后一批)都要进行打扫,如果这批牛中一共需要K种菜,那么吃完后的打扫时间为K*K。
请你帮助FJ如何安排各批次使得总打扫时间最少。
Input
第1行:空格隔开的两个整数N和M
第2到N+1行:第i+1行包含一个整数P_i。
Output
输出最少打扫时间。
Sample Input
13 4
1
2
1
3
2
2
3
4
3
4
3
1
4
Sample Output
11
Hint
【样例说明】
前4批每批1头牛,打扫时间各为1,第5批2头牛打扫时间为1,第6批5头牛打扫时间为4,接下来分两批打扫时间各为1,一共11。
解题思路
设Fi表示排完1~i头奶牛需要的最小时间。
90%
首先把相同的一块连续的缩成一个。
其实这里只需要二重循环枚举i和j表示要求Fi,将j~i分为一组的时间,也就是将这个来更新Fi。j由i-1开始反过来循环,令k表示j~i中需要k种菜,求k很容易,只要判断Pj是否在Pj+1…i中出现过并更新k就行了。
若k2>Fi就break
那么这个时候Fi=Min(Fi,Fj−1+k2)
Codes:
const
a=true; b=false;
var
f,p,num:array[0..40000]of longint;
bz:array[1..40000]of boolean;
n,i,j,k,t,m,s,tot,x:longint;
begin
read(n,m);
for i:=1 to n do
begin
read(x);
if p[tot]=x then //将连续一块的缩成一个
begin
inc(num[tot]);
end else
begin
inc(tot);
num[tot]:=1;
p[tot]:=x;
end;
end;
for i:=1 to tot do
begin
f[i]:=f[i-1]+1;
k:=1;
bz[p[i]]:=a;
s:=i;
for j:=i-1 downto 1 do
begin
s:=j;
if bz[p[j]]=b then //p[j+1..i]中没有出现过p[j]
begin
bz[p[j]]:=a;
k:=k+1;
end;
t:=k*k;
if t+1>=f[i] then break;
if f[j-1]+t<f[i] then f[i]:=f[j-1]+t;
end;
for j:=i downto s do bz[p[j]]:=b;
end;
writeln(f[tot]);
end.
100%
我们在90%的基础上在优化一些。
我们发现,将j倒过来枚举,会造成不必要的寻找。那么我们设Gi,j表示满足Pk…i中有j种菜最小的k。
设Lai为在寻找完当前的数之前,最近的i的位置。
那么G就可以这么更新:
所以
Codes:
var
f,p,la:array[-1..40000]of longint;
g:array[-1..200]of longint;
n,i,j,m,s:longint;
begin
read(n,m);
for i:=1 to m do la[i]:=-1;
g[0]:=1;
s:=trunc(sqrt(n));
for i:=1 to n do
begin
f[i]:=f[i-1]+1;
read(p[i]);
for j:=s downto 1 do
if la[p[i]]<g[j] then g[j]:=g[j-1];
g[0]:=i;
for j:=1 to trunc(sqrt(i)) do
begin
if g[j]=0 then break;
if f[g[j]-1]+j*j<f[i] then f[i]:=f[g[j]-1]+j*j;
end;
la[p[i]]:=i;
end;
writeln(f[n]);
end.