某【并不能AC的】模拟题部分解题报告

题目来源:清北学堂钟皓曦

D1T3 : 党

【问题描述】

你现在希望组建一支足球队,一支足球队一般来说由11人组成。这11人有四种不同的职业:守门员、后卫、中锋、前锋组成。你在组队的时候必须满足以下规则:

  1. 足球队恰好由11人组成。
  2. 11人中恰好有一名守门员,3-5 名后卫,2-5 名中锋,1-3 名前锋。
  3. 你需要从这11人中选出一名队长。
  4. 你这个足球队的价值是11人的价值之和再加上队长的价值,也就是说队长的价值会被计算两次
  5. 你这个足球队的花费是11人的花费之和,你的花费之和不能超过给定的上限。

现在告诉你球员的总数,每个球员的职业、价值、花费,以及花费的上限,你希望在满足要求的情况下,达到以下目标:

  1. 最大化队伍的价值。
  2. 在最大化队伍的价值的情况下,最小化队伍的花费。

你的任务是输出这三个值:价值、花费、方案数。

【输入格式】

第一行一个正整数N,代表可选的球员个数。
接下来N行,每行描述一个球员的信息。每行开始是一个字符串,可能的字符串有 Goalkeeper、Defender、Midfielder、Forward,分别代表该球员的职业是守门员、后卫、中锋、前锋。接下来两个数V,C,分别代表该球员的价值和花费。
最后一行一个整数,代表花费的上限。
数据保证一定存在一种解。

【输出格式】

一行三个整数,分表代表最大价值、最小花费和方案数。如果方案数超过了109,则直接输出109

【样例输入】
15 
Defender 23 45 
Midfielder 178 85 
Goalkeeper 57 50 
Goalkeeper 57 50 
Defender 0 45 
Forward 6 60 
Midfielder 20 50 
Goalkeeper 0 50 
Midfielder 64 65 
Midfielder 109 70 
Forward 211 100 
Defender 0 40 
Defender 29 45 
Midfielder 57 60 
Defender 52 45 
600 
【样例输出】
716 600 2 
【样例解释】

选择所有的五名后卫,选择价值为178,20,64,109的中锋和价值为6的前锋,两名守门员任意选择。选择价值为178的中锋作为队长。

【数据规模与约定】
  • 对于30%的数据,N20
  • 对于60%的数据,费用上限足够大。
  • 对于100%的数据,1N500,所有球员的价值和花费以及花费上限均在[0,1000]

问题分析

其实题目描述和数据不符。对于100%的数据,费用上限都不必考虑。以下算法均按此前提。

首先,可以枚举阵容的所有可能人数分配,对于每种位置,注定要选择价值最大,花费最小的前几个人。所以对于不同种类的球员,分别按照价值降序,花费升序排序后直接取前若干人即可。然后问题只剩下了种类。不难发现,某一阵容要做等价替换,只可能替换当前阵容中价值最小,花费最大的人。记与价值最小,花费最大的人相同的人共有n人,其中需要选择m人,则该阵容的方案总数为:

C(n,m)

只需要注意把总价值-花费相同的所有阵容统计在一起即可AC。

弱渣代码

#include <bits/stdc++.h>
using namespace std;

struct player {
    int val, cost;
    friend bool operator < (player a, player b) {
        if (a.val == b.val)
            return a.cost < b.cost;
        return a.val > b.val;
    }
    friend bool operator == (player a, player b) {
        return a.val == b.val && a.cost == b.cost;
    }
}G[505], D[505], M[505], F[505]; 

int g = 0, d = 0, m = 0, f = 0;
int n;
string str;
int val = -1, cost = 10000000;
long long num = 0;

inline long long C(int n, int m)
{
    long long ans = 1;
    for (register int i = n; i >= n-m+1; i--)
        ans *= i;
    for (register int i = m; i; i--)
        ans /= i;
    return ans;
}

inline long long ways(player pl[], int stp, int endp)
{
    int bef = 0, aft = 0;
    for (int i = stp; i; i--)
        if (pl[i] == pl[stp])
            bef++;
        else 
            break;
    for (int i = stp; i <= endp; i++)
        if (pl[i] == pl[stp])
            aft++;
        else
            break;
    return C(bef+aft-1, bef);
}

void sch(int gn, int dn, int mn, int fn)
{
    int maxp1, maxp2, maxp3, maxp4;
    int minc1, minc2, minc3, minc4;
    maxp1 = maxp2 = maxp3 = maxp4 = 
    minc1 = minc2 = minc3 = minc4 = 0;
    for (int i = 1; i <= gn; i++) 
        maxp1 += G[i].val, minc1 += G[i].cost;
    for (int i = 1; i <= dn; i++) 
        maxp2 += D[i].val, minc2 += D[i].cost;
    for (int i = 1; i <= mn; i++) 
        maxp3 += M[i].val, minc3 += M[i].cost;
    for (int i = 1; i <= fn; i++) 
        maxp4 += F[i].val, minc4 += F[i].cost;
    int totp = maxp1+maxp2+maxp3+maxp4, totc = minc1+minc2+minc3+minc4;
    totp += max(D[1].val, max(G[1].val, max(M[1].val, F[1].val)));
    if (val < totp || (val == totp && cost > totc)) {
        val = totp;
        cost = totc;
        num = ways(G, gn, g)*ways(D, dn, d)*ways(M, mn, m)*ways(F, fn, f);
    } else if (val == totp && cost == totc) {
        num += ways(G, gn, g)*ways(D, dn, d)*ways(M, mn, m)*ways(F, fn, f);
    }
}

int main()
{
    freopen("wosa.in", "r", stdin);
    freopen("wosa.out", "w", stdout);
    cin >> n; 
    for (int i = 1; i <= n; i++) {
        int a, b;
        cin >> str >> a >> b;
        if (str == "Goalkeeper") 
            G[++g] = {a, b};
        if (str == "Defender")
            D[++d] = {a, b};
        if (str == "Midfielder")
            M[++m] = {a, b};
        if (str == "Forward")
            F[++f] = {a, b};
    }
    sort(G+1, G+g+1);
    sort(D+1, D+d+1);
    sort(M+1, M+m+1);
    sort(F+1, F+f+1);
    for (int i = 3; i <= min(d, 5); i++)
        for (int j = 2; j <= min(5, m); j++)
            for (int k = 1; k <= min(3, f); k++)
                if (i+j+k == 10) 
                    sch(1, i, j, k);
    cout << val << " " << cost << " " << min(num, 1000000000ll) << endl;
    return 0;
}

D2T3 : 三部曲

【问题描述】

因为外来的入侵,国王决定在某些城市加派士兵。所有城市初始士兵数量为 0。当城市 i 被加派了 k 名士兵时。城市i的所有子城市需要被加派 k+1 名士兵。这些子城市的所有子城市需要被加派 k+2 名士兵。以此类推。 当然,加派士兵的同时,国王也需要不断了解当前的情况。于是他随时可能询问以城市 i 为根的子树中的所有城市共被加派了多少士兵。 你现在是国王的军事大臣,你能回答出国王的每个询问么?

【输入格式】

第一行,包含两个整数 N , P 代表城市数量以及国王的命令的数量。 第二行 N − 1 个整数,表示2 - N 号每个节点的父亲节点。 接下来的P行,每行代表国王的一个命令,命令分两种: A X K 在城市X加入K个士兵;Q X 询问以城市X为根的子树中所有士兵数量的和。

【输出格式】

对于每个Q,输出答案。

【样例输入】
7 10 
1 1 2 2 5 5 
Q 1 
A 2 1 
Q 1 
Q 2 
Q 5 
A 5 0 
Q 5 
A 3 1 
Q 1 
Q 2 

【样例输出】

0 
11 
11 
8 
10
14 
13 
【样例解释】

无。

【数据规模与约定】
  • 对于50%的数据,1N1000,1P300
  • 对于100%的数据,1N50000,1P1000001XN,0K1000
  • 事实上,还应该补充一个条件:对于90%的数据,保证是随机生成的。笔者注。

分析

问题的第一步:已知某一结点的命令,求这一节点及孩子兵力变化的总量。记节点nd的孩子数和所有孩子距离nd的深度和分别为son[nd],sigma[nd],不难发现,一次命令(nd,num)造成兵力的增加总量为:

ΔArmy=son[nd]×num+sigma[nd]()

而这两个数组都可以在预处理时方便的求得:

void dfs(int nd)
{
        son[nd] = 1;
        for (int i = head[nd]; i; i = edge[i].next) {
                dfs(edge[i].to);
                son[nd] += son[edge[i].to];
                sigma[nd] += sigma[edge[i].to]+son[edge[i].to];
        }
}

第二步优化复杂度:显然,朴素的O(NP)做法只能拿到50分,对于更大的数据就无能为力了。思考题目:一个明显的提示是当前城市k,子城市k+1,子城市的子城市k+2……,这个下推的过程显然是导致时间复杂度偏大的重要原因。什么能避免下推耗时呢?不难想到Lazy Tag思想。所谓Lazy Tag,就是当一个数据结构存在两种操作:Modify和Query时,如果Modify的复杂度较大,且Modify操作对于Query结果影响可以快速的确定,就可以使用Lazy Tag来代替直接修改。1

更进一步解释,就是维护的树的并不是简单的存下当前节点的兵力,而是维护三个值:Army[i],TagArmy[i],TagTime[i]分别表示当前节点及子节点的已调动的兵力,当前结点标记的未执行命令,和当前节点标记的未执行命令的次数。形象一点说,就是国王的军队并不负责,只在国王检查时调动兵力,且只调动国王要检查的兵力大概和我等写作业有异曲同工之妙),这样就节省了大量的时间。

具体的操作是:

inline void AddArmy(int nd, int num)
{
        tag_army[nd] += num;
        tag_time[nd]++;
        long long delta = son[nd]*num+sigma[nd];
        // 祖先节点的兵力增加值,见(*)
        for (int k = fa[nd]; k; k = fa[k])
                army[k] += delta;
        // 其祖先节点的兵力增加
} // 只加标记,复杂度为O(h),h为树高

inline int Query(int nd)
{
        if (nd == 0) return 0;
        // 到达树根(树根的父亲设为0)
        Query(fa[nd]);
        // 递归,要求下传父亲和祖先节点的所有标记
        for (int i = head[nd]; i; i = edge[i].next) {
                int to = edge[i].to;
                tag_army[to] += tag_army[nd] + tag_time[nd];
                tag_time[to] += tag_time[nd];
        }
        // 下传当前结点的标记给所有孩子,注意
        /*
        tag_army[to] += tag_army[nd] + tag_time[nd];
        下传的兵力要加上命令的次数
        */
        if (tag_time[nd] > 0)
                army[nd] += tag_army[nd]*son[nd]+sigma[nd]*tag_time[nd];
        tag_time[nd] = 0;
        tag_army[nd] = 0;
        // 如果当前节点有完成下传的标记,删除之,并记录新的部队总数
        return army[nd];
} // O(h)

在期望情况下(树是近似平衡的),算法的复杂度为O(PlgN)。但一个问题是特殊数据(链)会导致这种算法超时,即达到O(PN)的最坏复杂度。对此其实有一种利用dfs序建立线段树,用区间修改处理的方法可以AC,Θ(PlgN)。不过显然,这样的方法从思维建立上更“竞赛”,更“优美”一点。

并不AC的示例代码

#include <bits/stdc++.h>
using namespace std;

int fa[50005];
struct p {
        int to, next;
}edge[60005];
int head[50005], top = 0;
void push(int i, int j)
{
        edge[++top].to = j;
        edge[top].next = head[i];
        head[i] = top;
}
long long son[50005], sigma[50005]; //num of son(including himself), sigma of depth
long long tag_army[50005], tag_time[50005]; // lazy tag
long long army[50005];
int N, P;
inline void init()
{
        memset(fa, 0, sizeof fa);
        memset(head, 0, sizeof head);
        memset(son, 0, sizeof son);
        memset(sigma, 0, sizeof sigma);
        memset(tag_army, 0, sizeof tag_army);
        memset(tag_time, 0, sizeof tag_time);
        memset(army, 0, sizeof army);
}

void dfs(int nd)
{
        son[nd] = 1;
        for (int i = head[nd]; i; i = edge[i].next) {
                dfs(edge[i].to);
                son[nd] += son[edge[i].to];
                sigma[nd] += sigma[edge[i].to]+son[edge[i].to];
        }
}

inline void AddArmy(int nd, int num)
{
        tag_army[nd] += num;
        tag_time[nd]++;
        long long delta = son[nd]*num+sigma[nd];
        for (int k = fa[nd]; k; k = fa[k])
                army[k] += delta;
} // just add tag

inline int Query(int nd)
{
        if (nd == 0) return 0;
        Query(fa[nd]);
        for (int i = head[nd]; i; i = edge[i].next) {
                int to = edge[i].to;
                tag_army[to] += tag_army[nd] + tag_time[nd];
                tag_time[to] += tag_time[nd];
        }
        if (tag_time[nd] > 0)
                army[nd] += tag_army[nd]*son[nd]+sigma[nd]*tag_time[nd];
        tag_time[nd] = 0;
        tag_army[nd] = 0;
        return army[nd];
}

int main()
{
        freopen("truetears.in", "r", stdin);
        freopen("truetears.out", "w", stdout);
        init();
        scanf("%d%d", &N, &P);
        for (int i = 2; i <= N; i++) {
                scanf("%d", &fa[i]);
                push(fa[i], i);
        }
        dfs(1);
        for (int i = 1; i <= P; i++) {
                char cmd;
                int a, b;
                //scanf("%c", &cmd);
                cin >> cmd;
                if (cmd == 'A') {
                        scanf("%d%d", &a, &b);
                        AddArmy(a, b);
                } else {
                        scanf("%d", &a);
                        //cin >> a;
                        printf("%d\n", Query(a));
                }
        }
    return 0;
}

转载于:https://www.cnblogs.com/ljt12138/p/6684369.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值