2017.1.12【初中部 】普及组模拟赛C组 简单游戏 题解

原题:

http://172.16.0.132/junior/#contest/show/1360/1

题目描述:

Charles和sunny在玩一个简单的游戏。若给出1~n的一个排列A,则将A1、A2相加,A2、A3相加……An-1、An相加,则得到一组n-1个元素的数列B;再将B1、B2相加,B2、B3相加,Bn-2、Bn-1相加,则得到一组n-2个元素的数列……如此往复,最终会得出一个数T。而Charles和sunny玩的游戏便是,Charles给出n和T,sunny在尽可能短的时间内,找到能通过上述操作得到T且字典序最小的1~n的排列。(sunny大声说:“What an easy game!”,接着几下就给出了解),Charles觉得没意思,就想和你玩,当然,你可以用一种叫做“电子计算机”的东西帮你。

输入:

本题有多组数据,对于每组数据:一行两个整数n(0

输出:

对于每组测试数据输出一行n个整数,用空格分开,行尾无多余空格,表示求出来的满足要求的1~n的一个排列。

样例输入:

4 16
3 9
0 0

样例输出:

3 1 2 4
1 3 2

样例解释:

开始排列: 3 1 2 4
第一次操作:3+1=4 1+2=3 2+4=6
得到:4 3 6
第二次操作:4+3=7 3+6=9
得到:7 9
最后操作: 7+6=16
得到: 16

数据范围限制:

数据保证有解。
对于30%的数据,保证该组里的每个N都不超过10。
对于100%的数据,保证有每个N不超过20,且每组数据的个数不超过10。

分析:

分析:题目要求1-N的一个排列A1,A2…An使得C(N-1,0)*A1+C(N-1,1)*A2+….+C(N-1,N-1)*AN=T,
即:这里写图片描述 —–①式
方法很好确定,先把C(N-1,i)求出来,然后只要把每一个位上的数确定好就可以了,所以采用深度优先搜索的方法。
用DFS(x,y)表示当前将要确定第x个位置上数,已经确定的和为y,把每种情况都搜出来,然后在递归出口的位置判断①式是否成立,不过很可惜,这种方法得不到分;
剪枝一:加一个小小的优化,就是在确定第X个数时,保证新求出来的y不能大于T,加上这个优化后,可以得40分;
剪枝二:由于C(N-1,i)=C(N-1,N-1-i),具有对称性,题目又要求最小字典序列,所以在枚举时当x>n div 2 时,要保证a[x]>a[N+1-x],这样程序速度会提高,但是该题还是只能得40分;
剪枝三:当枚举到第X个数时,剩余的N-X个数,与剩余的N-X个系数一一对应,让大数和大系数相乘,小数和小系数相乘可以得到最大值,让大数和小系数相乘,小数和大系数相乘可以得到最小值,如果剩余的值不在这个范围内,就不要搜下去,这样可以大大优化;

实现:

uses math;
var
        i,j,a,b,c,q1,q2:longint;
        v:array[1..21]of longint;
        p:array[1..21]of boolean;
        p1:boolean;
        f:array[1..20,1..20]of longint;
        o,o1:array[0..20]of longint;
procedure q(l,r:longint);
var
        i,j,mid:longint;
begin
        i:=l;j:=r;mid:=o[(i+j) div 2];
        while i<j do
        begin
                while o[i]<mid do inc(i);
                while o[j]>mid do dec(j);
                if i<=j then
                begin
                        o[0]:=o[i];o[i]:=o[j];o[j]:=o[0];
                        inc(i);dec(j);
                end;
        end;
        if l<j then q(l,j);
        if i<r then q(i,r);
end;
function pd(u,t:longint):boolean;
var
        i,j,s,ans,ans1:longint;
begin
        s:=0;
        for i:=1 to a do
                if p[i]=true then
                begin
                        inc(s);o[s]:=i;
                end;
        q(1,s);o1:=o;s:=0;
        for i:=u to a do
        begin
                inc(s);o[s]:=f[a,i];
        end;
        q(1,s);ans:=0;ans1:=0;j:=s;
        for i:=1 to s do
        begin
                ans:=ans+o[i]*o1[i];
                ans1:=ans1+o[i]*o1[j];
                dec(j);
        end;
        if ans+t<b then exit(false);
        if ans1+t>b then exit(false);
        exit(true);
end;
procedure dg(s,ans:longint);
var
        i:longint;
begin
        if p1=false then exit;
        if ans>b then exit;
        if pd(s,ans)=false then exit;
        if s=a+1 then
        begin
                if ans=b then
                begin
                        p1:=false;
                        for i:=1 to a do write(v[i],' ');
                        writeln;
                end;
        end
        else
                for i:=1 to a do
                        if (p[i]=true)and(i>v[a+1-s]) then
                        begin
                                p[i]:=false;v[s]:=i;dg(s+1,ans+i*f[a,s]);
                                v[s]:=0;p[i]:=true;
                        end;
end;
begin
        assign(input,'easy.in');reset(input);
        assign(output,'easy.out');rewrite(output); 
        for i:=1 to 20 do
        begin
                f[i,1]:=1;f[i,i]:=1;
        end;
        for i:=3 to 20 do
                for j:=2 to i-1 do
                        f[i,j]:=f[i-1,j]+f[i-1,j-1];
        while true do
        begin
                readln(a,b);
                if (a=0)and(b=0) then halt;
                fillchar(p,sizeof(p),true);
                p1:=true;dg(1,0);
        end;
        close(input);close(output);
end.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值