【状态压缩】【动态规划】电子竞技

【问题描述】
每年的北京信息学冬令营上,同学们都会在课余开展丰富多彩的电子竞技活动。经过一年又一年的实战,同学们对彼此的实力已经非常熟悉了,以至于产生出了一套实力对战评分系统。
这套系统把每个人的实力量化为一个整数。并且对于两个实力分别为RA, RB的人A 和B,这套系统把他们对战的通常的结果也量化为一个关于他们实力值的实数分数:

其中EA , EB 分别为A 和B 在对战中通常得到的对战分数。例如,若RA = 2432, RB = 2611 ,则在A 和B 的对战中A 取得的对战分数应该为

不过,今年冬令营的情况与以往略有不同,同学们分成了人数相等的两队展开了团体比赛。每队的老大给自己的队员排定一个顺序,然后两队按顺序第一个人互相对战,第二个人互相对战,以此类推。对于团体比赛中对战
的每一对选手,我们都可以根据他们的实力值计算出他们分别获得的对战分数。而两个团队在这样一种比赛顺序下的团队对战分数就是团队每一位选手在这个顺序下获得的对战分数和。
为了公平竞赛,同学们约定了一个整数L,并规定一个团队中实力为x的选手必须排在所有实力严格小于x – L 的选手之前。例如若L = 100,实力为2437 的一名选手必须排在本团队实力为2336 的选手之前,但是却不一定
排在本团队实力为2337 的选手之前。你作为一支队伍的老大,运用了种种不正当手段搞来了另外一直队伍排好的队员顺序。并且你也了解自己队伍和对方队伍每一个人的实力值。你现
在的任务就是想方设法安排本队人员的顺序,使得你的团队在对战中获得的分数最大。
【输入文件】
输入文件的第一行包含一个整数 N,代表每队的人数。
接下来的N 行,每行一个整数,表示你的每个队员的能力值。
接下来的N 行,每行一个整数,依次表示对方团队的每个队员的能力值。
最后一行包含一个整数,代表L。
【输出文件】
输出文件仅包含一个实数,表示你最大可能获得的分数。答案四舍五入保留
到小数点后六位
【样例输入】
4
2239
2412
2399
2267
2534
2429
2340
2389
100
【样例输出】
1.483574
【样例说明】
根据题目中的规定,可能的安排顺序只可能有四种,它们能获得的分数如下
        排列            分数
2399 2412 2239 2267   1.48040986
2399 2412 2267 2239   1.48357361
2412 2399 2239 2267   1.47815348
2412 2399 2267 2239   1.48131723
注意,如果没有L 的限制,排列2239 2267 2399 2412 可以得到更多的分数。
但是,由于2239 和2267 不允许2399 和2412 之前出现,这是不可能的。
【数据规模】
对于100% 的数据,有1 ≤ N ≤ 20 成立。
对于100% 的数据,每个人的能力值都在1500 到3000 之间。
对于100% 的数据,有0 ≤ L ≤ 1500 成立。
对于100% 的数据,你的对手队伍排列好的顺序中的第i 名队员的能力值与
L 的和大于等于对手队伍中排在他之后的任意一名队员的能力值。
状态压缩型动态规划。
状态:已经参加过比赛的选手集合。
转移时,枚举当前参赛的选手,若他还没有参加过比赛并且满足题目中给定的条件,那么就更新该选手加入的集合所对应的总得分。

一个优化:将选手的实力值排序(排序方向无所谓),然后每次只需要在已参加比赛的所有选手中选出一个实力值最小的选手进行判定即可(因为若该选手都能满足条件,那么比他实力值大的其他选手都一定能满足条件)。
Accode:

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cmath>
const int maxN = 30;
const int maxSTATUS = 1 << 20;

double f[maxSTATUS], cost[maxN][maxN];
//由于随着状态转移的推进,集合中含有人数在不断增加,
//所以之前的状态并不影响后面的状态,从而可以就地滚动。
int a[maxN], b[maxN], n, L, pst, ths = 1;
int cnt[2], status[2][maxSTATUS];
//cnt数组用于记录当前需要枚举的所有状态的总数,
//分别存储在status里面,减少枚举量,防止无效状态的枚举。

inline bool check(int S, int i)
{
    for (int j = n - 1, tmp = 1
         << j; tmp; tmp >>= 1, --j)
        if (tmp & S) return a[i] - L <= a[j];
    return 1;
}

int main()
{
    freopen("compete.in", "r", stdin);
    freopen("compete.out", "w", stdout);
    scanf("%d", &n);
    for (int i = 0; i < n; ++i) scanf("%d", a + i);
    for (int i = 0; i < n; ++i) scanf("%d", b + i);
    scanf("%d", &L);
    for (int i = 0; i < n; ++i)
    for (int j = i + 1; j < n; ++j)
        if (a[i] < a[j]) std::swap(a[i], a[j]);
    for (int i = 0; i < n; ++i)
    for (int j = 0; j < n; ++j)
        cost[i][j] = (1.L / (1. + pow(10.L,
            (b[j] - a[i]) / 400.L)));
	//对战的结果分数可以预处理出来以免多次计算。
	//注意要先排序再预处理这个分数。
    status[ths][cnt[ths]++] = 0;
    for (int i = 0; i < n; ++i)
    {
        std::swap(pst, ths); cnt[ths] = 0;
        for (int k = 0; k < cnt[pst]; ++k)
        {
            int Last = status[pst][k];
            double val = f[Last];
            for (int j = 0; j < n; ++j)
            if (!(Last & (1 << j)))
            if (check(Last, j))
            {
                int Now = Last | (1 << j);
                if (f[Now] < 1e-12)
                    status[ths][cnt[ths]++] = Now;
                if (val + cost[j][i] > f[Now])
                    f[Now] = val + cost[j][i];
            }
            else break;
        }
    }
    printf("%.6lf", f[(1 << n) - 1]);
    return 0;
}
再贴一个朴素的搜索程序:
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cmath>

const char fi[] = "compete.in";
const char fo[] = "compete.out";
const int maxN = 30;

int a[maxN], b[maxN], c[maxN];
bool tag[maxN];
int n, L;
double ans = 0, cost[maxN][maxN];

void init_file()
{
    freopen(fi, "r", stdin);
    freopen(fo, "w", stdout);
    return;
}

inline bool cmp(const int &a, const int &b)
{return a > b;}

void readdata()
{
    scanf("%d", &n);
    for (int i = 0; i < n; ++i)
        scanf("%d", a + i);
    for (int i = 0; i < n; ++i)
        scanf("%d", b + i);
    scanf("%d", &L);
    std::sort(a, a + n, cmp);
    for (int i = 0; i < n; ++i)
    for (int j = 0; j < n; ++j)
        cost[i][j] = (1.L / (1. + pow(10.L,
            (b[j] - a[i]) / 400.L)));
    return;
}

void print()
{
    double tmp = 0;
    for (int i = 0; i < n; ++i)
        tmp += cost[c[i]][i];
    if (tmp > ans) ans = tmp;
    return;
}

void Dfs(int i)
{
    if (i >= n) {print(); return;}
    for (int j = 0; j < n; ++j)
    {
        if (tag[j]) continue;
        if (a[j] - L > a[c[i - 1]]) break;
        c[i] = j; tag[j] = 1;
        Dfs(i + 1); tag[j] = 0;
    }
    return;
}

void work()
{
    for (int i = 0; i < n; ++i)
    if (a[0] - a[i] <= L)
    {
        c[0] = i;
        tag[i] = 1;
        Dfs(1);
        tag[i] = 0;
    }
    printf("%.6lf\n", ans);
    return;
}

int main()
{
    init_file();
    readdata();
    work();
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值