[jzoj]1501. 糖果(优化多维背包的多种方法)

来源:互联网 发布:pkpm计算软件 编辑:程序博客网 时间:2024/05/29 04:00

一般来说,求解多维背包是求最小价值,但是也可以求其他的,例如此题:

多维背包复杂度O(nmk),但有很多的优化方法.

Link

https://jzoj.net/senior/#contest/show/1992/2

Problem

给定n个背包的重量ci及个数ki,使其分成两堆使得差最小.

Data constraint

50%的数据1<=N,Ki,Ci<=100.
100%的数据1<=N<=100,1<=Ki<=500,1<=Ci<=200.

Solution

Method-1-1

我们直接采用多维背包求解,不加任何优化:

var        f:array[0..10000000] of boolean;        x,y:array[1..100] of longint;        n,i,j,k,sum,ans:longint;function min(x,y:longint):longint;begin        if x<y then exit(x); exit(y);end;begin        readln(n);        for i:=1 to n do        begin                readln(x[i],y[i]);                inc(sum,x[i]*y[i]);        end;        f[0]:=true;        for i:=1 to n do                for k:=1 to x[i] do                        for j:=sum downto y[i] do                                if f[j-y[i]] then f[j]:=true;        ans:=sum;        for i:=1 to sum do                if f[i] then ans:=min(ans,abs(i-(sum-i)));        writeln(ans);end.

对于此题,可以拿到72分.

Method-1-2

我们采取两个优化:二进制优化每次sum依次累加的优化.

var        f:array[0..10000000] of boolean;        d:array[1..100,1..100] of longint;        x,y,len:array[1..100] of longint;        n,i,j,k,t,sum,ans:longint;function min(x,y:longint):longint;begin        if x<y then exit(x); exit(y);end;begin        readln(n);        for i:=1 to n do        begin                readln(x[i],y[i]);                t:=x[i];                k:=1;                while t-k>0 do                begin                        t:=t-k;                        inc(len[i]);                        d[i,len[i]]:=k;                        k:=k shl 1;                end;                inc(len[i]);                d[i,len[i]]:=t;        end;        f[0]:=true;        for i:=1 to n do        begin                inc(sum,x[i]*y[i]);                for k:=1 to len[i] do                        for j:=sum downto y[i]*d[i,k] do                                if f[j-y[i]*d[i,k]] then f[j]:=true;        end;        ans:=sum;        for i:=1 to sum do                if f[i] then ans:=min(ans,abs(i-(sum-i)));        writeln(ans);end.

可以多过一个数据点,并且对于刚刚跑400ms的一个数据,现在只跑了40ms,可见优化之大,但由于数据的原因,并不能多拿多少分.

于是继续优化.

Method-1-3

注意到1<=Ci<=200,所以我们可以考虑把相同重量的背包合并在一起,那么二进制的优化就会明显很多.

var        tot,a,len:array[0..200] of longint;        bz:array[0..200] of boolean;        f:array[0..10000000] of boolean;        d:array[0..100,1..100] of longint;        n,i,j,k,t,sum,ans,lena,x,y,maxsum:longint;function min(x,y:longint):longint;begin        if x<y then exit(x); exit(y);end;begin        readln(n);        fillchar(bz,sizeof(bz),true);        for i:=1 to n do        begin                readln(x,y);                inc(maxsum,x*y);                inc(tot[y],x);                if bz[y] then                begin                        bz[y]:=false;                        inc(lena);                        a[lena]:=y;                end;        end;        for i:=1 to lena do        begin                t:=tot[a[i]];                k:=1;                while t-k>0 do                begin                        t:=t-k;                        inc(len[i]);                        d[i,len[i]]:=k;                        k:=k shl 1;                end;                inc(len[i]);                d[i,len[i]]:=t;        end;        f[0]:=true;        for i:=1 to lena do        begin                inc(sum,a[i]*tot[a[i]]);                for k:=1 to len[i] do                                        for j:=sum downto a[i]*d[i,k] do                                if f[j-a[i]*d[i,k]] then f[j]:=true;         end;        ans:=sum;        for i:=1 to sum do                if f[i] then ans:=min(ans,abs(i-(sum-i)));        writeln(ans);end.

于是这样子,可以拿到92分,进了一大步.

Method-1-4

我们再来两个优化:
我们把AiDi,k按从小到大排个序,可以发现刚刚跑900ms+的点只跑了500ms+,优化了近0.4秒.
当然,还有一个更加强劲的优化,我们判断如果当前可行的最优解已经产生,那么就没必要继续进行dp了,直接输出.
注意常数,如果把判断放在第三重循环里,就会多将近0.1秒.

var        tot,a,len:array[0..200] of longint;        bz:array[0..200] of boolean;        f:array[0..10000000] of boolean;        d:array[0..100,1..100] of longint;        n,i,j,k,t,sum,ans,lena,x,y,maxsum:longint;function min(x,y:longint):longint;begin        if x<y then exit(x); exit(y);end;begin        readln(n);        fillchar(bz,sizeof(bz),true);        for i:=1 to n do        begin                readln(x,y);                inc(maxsum,x*y);                inc(tot[y],x);                if bz[y] then                begin                        bz[y]:=false;                        inc(lena);                        a[lena]:=y;                end;        end;        for i:=1 to lena do        begin                t:=tot[a[i]];                k:=1;                while t-k>0 do                begin                        t:=t-k;                        inc(len[i]);                        d[i,len[i]]:=k;                        k:=k shl 1;                end;                inc(len[i]);                d[i,len[i]]:=t;        end;        for i:=1 to lena-1 do                for j:=i+1 to lena do                        if a[i]*tot[a[i]]>a[j]*tot[a[j]] then                        begin                                a[0]:=a[i]; a[i]:=a[j]; a[j]:=a[0];                                len[0]:=len[i]; len[i]:=len[j]; len[j]:=len[0];                                d[0]:=d[i]; d[i]:=d[j]; d[j]:=d[0];                        end;        f[0]:=true;        for i:=1 to lena do        begin                inc(sum,a[i]*tot[a[i]]);                for k:=1 to len[i] do                begin                        for j:=sum downto a[i]*d[i,k] do                                if f[j-a[i]*d[i,k]] then f[j]:=true;                        if f[maxsum div 2] then                                begin                                        if maxsum mod 2=1 then writeln(1) else writeln(0);                                        halt;                                end;                end;        end;        ans:=sum;        for i:=1 to sum do                if f[i] then ans:=min(ans,abs(i-(sum-i)));        writeln(ans);end.

Method-2

根据这种题的一般规律,答案差是不会很大的, 我们限定在一个最大却又时间稳妥的范围,然后直接按照一般思路dp就好了.

即枚举一个状态,并通过这个状态由一个背包分成两份去更新下一个状态:

var        n,i,j,p,tmp:longint;        c,k:array[1..100]of longint;        f:array[0..100,0..400]of boolean;begin        readln(n);        for i:=1 to n do                readln(k[i],c[i]);        f[0,0]:=true;        for i:=1 to n do                for j:=0 to 400 do                        if f[i-1,j] then                                for p:=0 to k[i] do                                begin                                        tmp:=abs(j+(k[i]-p-p)*c[i]);                                        if tmp<=400 then f[i,tmp]:=true;                                end;        for i:=0 to 400 do                if f[n,i] then                begin                        writeln(i);                        break;                end;end.

这种方法对于这道题的数据很吃香,最大的一个点只跑了20ms,考试时可以将以上两种方法并用,使得正确率最高!

原创粉丝点击