如果a/b是一个质数,那么a,b之间的边就会保留,自己到自己的边也可以忽略掉。
结论①:
如果a是可以被选的数,那么a必然有一个没有删掉的约数b,a/b是质数。
证明:
如果a被删除了,那么a的约数也都被删除了。
假设a有约数b没有被删除,但没有一个满足a/b是质数。
那么,必然有数c,使得c是a的约数,b是c的约数,并且a/c是质数。
由于c被删除了,所以b也应该被删除了,所以假设不成立。
每一次删除时,至少都要删除两个数,求上界时,考虑最好的情况:
每次都只删除两个数。
由于每一个数最多能删除一次,所以不难得出一个模型:
在图中求出一个最佳匹配!
具体模型就是,把原来构造的图看成是无向图,边上的权值定为它两个顶点中数字较大的那一个,求一个最佳匹配,匹配表示选择这条边上较大的数删除,较小的数也被删除了。
这是一个理想情况,也就是一个上界。
怎样求最佳匹配呢?
进一步发现,上面的图是一个二分图:
只要我们对每一个结点标记一个层次:
一个节点u,把u分解质因数,设
,其中pi是互不相同的质数,结点u的层次level(u)就是:
a1+a2+……+ak。
那么,一条边连接的两个点u,v一定满足|level(u)-level(v)|=1。
这样,把level值为奇数的点作为点集X,为偶数的作为点集Y,这样就得到了一个二分图的模型。
最佳匹配可以用找增广轨的方法。
顺便说一句,这个上界会不会就是答案呢?
不是的,例如n=50时求出上界是811,但实际答案是808。
因为求匹配时存在着这样的匹配:
a)2—22,b)3—39,c)11—33,d)13—26。
不难发现,由于删除22时2在前面没有被删除,所以删除操作a)一定在d)之前,同理得出b)在c)之前,c)在a)之前,d)在b)之前。
这样就形成了一个环:
这个匹配不能得出一个删除方案。
即使如此,在数据较小时这个上界基本上是答案,有的虽然不是答案,也只比答案大一点了,这说明这个上界已经非常好了,那么就可以尝试用这个上界。
求上界的方法确定了,再来确定一个搜索方案。
在这里,可以考虑用IDA*算法。
对于一个状态,f表示这个状态现在已经得到的分数,g函数就是在剩下的点中匹配得出的一个上界,现在证明函数h=f+g满足相容性。
(不同于求最小值,这里需要求最大值,所以需要证明state1.h≥state2.h,其中state2是state1的后继)。
证明:
设state2是通过state1删除数a得到的,那么state2.f=state1.f+a。
那么必然有一个a的约数b,在state1中没有被删除,而在state2中删除了。
state2不会有a,b点,只需要在state2的最佳匹配中,加上一个匹配a--b,这样可以得到一个state1的匹配,它的权值和是state2.g+a,所以state1.g≥state2.g+a。
所以state1.f≥state2.f,这样就证明了相容性。
这样就可以尝试着用IDA*算法来实现了,但实践证明效果不是很好。
原因是状态总数还是很多,还需要做一些优化。
结论②:
如果一个数a在删除时,它有大于等于4个约数(包含自己)没有删除,并且它至少有一个层次比它小2的约数没有删除,那么删除a不会是最优的方案。
证明:
由于a剩下的约数在图中是连通的,所以可以分情况讨论(这些情况有交集但覆盖了所有的情况)。
设a有k个约数。
情况a):
如果有大于等于2小于等于k-2个约数满足它的层次比a的层次小1。
那么,至少存在一个层次为level(a)-2的约数,它到某一个层次为level(a)-1的约数b有边。
那么可以删除b,由于a有至少两个层次为level(a)-2的点,所以删除b后,a仍然能够被选。
这时,先删除b再删除a剩下的数与直接删除a剩下的数相同,但得到的分数比直接删除a好。
情况b):
如果有一个层次为level(a)-3的约数,那么从a出发有一个长度为3的链,这时,设这条链是a->b->c->d,那么先删除c再删除a与直接删除a得到的状态是一样的,但是前者得到的分数要多。
情况c):
有一个层次为level(a)-1的点b,除了a,b外的点的层次都是level(a)-2。
这种情况是不会出现的,由于至少有两个层次为level(a)-2的点,取两个c,d。
那么b到c,b到d都有边。
设p1=b/c,p2=b/d,显然p1≠p2并且p1,p2都是质数,那么考虑a/p1他一定是c的倍数。
由结论①知a/p1一定没有被删除。
同样a/p2也没有被删除,但是却只有一个层次为level(a)-1的点,这显然不可能。
即情况c)不会出现。
不难发现上面已经包含了所有的情况。
这个可以用来剪枝。
另外,在有些情况下能够保证最优,例如:
猜想③:
一个数只有两个约数没有被删除,并且这个数的没有倍数(除了自己),并且它是满足前面条件的最大的数,那么删除这个数是最优的。
我不能证明猜想,但是可以证明的是,在n≤120时不会有反例,而在n较大时似乎有一个这样的例子,这个只是在证明时构造出来的一个反例,实际中也许不会出现,下图表示。
然后,有些删除操作可以交换,即顺序不同但得到的分数和剩下的状态是一样的。
为了避免重复,可以考虑相邻两个操作能不能交换的情况。
还有,在求最大匹配的时候,不需要每次都重新找增广轨,只需要在以前找到匹配的基础上修改,这个修改,不需要是增广轨。
它是一条匹配便于没有匹配边的交替轨,它可以是偶数条边,只需要改变的权值大于0就可以了。
至此,算法已经基本成形了。
如果用上上面所有的优化,包括没有证明的猜想③,实践证明效果非常好。
下面是一些测试的结果:
N
10
30
50
65
80
100
105
110
120
答案
40
301
808
1328
2041
3164
3434
3764
4593
时间(单位:
s)
0.00
0.00
0.00
0.00
0.00
0.05
0.36
0.22
0.22
测试环境:
Pentium41.70GHzCPUFreepascalv1.0.6。
所有的数据加起来都只用了2.14s。
奇怪的是,如果不用猜想③,那么在n从70到74,81到86,和从105到120这一段速度奇慢,其余的都是很快,下面的数据可以说明:
n
70
71
72
73
74
81
82
83
84,85,105~120
答案
1566
1570
1642
1644
1718
2105
2187
2191
/
时间(不用猜想)
110.99
116.29
51.92
66.76
154.89
42.42
89.45
95.22
Toolong
除了没有运行出来的点不知道外,其余的点都没有找到猜想③的反例。
[参考文献]
《算法艺术与计算机竞赛》——刘汝佳黄亮
[讨论]
向刘汝佳请教过,知道题目还没有找到多项式算法,然后从他书上的提示找算法。
[感谢]
刘汝佳
3、程序
const
maxn=120;
maxnode=500;
size=127;
inputfile=’taxman.in’;
outputfile=’taxman.out’;
type
tsearchsystem=record
f,g:
longint;
match,number:
array[1..maxn]ofinteger;
cut:
array[1..maxn]ofboolean;
end;
var
getso:
boolean;
n,tans:
integer;
lower:
longint;
point,level:
array[1..maxn+1]ofinteger;
node,value:
array[1..maxnode]ofinteger;
searchsys:
array[0..maxnshr1]oftsearchsystem;
ans,nowans:
array[0..maxnshr1]ofinteger;
procedureprepare;
var
i,j,p:
integer;
prime:
array[1..maxn]ofboolean;
begin
fillchar(prime,sizeof(prime),1);
fori:
=2tondoifprime[i]then
forj:
=itondividoprime[i*j]:
=false;
level[1]:
=0;
fori:
=2tondoforj:
=2toidoifimodj=0thenbegin
level[i]:
=level[idivj]+1;
break
end;
p:
=0;
fori:
=1tondobegin
point[i]:
=p+1;
forj:
=1toi-1doif(imodj=0)andprime[idivj]thenbegin
inc(p);node[p]:
=j;value[p]:
=i
end;
forj:
=2tondividoifprime[j]thenbegin
inc(p);node[p]:
=i*j;value[p]:
=i*j
end
end;
point[n+1]:
=p+1;
end;
functionfind(varsys:
tsearchsystem;direct:
boolean):
boolean;
var
i,k,head,tail,t:
integer;
q:
array[0..size]ofinteger;
inq:
array[1..maxn]ofboolean;
dist,par:
array[1..maxn]ofinteger;
begin
withsysdobegin
head:
=0;tail:
=0;
fillchar(par,sizeof(par),0);
fillchar(inq,sizeof(inq),0);
fori:
=1tondo
if(match[i]=0)and(odd(level[i])=direct)andnotcut[i]thenbegin
inc(tail);q[tail]:
=i;dist[i]:
=0;inq[i]:
=true;
end
elsedist[i]:
=-maxint;
whileheadinc(head);
k:
=q[headandsize];
inq[k]:
=false;
ifodd(level[k])=directthenbegin
fori:
=point[k]topoint[k+1]-1do
if(node[i]<>match[k])andnotcut[node[i]]thenbegin
t:
=node[i];
ifcut[t]thencontinue;
ifdist[k]+value[i]>dist[t]thenbegin
dist[t]:
=dist[k]+value[i];
par[t]:
=k;
ifnotinq[t]thenbegin
inc(tail);
q[tailandsize]:
=t;
inq[t]:
=true
end
end
end
end
elsebegin
ifmatch[k]<>0thenbegin
ifmatch[k]>kthent:
=match[k]elset:
=k;
ifdist[k]-t>dist[match[k]]thenbegin
dist[match[k]]:
=dist[k]-t;
par[match[k]]:
=k;
ifnotinq[match[k]]thenbegin
inc(tail);
q[tailandsize]:
=match[k];
inq[match[k]]:
=true
end
end
end
end
end;
t:
=-maxint;
fori:
=1tondo
if(odd(level[i])xordirect)and(dist[i]>t)and(match[i]=0)andnotcut[i]then
begin
k:
=i;t:
=dist[i]
end;
ift>0thenbegin
find:
=true;
inc(g,t);
whilek<>0dobegin
i:
=par[k];
match[i]:
=k;match[k]:
=i;
k:
=par[i]
end;
exit
end;
t:
=-maxint;
fori:
=1tondo
if(odd(level[i])=direct)and(dist[i]>t)andnotcut[i]then
begin
k:
=i;t:
=dist[i]
end;
ift>0thenbegin
find:
=true;
inc(g,t);
i:
=match[k];
match[k]:
=0;
whilei<>0dobegin
k:
=par[i];
match[k]:
=i;match[i]:
=k;
i:
=par[k]
end
end;
find:
=false
end
end;
proceduregetreadyforsearch(varsys:
tsearchsystem);
var
i,j:
integer;
begin
withsysdobegin
f:
=0;g:
=0;
fillchar(number,sizeof(number),0);
fillchar(cut,sizeof(cut),0);
fori:
=1tondoforj:
=1tondivido
inc(number[i*j]);
fillchar(match,sizeof(match),0);
repeatuntilnotfind(sys,true);
end;
end;
functioncan(dep,d1,d2:
integer):
boolean;
var
i:
integer;
begin
withsearchsys[dep]dobegin
ifd1modd2=0thenbegincan:
=false;exitend;
fori:
=1tod2shr1do
ifnotcut[i]and(d2modi=0)and(d1modi<>0)thenbegin
can:
=true;exit
end;
can:
=false
end
end;
proceduresearch(dep:
integer);
var
i,j,k,t,best:
integer;
b:
boolean;
begin
ifgetsothenexit;
withsearchsys[dep]dobegin
iff=lowerthenbegin
getso:
=true;ans:
=nowans;tans:
=dep;exit
end;
iff+gbest:
=0;
fori:
=ndownto1doifnotcut[i]and(number[i]=2)thenbegin
b:
=true;
forj:
=2tondividoifnotcut[i*j]thenb:
=false;
ifbthenbeginbest:
=i;breakend;
end;
fori:
=ndownto1do
ifnotcut[i]and(number[i]>=2)thenbegin
if(best<>0)and(i<>best)thencontinue;
ifnumber[i]>=4thenbegin
b:
=true;
forj:
=1toishr1do
if(imodj=0)andnotcut[j]and(level[j]<>level[i]-1)then
beginb:
=false;breakend;
ifnotbthencontinue;
end;
if(dep>0)and(i>nowans[dep-1])and(nowans[dep-1]>0)and
can(dep-1,i,nowans[dep-1])then
continue;
ifbest<>0thennowans[dep]:
=-ielsenowans[dep]:
=i;
searchsys[dep+1]:
=searchsys[dep];
withsearchsys[dep+1]dobegin
inc(f,i);
forj:
=1toidoif(imodj=0)andnotcut[j]thenbegin
cut[j]:
=true;
fork:
=2tondivjdodec(number[j*k])
end;
forj:
=1toidoif(imodj=0)and(match[j]<>0)thenbegin
ifmatch[j]>jthent:
=match[j]elset:
=j;
dec(g,t);
match[match[j]]:
=0;match[j]:
=0
end;
end;
repeatuntilnotfind(searchsys[dep+1],true)and
notfind(searchsys[dep+1],false);
search(dep+1);
end
end
end;
procedurework;
var
i:
integer;
begin
getreadyforsearch(searchsys[0]);
lower:
=searchsys[0].g+1;
getso:
=false;
repeat
dec(lower);
search(0);
untilgetso;
assign(output,outputfile);rewrite(output);
writeln(lower);
close(output);
end;
begin
assign(input,inputfile);reset(input);readln(n);close(input);
prepare;
work;
end.