51Nod 算法马拉松17 最好的排列 贪心求解加高精度

题目大意

我们定义“排列的价值”为所有区间的最大值之和。一个最好的排列,应当是在所有排列中,价值最高的。

现在有 N 个整数,他们是从1 N 且两两互不相同。现在要把他们重新排列一下,使得排列的价值最大。输出最大的排列价值。

N10100

解题思路

考虑一个数对答案的贡献,若这个数所在位置是x,左边比它大的最近的数的位置是 l ,右边比它大的最近的数的位置是r,那么这个数对答案的贡献为 (xl+1)(rx+1) 次。那么我们考虑贪心,我们每次都把最大的数放到中间,然后就把序列分成了两块。然后再分别贪心求解。

但是 N 10100次方,但是由于每次分成两个最多差 1 的数,所以不同的区间大小只有log级别个,那么我们递归找区间时遇到相同的就退出,最后统计每个区间的个数。然后计算答案。由于 N <script type="math/tex" id="MathJax-Element-523">N</script>很大,所以要用高精度。

程序

//YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <map>

using namespace std;
typedef unsigned long long LL;

const int MAXN = 1e3 + 5;

struct Num {
    int P[600];
};

char S[200];
map<LL,int> Map;
Num N, Get[1000], Len[1000], Work, One, Ans;
int Last[MAXN], Next[MAXN * 10], Go[MAXN * 10], tot;
int Cnt, Rd[MAXN], Ord[MAXN];

void Link(int u, int v) {
    Next[++ tot] = Last[u], Last[u] = tot, Go[tot] = v;
}

LL GetHash(Num Now) {
    LL Ans = 0;
    for (int i = Now.P[0]; i; i --) 
        Ans = Ans * 11 + Now.P[i];
    return Ans;
}

Num Dec(Num A) {
    A.P[1] --;
    int p = 1;
    while (A.P[p] < 0) {
        A.P[p] += 10;
        A.P[p + 1] --;
        p ++;
    }
    while (A.P[A.P[0]] == 0 && A.P[0] > 0) A.P[0] --;
    return A;
}

Num Div2(Num A) {
    for (int i = A.P[0]; i; i --) {
        if (A.P[i] % 2 == 1 && i > 1) A.P[i - 1] += 10;
        A.P[i] /= 2;
    }
    while (A.P[A.P[0]] == 0 && A.P[0] > 0) A.P[0] --;
    return A;
}   

Num Inc(Num A) {
    A.P[1] ++;
    int p = 1;
    while (A.P[p] > 9) {
        A.P[p] -= 10;
        A.P[p + 1] ++;
        p ++;
    }
    while (A.P[A.P[0] + 1] > 0) A.P[0] ++;
    return A;
}   

Num Add(Num A, Num B) {
    Num C;
    memset(C.P, 0, sizeof C.P);
    C.P[0] = max(A.P[0], B.P[0]);
    for (int i = 1; i <= C.P[0]; i ++) {    
        C.P[i] = C.P[i] + A.P[i] + B.P[i];
        C.P[i + 1] += C.P[i] / 10;
        C.P[i] = C.P[i] % 10;
    }
    while (C.P[C.P[0] + 1] > 0) C.P[0] ++;
    return C;
}

Num Mul(Num A, Num B) {
    Num C;
    memset(C.P, 0, sizeof C.P);
    C.P[0] = A.P[0] + B.P[0] - 1;
    for (int i = 1; i <= A.P[0]; i ++)
        for (int j = 1; j <= B.P[0]; j ++) {
            C.P[i + j - 1] = C.P[i + j - 1] + A.P[i] * B.P[j]; 
            C.P[i + j] = C.P[i + j] + C.P[i + j - 1] / 10;  
            C.P[i + j - 1] = C.P[i + j - 1] % 10;
        }
    while (C.P[C.P[0] + 1] > 0) C.P[0] ++;
    return C;
}

bool Comp(Num A, Num B) {
    if (A.P[0] < B.P[0]) return 1;
    if (B.P[0] < A.P[0]) return 0;
    for (int i = A.P[0]; i; i --) {
        if (A.P[i] < B.P[i]) return 1;
        if (B.P[i] < A.P[i]) return 0;
    }
    return 1;
}

int Div(Num Now) {
    if (Now.P[0] == 0) return 0;
    LL Hnum = GetHash(Now);
    if (Map[Hnum]) return Map[Hnum];
    Get[++ Cnt] = Now;
    Map[Hnum] = Cnt;
    Now = Dec(Now);
    Num A = Div2(Now);
    Num B;
    if (Now.P[1] % 2 == 0) B = A; else
        B = Inc(A);
    int Ord = Cnt;
    int NA = Div(A), NB = Div(B);
    if (NA) Link(Ord, NA), Rd[NA] ++;
    if (NB) Link(Ord, NB), Rd[NB] ++;
    return Ord;
}

void Dfs(int Now) {
    static int D[MAXN];
    int top = 1;
    D[top] = 1;
    while (top) {
        int Now = D[top --];
        for (int p = Last[Now]; p; p = Next[p]) {
            int v = Go[p];
            Len[v] = Add(Len[v], Len[Now]);
            Rd[v] --;
            if (!Rd[v]) D[++ top] = v;
        }
    }
}

bool Cmp(int A, int B) {
    return Comp(Get[A], Get[B]);
}

void Calc() {
    for (int i = 1; i <= Cnt; i ++) Ord[i] = i;
    sort(Ord + 1, Ord + 1 + Cnt, Cmp);
    for (int i = 1; i <= Cnt; i ++) {
        int Now = Ord[i];
        Num Sum = Div2(Mul(Add(Inc(Work), Add(Work, Len[Now])), Len[Now]));
        Get[Now] = Inc(Get[Now]);   
        Num A = Div2(Get[Now]), B;
        if (Get[Now].P[1] % 2 == 0) B = A; else
            B = Inc(A);
        Ans = Add(Ans, Mul(Mul(A, B), Sum));
        Work = Add(Work, Len[Now]);
    }
    for (int i = Ans.P[0]; i; i --)
        printf("%d", Ans.P[i]);
}


int main() {
    scanf("%s", S + 1);
    N.P[0] = strlen(S + 1);
    for (int i = N.P[0]; i; i --) N.P[i] = S[N.P[0] - i + 1] - '0';
    One.P[0] = One.P[1] = 1;

    Len[1] = One;
    Div(N);
    Dfs(0);
    Calc();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值