解题报告5整数因子团问题文档格式.docx
《解题报告5整数因子团问题文档格式.docx》由会员分享,可在线阅读,更多相关《解题报告5整数因子团问题文档格式.docx(14页珍藏版)》请在冰豆网上搜索。
也不是很难,因为人在玩游戏的过程中容易得出一个比较优的决策。
在数据比较小时,还能够比较快的得到最优决策。
这是因为我们在用一个策略——贪心策略!
的确,由于题目的简明清晰,很容易想出各种贪心方法。
这些贪心方法,有的能够保证最优,有的却不能保证。
但是,这是一道求最优解的题。
贪心的思想只能作为一个参考,并不能解决此题。
弄了很久,很难找到比较好的方法,最后,搜索似乎是唯一的出路。
搜就搜吧。
看到数据范围很容易受惊,太大了!
要能够高效率,就必须有很强的剪枝。
在一般情况下,如果求出了一个下界,用最优性剪枝可能会比较有效果。
这个下界,可以首先贪心得到,也可以在搜索过程中贪心的规定搜索顺序。
但是,这个是最简单的剪枝,它还远远不够。
最优性剪枝的判断方法是,如果当前得到的分数加上后面最多能够得到的分数还不超过现在的最优解,那么就可以剪枝。
“后面最多能够得到的分数”——这意味着我们只求出下界是不够的,还需要求出上界。
这个上界看起来不是很好求,就用剩下的所有数的和代替?
太不像样了。
要得到一个像样的上界,就必须分析这堆数的规律。
首先,把这些数和它们的关系用图的方法表现出来。
用n个节点代表n个数,如果a是b的倍数,那么就从a到b连一条有向边。
显然,这个倍数关系是一个偏序关系,由于它的传递性,可以去掉一些边,使得从剩下的边就可以看出一个偏序关系。
如果a是b的倍数,存在b<
c<
a,使得c是b的倍数,a是c的倍数,那么a到b的边是可以不要的,最后剩下的边就会是:
如果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.05
0.36
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:
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;
i,j,p:
prime:
begin
fillchar(prime,sizeof(prime),1);
fori:
=2tondoifprime[i]then
forj:
=itondividoprime[i*j]:
=false;
level[1]:
=0;
=2tondoforj:
=2toidoifimodj=0thenbegin
level[i]:
=level[idivj]+1;
break
p:
=1tondobegin
point[i]:
=p+1;
=1toi-1doif(imodj=0)andprime[idivj]thenbegin
inc(p);
node[p]:
=j;
value[p]:
=i
=2tondividoifprime[j]thenbegin
=i*j;
=i*j
end
point[n+1]:
end;
functionfind(varsys:
tsearchsystem;
direct:
boolean):
i,k,head,tail,t:
q:
array[0..size]ofinteger;
inq:
dist,par:
withsysdobegin
head:
tail:
fillchar(par,sizeof(par),0);
fillchar(inq,sizeof(inq),0);
=1tondo
if(match[i]=0)and(odd(level[i])=direct)andnotcut[i]thenbegin
inc(tail);
q[tail]:
=i;
dist[i]:
inq[i]:
=true;
elsedist[i]:
=-maxint;
whilehead<
taildobegin
inc(head);
k:
=q[headandsize];
inq[k]:
ifodd(level[k])=directthenbegin
=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
q[tailandsize]:
=t;
inq[t]:
=true
elsebegin
ifmatch[k]<
0thenbegin
ifmatch[k]>
kthent:
=match[k]elset:
ifdist[k]-t>
dist[match[k]]thenbegin
dist[match[k]]:
=dist[k]-t;
par[match[k]]:
ifnotinq[match[k]]thenbegin
=match[k];
inq[match[k]]:
if(odd(level[i])xordirect)and(dist[i]>
t)and(match[i]=0)andnotcut[i]then
begin
t:
=dist[i]
ift>
find:
inc(g,t);
whilek<
0dobegin
i:
=par[k];
match[i]:
match[k]:
=par[i]
exit
if(odd(level[i])=direct)and(dist[i]>
t)andnotcut[i]then
match[k]:
whilei<
=par[i];
match[i]:
=par[k]
=false
proceduregetreadyforsearch(varsys:
tsearchsystem);
i,j:
f:
g:
fillchar(number,sizeof(number),0);
fillchar(cut,sizeof(cut),0);
=1tondoforj:
=1tondivido
inc(number[i*j]);
fillchar(match,sizeof(match),0);
repeatuntilnotfind(sys,true);
functioncan(dep,d1,d2:
integer):
withsearchsys[dep]dobegin
ifd1modd2=0thenbegincan:
exitend;
=1tod2shr1do
ifnotcut[i]and(d2modi=0)and(d1modi<
0)thenbegin
can:
exit
proceduresearch(dep:
integer);
i,j,k,t,best:
b:
ifgetsothenexit;
iff=lowerthenbegin
ans:
=nowans;
tans:
=dep;
iff+g<
lowerthenexit;
best:
=ndownto1doifnotcut[i]and(number[i]=2)thenbegin
=2tondividoifnotcut[i*j]thenb:
ifbthenbeginbest:
breakend;
=ndownto1do
ifnotcut[i]and(number[i]>
=2)thenbegin
if(best<
0)and(i<
best)thencontinue;
ifnumber[i]>
=4thenbegin
=1toishr1do
if(imodj=0)andnotcut[j]and(level[j]<
level[i]-1)then
beginb:
ifnotbthencontinue;
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]:
searchsys[dep+1]:
=searchsys[dep];
withsearchsys[dep+1]dobegin
inc(f,i);
=1toidoif(imodj=0)andnotcut[j]thenbegin
cut[j]:
fork:
=2tondivjdodec(number[j*k])
=1toidoif(imodj=0)and(match[j]<
ifmatch[j]>
jthent:
=match[j]elset:
dec(g,t);
match[match[j]]:
match[j]:
=0
repeatuntilnotfind(searchsys[dep+1],true)and
notfind(searchsys[dep+1],false);
search(dep+1);
procedurework;
getreadyforsearch(searchsys[0]);
=searchsys[0].g+1;
repeat
dec(lower);
search(0);
untilgetso;
assign(output,outputfile);
rewrite(output);
writeln(lower);
close(output);
assign(input,inputfile);
reset(input);
readln(n);
close(input);
prepare;
work;
end.