题目来源:清北学堂钟皓曦
D1T3 : 党
【问题描述】
你现在希望组建一支足球队,一支足球队一般来说由11人组成。这11人有四种不同的职业:守门员、后卫、中锋、前锋组成。你在组队的时候必须满足以下规则:
- 足球队恰好由11人组成。
- 11人中恰好有一名守门员,3-5 名后卫,2-5 名中锋,1-3 名前锋。
- 你需要从这11人中选出一名队长。
- 你这个足球队的价值是11人的价值之和再加上队长的价值,也就是说队长的价值会被计算两次。
- 你这个足球队的花费是11人的花费之和,你的花费之和不能超过给定的上限。
现在告诉你球员的总数,每个球员的职业、价值、花费,以及花费的上限,你希望在满足要求的情况下,达到以下目标:
- 最大化队伍的价值。
- 在最大化队伍的价值的情况下,最小化队伍的花费。
你的任务是输出这三个值:价值、花费、方案数。
【输入格式】
第一行一个正整数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%的数据,
N≤20 。- 对于60%的数据,费用上限足够大。
- 对于100%的数据,
1≤N≤500 ,所有球员的价值和花费以及花费上限均在[0,1000] 。
问题分析
其实题目描述和数据不符。对于100%的数据,费用上限都不必考虑。以下算法均按此前提。
首先,可以枚举阵容的所有可能人数分配,对于每种位置,注定要选择价值最大,花费最小的前几个人。所以对于不同种类的球员,分别按照价值降序,花费升序排序后直接取前若干人即可。然后问题只剩下了种类。不难发现,某一阵容要做等价替换,只可能替换当前阵容中价值最小,花费最大的人。记与价值最小,花费最大的人相同的人共有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%的数据,
1≤N≤1000,1≤P≤300 。 - 对于100%的数据,
1≤N≤50000,1≤P≤1000001≤X≤N,0≤K≤1000 。 - 事实上,还应该补充一个条件:对于90%的数据,保证是随机生成的。笔者注。
分析
问题的第一步:已知某一结点的命令,求这一节点及孩子兵力变化的总量。记节点
而这两个数组都可以在预处理时方便的求得:
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;
// 其祖先节点的兵力增加
} // 只加标记,复杂度为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)
在期望情况下(树是近似平衡的),算法的复杂度为
并不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
", Query(a));
}
}
return 0;
}