NOIP 2012 Senior 2

来源:互联网 发布:python 移除数组元素 编辑:程序博客网 时间:2024/05/16 16:13

果然,一开始我又往DP方面想。还好,我及时发现了这个根本没法描述状态(就算用集合,也只是阶乘级到指数级,况且n<=1000,根本不可能),于是开始往贪心的方面想。

我们设左手上的数为A[i],右手上的数为B[i]

解1:
考虑到获得最多金币的大臣应该在最后一个,或者说,靠近最后一个。因为最后一个大臣计算金币时,分子是最大的,为其它大臣A的乘积。为什么说是靠近呢?因为有可能最后一个大臣的B[i]比倒数第二个大臣的B[i]大,导致获得的金币反而少。那我们先让最后一个大臣获得的金币尽量少试试。
第一次计算,可以计算其他人A[i]的积除以某个人的B[i],把最小的放在队列的最后,以后的计算把除了自己和计算过了的人的A[i]相乘,再除以这个人的B[i],同样把最小的放在后面。直到所有人都放完了就完了。
下面让我们简单证明下,为什么要把尽量小的放在后面:

设放在最后的大臣的左手为L1,右手为R1,他的根据之前描述的算法算出来的金币最少。倒数第二个大臣的左手为L2,右手为R2,这个大臣是随机的,但是把他放在倒数第一的位置时获得的金币比最后的大臣多。再设倒数第三个到国王的左手的乘积为M,则有:

COIN1=ML2R1
COIN2=MR2
交换后有
COIN1=MR1
COIN2=ML1R2

交换后的COIN2COIN1大,说明不会有更优解。

那如果COIN1=COIN2呢? 可以用一个贪心策略:尽量让L大的数放在后面,因为这样之前的L的乘积会尽量小。
若此时COINL均相同,那么R是相同的,随便选就可以了。最后的答案就是计算过程中出现的最大COIN。可以再一次使用贪心。若在计算过程中COIN的值非严格递减,则之后的COIN也会递减,这时便已得到了答案,不用算完。

以上便是我做题时想到的思路。很幸运,居然得到了90分。其实以上思路并不严密。我们每次选择的都是放在当前位置COIN最小的大臣,实际却不能保证先选的大臣的COIN在最后是最大值。同时,我们也不能保证COINL相同的大臣R也相同。一个典型的例子是:

1 1 //国王1 11 1101 1191 120

我们肯定会先选2,3,4号大臣,但是我们在计算时算到倒数第二个大臣就退出了。

一个解决方法是:只当金币严格递减时才退出。这样就能保证答案正确了。但是这样肯定会使计算速度放慢。幸运的是,最后一个点用这种方法在时间上刚好能过。

#include <cstdio>#include <cstdlib>#include <cmath>#include <cstring>#include <iostream>#include <algorithm>#include <vector>#include <string>#include <stack>#include <queue>#include <deque>#include <map>#include <set>using std::cin;using std::cout;using std::endl;#define FOR(x, f, t) for(int x = (f); x <= (t); x++)inline int readIn(){    int a;    scanf("%d",&a);    return a;}const int maxn = 1005;int n;int kingA, kingB;int A[maxn],B[maxn];bool vis[maxn];struct BigInt{    static const int digit = 1e9;    static const int size = 445;    std::vector<long long> num;    int length;    BigInt(int init = -1)    {        if(init==-1)        {            num.resize(size,digit-1);            length = size;        }        else        {            num.resize(size);            num[0] = init;            length = bool(init);        }    }    void operator*= (int b)    {        for(int i=0; i<length; i++)        {            num[i]*=b;        }        for(int i=0; i<length; i++)        {            num[i+1]+=num[i]/digit;            num[i]%=digit;        }        if(num[length]) length++;    }    void operator/= (int b)    {        long long div = 0;        long long mod = 0;        for(int i=length-1; i>=0; i--)        {            num[i]+=mod * digit;            mod = num[i] % b;            num[i] = num[i] / b;        }        if(length && !num[length - 1]) length--;    }    BigInt operator/ (int b)    {        BigInt ret = *this;        ret/=b;        return ret;    }    bool operator< (const BigInt& b) const    {        if(length<b.length) return true;        else if(length>b.length) return false;        for(int i=length-1; i>=0; i--)        {            if(num[i]<b.num[i]) return true;            else if(num[i]>b.num[i]) return false;        }        return false;    }    bool operator== (const BigInt& b) const    {        return !(*this<b) && !(b<*this);    }    bool operator>= (const BigInt& b) const    {        return b<*this || *this==b;    }    void print()    {        if(length) printf("%lld",num[length-1]);        else printf("0");        for(int i = length-2; i>=0; i--)        {            printf("%09lld",num[i]);        }        puts("");    }};void run(){    n=readIn();    kingA=readIn();    kingB=readIn();    FOR(i, 1, n)    {        A[i]=readIn();        B[i]=readIn();    }    BigInt mul = kingA; //注意高精度    FOR(i, 1, n)    {        mul*=A[i];    }    BigInt ans = 0;    FOR(i, 1, n)    {        BigInt minVal;        int minIndex = 0;        FOR(j, 1, n) //找到放在倒数第i个位置获得金币最少的大臣        {            if(vis[j]) continue;            BigInt temp = mul/A[j]/B[j];            if(temp<minVal || temp==minVal && A[j]>A[minIndex])            {                minVal = temp;                minIndex = j;            }        }        mul/=A[minIndex];        if(minVal>=ans) //这里只能是大于等于        {            ans=minVal;        }        else            break;        vis[minIndex] = true;    }    ans.print();}int main(){    run();    return 0;}

解2

设相邻两个人分别为ii+1。左手数字为aiai+1,右手数字为bibi+1。两人的金币数为wiwi+1
Pi=a1a2a3...ai
可得:

wi=Pi1bi

wi+1=Pibi+1

Pi=Pi1ai

那么
wi+1=Pi1aibi+1=wiaibibi+1

发现,若使用递推,对于相邻的二元组(i,i+1)来说,i+1只受aibi影响,因此对aibi排序就可以了,相同的值不用加其它限制条件。

参考代码

#include <cstdio>#include <cstdlib>#include <cmath>#include <cstring>#include <iostream>#include <algorithm>#include <vector>#include <string>#include <stack>#include <queue>#include <deque>#include <map>#include <set>using std::cin;using std::cout;using std::endl;#define FOR(x, f, t) for(int x = (f); x <= (t); x++)inline int readIn(){    int a;    scanf("%d",&a);    return a;}const int maxn = 1005;int n;int kingA, kingB;struct People{    int A;    int B;    void input()    {        A=readIn();        B=readIn();    }    bool operator< (const People& b) const    {        return A*B < b.A*b.B;    }} people[maxn];struct BigInt{    static const int digit = 1e9;    static const int size = 445;    std::vector<long long> num;    int length;    BigInt(int init = -1)    {        if(init==-1)        {            num.resize(size,digit-1);            length = size;        }        else        {            num.resize(size);            num[0] = init;            length = bool(init);        }    }    void operator*= (int b)    {        for(int i=0; i<length; i++)        {            num[i]*=b;        }        for(int i=0; i<length; i++)        {            num[i+1]+=num[i]/digit;            num[i]%=digit;        }        if(num[length]) length++;    }    void operator/= (int b)    {        long long div = 0;        long long mod = 0;        for(int i=length-1; i>=0; i--)        {            num[i]+=mod * digit;            mod = num[i] % b;            num[i] = num[i] / b;        }        if(length && !num[length - 1]) length--;    }    BigInt operator/ (int b)    {        BigInt ret = *this;        ret/=b;        return ret;    }    bool operator< (const BigInt& b) const    {        if(length<b.length) return true;        else if(length>b.length) return false;        for(int i=length-1; i>=0; i--)        {            if(num[i]<b.num[i]) return true;            else if(num[i]>b.num[i]) return false;        }        return false;    }    void print()    {        if(length) printf("%lld",num[length-1]);        else printf("0");        for(int i = length-2; i>=0; i--)        {            printf("%09lld",num[i]);        }        puts("");    }};void run(){    n=readIn();    kingA=readIn();    kingB=readIn();    FOR(i, 1, n)    {        people[i].input();    }    std::sort(people+1, people+1+n);    BigInt ans = 0;    BigInt mul = kingA;    FOR(i, 1, n)    {        ans = std::max(ans, mul / people[i].B);        mul*=people[i].A;    }    ans.print();}int main(){    run();    return 0;}

时间复杂度从O(n^2)降到了O(n log n)。

原创粉丝点击