|
上次的写法剪枝手段单一,只是判断每条边被同一颜色的三角形覆盖不超过两次,其实对应每个三角形的三种上色 情形(0,1,2)的次数都可以加以限制。
--------------------- 改良过的暴力法几秒钟可以算出 7 个人,但是8个人还是无法算
drop table t;
----- 表T存储所有三角形,总共C(N,3)个
create table t as
with d as (
select replace(sys_connect_by_path(c,','),',') s from (
(select chr(64+level) c from dual connect by level<=7)
)
connect by nocycle c>prior c and level<=3
)
,s as ( --------- 所有三角形的边,N个顶点总共C(N,2)条
select s,power(10,row_number() over(order by s)-1) h ------ 每条边用十进制的一位表示, 总共需要C(N,2)位
,-row_number() over(order by s) id ---------- 每条边在计数器中的位置,用负数表示,从右边算起,
from d
where length(s)=2
)
--------- 所有三角形,N个顶点总共C(N,3)个
select /*+ materialize */ t1.s,t1.h,t1.rn,t1.id1,t1.id2,t1.id3
,sum(s.b) b ------- 每个三角形的三个顶点所对应的三个二进制位
from (
select d.s
,sum(s.h) h ------- 每个三角形的三条边所对应的三个十进制位
,row_number() over(order by d.s) rn
,max(decode(s.s,substr(d.s,1,2),s.id)) id1 ------ 这个三角形对应的第一条边在计数器中的位置
,max(decode(s.s,substr(d.s,2,2),s.id)) id2 ------ 这个三角形对应的第二条边在计数器中的位置
,max(decode(s.s,substr(d.s,1,1)||substr(d.s,3,1),s.id)) id3 ------ 这个三角形对应的第三条边在计数器中的位置
from d,s
where length(d.s)=3
and s.s in (substr(d.s,1,2) ---- 第一条边
,substr(d.s,2,2) ---- 第二条边
,substr(d.s,1,1)||substr(d.s,3,1) ---------- 第三条边
)
group by d.s
) t1
,(select s,power(2,row_number() over(order by s)-1) b from d where length(s)=1) s ---- N个顶点分别用一个二进制位表示
where instr(t1.s,s.s)>0
group by t1.s,t1.h,t1.rn,t1.id1,t1.id2,t1.id3
;
drop table r1;
drop table r2;
create table r1 (s varchar2(200),cnt1 number,cnt2 number,b1 number,b2 number,h0 number,h1 number,h2 number,cnt number);
create table r2 as select * from r1 where 1=0;
declare
v_cnt number:=2;
v_rowcount number;
v_result varchar2(100);
NUM number :=7;
v_pad number :=NUM*(NUM-1)/2; ---每条边一位,总共需要C(NUM,2)位
begin
execute immediate 'truncate table r1';
insert into r1
select listagg(s||rn,',') within group(order by s) s ----- 当前解法的所有三角形
,1 as cnt1 ------ 1组三角形总数
,1 as cnt2 ------ 2组三角形总数
,sum(decode(rn,1,b)) as b1 ------ 1组覆盖哪些顶点
,sum(decode(rn,2,b)) as b2 ------ 2组覆盖哪些顶点
,0 as h0 ------ 0组覆盖哪些边
,sum(decode(rn,1,h)) as h1 ------ 1组覆盖哪些边
,sum(decode(rn,2,h)) as h2 ------ 2组覆盖哪些边
,2 as cnt
from t
where s in ('ABC','ABD') ------- 以ABC,ABD为起点,假设ABC分在1组,ABD分在2组
;
loop
v_cnt := v_cnt+1;
execute immediate 'truncate table r2';
insert into r2
select r.s||decode(n,0,null,','||t.s||n) as s
,decode(n,1,cnt1+1,cnt1) as cnt1
,decode(n,2,cnt2+1,cnt2) as cnt2
,decode(n,1,r.b1+t.b-bitand(r.b1,t.b),r.b1) as b1 ----- 二进制的"OR"算法,表示覆盖哪些顶点
,decode(n,2,r.b2+t.b-bitand(r.b2,t.b),r.b2) as b2
,decode(n,0,r.h0+t.h,r.h0) as h0 --------- 0组覆盖的每个边的累计
,decode(n,1,r.h1+t.h,r.h1) as h1 --------- 1组覆盖的每个边的累计
,decode(n,2,r.h2+t.h,r.h2) as h2 --------- 2组覆盖的每个边的累计
,V_CNT as cnt
from r1 r,t,(select level-1 n from dual connect by level<=3) ----0: 这个三角形不属于任何组; 1:该三角形属于1组; 2:该三角形属于2组
,LATERAL ( ------ 把三条边对应的三种情形已经被覆盖的次数从三个计数器中抠出来
select to_number(substr(lpad(r.h0,v_pad,'0'),t.id1,1)) as c01 ---- n=0的三边计数器
,to_number(substr(lpad(r.h0,v_pad,'0'),t.id2,1)) as c02
,to_number(substr(lpad(r.h0,v_pad,'0'),t.id3,1)) as c03
,to_number(substr(lpad(r.h1,v_pad,'0'),t.id1,1)) as c11 ---- n=1的三边计数器
,to_number(substr(lpad(r.h1,v_pad,'0'),t.id2,1)) as c12
,to_number(substr(lpad(r.h1,v_pad,'0'),t.id3,1)) as c13
,to_number(substr(lpad(r.h2,v_pad,'0'),t.id1,1)) as c21 ---- n=2的三边计数器
,to_number(substr(lpad(r.h2,v_pad,'0'),t.id2,1)) as c22
,to_number(substr(lpad(r.h2,v_pad,'0'),t.id3,1)) as c23
from DUAL
) b
where t.rn=V_CNT
and (r.cnt1<NUM*(NUM-1)*(NUM-2)/12 ------- C(N,3)/2
or r.cnt2<NUM*(NUM-1)*(NUM-2)/12
)
and (n=0 and c01<(NUM-2-greatest(c11,c21)*2)
and c02<(NUM-2-greatest(c12,c22)*2)
and c03<(NUM-2-greatest(c13,c23)*2)
OR n=1 and c11<FLOOR((NUM-2-c01)/2)
and c12<FLOOR((NUM-2-c02)/2)
and c13<FLOOR((NUM-2-c03)/2)
OR n=2 and c21<FLOOR((NUM-2-c01)/2)
and c22<FLOOR((NUM-2-c02)/2)
and c23<FLOOR((NUM-2-c03)/2)
)
-- and instr(r.h1,'3')=0 -------因为有六个顶点,每条边最多对应四个三角形(6-2=4),索引每条边最多被每组覆盖两次,这个十进制位表示累计数,只能出现0,1,2
-- and instr(r.h2,'3')=0
;
v_rowcount :=sql%rowcount;
if v_rowcount=0 then
exit;
end if;
dbms_application_info.set_client_info ('cnt='||v_cnt||' rowcount='||v_rowcount);
select min(s) into v_result
from (
select *
from r2
where b1=power(2,NUM)-1
and b2=power(2,NUM)-1
and h1=h2
order by cnt1+cnt2
)
where rownum=1;
if v_result is not null then
dbms_output.put_line('solution found:'||v_result);
exit;
end if;
execute immediate 'truncate table r1';
insert into r1 select * from r2;
commit;
end loop;
end;
/
solution found:ABC1,ABD2,ACE2,ADG1,AEF1,AFG2,BCF2,BDF1,CDE1,CDG2,CFG1,DEF2
PL/SQL procedure successfully completed.
Elapsed: 00:00:03.90
---- 检验条件2
WITH r AS (SELECT 'ABC1,ABD2,ACE2,ADG1,AEF1,AFG2,BCF2,BDF1,CDE1,CDG2,CFG1,DEF2' r from dual)
,t as (
select regexp_substr(r,'[^,]+',1,level) s
from r
connect by level<=REGEXP_COUNT(r,',')+1
)
,d as (select t.s,substr(s,n,1) d, substr(s,-1) grp from t,(select level n from dual connect by level<=3))
select d,grp,count(*)
from d
group by d,grp
order by 1,2
;
D GRP COUNT(*)
---- ---- ----------
A 1 3
A 2 3
B 1 2
B 2 2
C 1 3
C 2 3
D 1 3
D 2 3
E 1 2
E 2 2
F 1 3
F 2 3
G 1 2
G 2 2
14 rows selected.
---- 检验条件3
WITH r AS (SELECT 'ABC1,ABD2,ACE2,ADG1,AEF1,AFG2,BCF2,BDF1,CDE1,CDG2,CFG1,DEF2' r from dual)
,t as (
select regexp_substr(r,'[^,]+',1,level) s
from r
connect by level<=REGEXP_COUNT(r,',')+1
)
,d as (select t.s
,decode(n,1,substr(s,1,2),2,substr(s,2,2),substr(s,1,1)||substr(s,3,1)) as e
,substr(s,-1) grp
from t,(select level n from dual connect by level<=3)
)
select e,count(decode(grp,1,1)) c1,count(decode(grp,2,1)) c2
from d
group by e
order by 1
;
E C1 C2
-------- ---------- ----------
AB 1 1
AC 1 1
AD 1 1
AE 1 1
AF 1 1
AG 1 1
BC 1 1
BD 1 1
BF 1 1
CD 1 1
CE 1 1
CF 1 1
CG 1 1
DE 1 1
DF 1 1
DG 1 1
EF 1 1
FG 1 1
18 rows selected.
|
|