The 2021 Shanghai Collegiate Programming Contest
没写完,今天困了,如果明天起来我还记得我就接着写
A. 小 A 的点面论
思路
垂直向量,高中数学,枚举就好,公式计算挺麻烦的
#include <bits/stdc++.h>
using namespace std;
int main() {
int a, b, c, d, e, f;
cin >> a >> b >> c >> d >> e >> f;
int x, y, z;
for (int x = -200; x <= 200; x ++ )
for (int y = -200; y <= 200; y ++ )
for (int z = -200; z <= 200; z ++ ) {
if (a * x + b * y + c * z == 0 && d * x + e * y + f * z == 0) {
if (x != 0 || y != 0 || z != 0) {
cout << x <<' ' << y << ' ' << z << endl;
return 0;
}
}
}
return 0;
}
B. 小 A 的卡牌游戏
思路
d p dp dp 裸题
从 a , b , c a, b, c a,b,c 三个物品中挑选,使得价值最大。如果是两个物品,显然,最优的选择应该是挑选 a , b a, b a,b 差最大的,而不是 a , b a, b a,b 里面各自最大的,例如 $$,那么三个物品的选择就是,把 a , b a, b a,b 当成一个整体,物品 c c c 作为 d p dp dp 的物品,然后在优先选择 a , b a, b a,b 差大的物品。
初始化由所求是 m a x o r m i n max or min maxormin 决定
状态:dp[i][j]
表示遍历到第
i
i
i 个 物品,选了
j
j
j 个物品
c
c
c
转移:见代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL LNF = 0x3f3f3f3f3f3f3f3f;
const int N = 5e3 + 10;
struct Node{
int a, b, c;
bool operator< (const Node &w) const {
return b - a > w.b - w.a;
}
}node[N];
LL dp[N][N];
int main() {
int n, a, b, c;
scanf("%d%d%d%d", &n, &a, &b, &c);
for (int i = 1; i <= n; i ++ )
scanf("%d%d%d", &node[i].a, &node[i].b, &node[i].c);
sort(node + 1, node + n + 1);
for (int i = 0; i <= n; i ++ )
for (int j = 0; j <= c; j ++ )
dp[i][j] = -LNF;
dp[0][0] = 0;
for (int i = 1; i <= n; i ++ )
for (int j = 0; j <= min(c, i); j ++ ) {
if (j) dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + node[i].c);
if (i - j <= b) dp[i][j] = max(dp[i][j], dp[i - 1][j] + node[i].b);
else dp[i][j] = max(dp[i][j], dp[i - 1][j] + node[i].a);
}
cout << dp[n][c] << endl;
return 0;
}
C. 小 A 的期末考试
思路
签到,注意精度
#include <bits/stdc++.h>
using namespace std;
#define FI first
#define SE second
typedef pair<int, int> PII;
PII a[110];
int main() {
int n, m;
cin >> n >> m;
double sum = 0;
for (int i = 1; i <= n; i ++ ) {
cin >> a[i].FI >> a[i].SE;
sum += a[i].SE;
}
sum /= n;
for (int i = 1; i <= n; i ++ ) {
if (a[i].FI == m) {
if (a[i].SE < 60) a[i].SE = 60;
}
else if (a[i].SE >= sum) a[i].SE = max(0, a[i].SE - 2);
}
sort(a + 1, a + n + 1);
for (int i = 1; i <= n; i ++ ) cout << a[i].SE << ' ';
return 0;
}
D. Zztrans 的班级合照
思路
d p dp dp 裸题,复杂度 O ( n 2 ) O(n ^ 2) O(n2)
通过手模,组合数学, d p dp dp 的一些知识,显然能想到这是一种升级的背包
我们把身高一样的人划分为同一组,他们组内的排序是组内人数的阶乘
那么我们先把他们每组的排序方式先计算出来,剩下的任务就是在每组都以某种排序的情况下我们怎么划分第一二排
我们让高个子的排在前面
初始化,所有方案数都是 0 0 0,当没有选择人,第二排确定 0 0 0 个人的方案数是 1 1 1
状态:dp[i][j]
表示遍历到前
i
i
i 个人,其中
j
j
j 个人在第二排,那么一定要 j >= i - j
转移:见代码,该组内有 k k k 个人到第二排
using namespace std;
#define PB push_back
typedef long long LL;
typedef vector<int> VI;
const int mod = 998244353;
const int N = 5e3 + 10;
int a[N];
int cnt[N];
LL dp[N][N];
VI res;
LL fac[N];
int main() {
int n;
cin >> n;
fac[0] = 1;
for (int i = 1; i <= n; i ++ ) fac[i] = fac[i - 1] * i % mod;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
sort(a + 1, a + n + 1, greater<int>());
for (int i = 1; i <= n; i ++ ) {
if (!cnt[a[i]]) res.PB(a[i]);
cnt[a[i]] ++;
}
LL ans = 1;
for (int i = 1; i <= n; i ++ ) {
if (cnt[i] >= 2) {
ans = ans * fac[cnt[i]] % mod;
}
}
dp[0][0] = 1;
int sum = 0;
for (int i = 0; i < res.size(); i ++ ) {
sum += cnt[res[i]];
for (int j = min(sum, n / 2); j >= sum - j; j -- ) {
for (int k = 0; k <= cnt[res[i]] && k <= j; k ++ ) {
dp[sum][j] = (dp[sum][j] + dp[sum - cnt[res[i]]][j - k]) % mod;
}
}
}
ans = ans * dp[n][n / 2] % mod;
cout << ans << endl;
return 0;
}
E. Zztrans 的庄园
思路
签到,文字游戏
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
LL pre[N], back[N];
LL a[N];
int main() {
int n, k;
cin >> n >> k;
double ans = 0;
for (int i = 1; i <= n; i ++ ) {
char op[2];
double p;
cin >> op >> p;
if (op[0] == 'D') ans += 16 * p;
else if (op[0] == 'C') ans += 24 * p;
else if (op[0] == 'B') ans += 54 * p;
else if (op[0] == 'A') ans += 80 * p;
else ans += 10000 * p;
}
printf("%.7lf\n", ans * k - 23 * k);
return 0;
}
G. 鸡哥的雕像
思路
签到,很坑
不能用前缀和来写,因为一旦出现 998244353
及其倍数,整个数列的答案都是
0
0
0,但是 998244353
及其倍数本身答案却不一定是
0
0
0
比如样例 1, 1, 998244353, 1, 1
第三个数的答案是
1
1
1 但是用前缀和答案就是
0
0
0
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 998244353;
const int N = 1e5 + 10;
LL pre[N], back[N];
LL a[N];
int main() {
int n;
scanf("%d", &n);
pre[0] = back[n + 1] = 1;
for (int i = 1; i <= n; i ++ ) {
scanf("%lld", a + i);
pre[i] = pre[i - 1] * a[i] % mod;
}
for (int i = n; i; i -- )
back[i] = back[i + 1] * a[i] % mod;
for (int i = 1; i <= n; i ++ )
printf("%lld ", pre[i - 1] * back[i + 1] % mod);
return 0;
}
H. 鸡哥的 AI 驾驶
思路
二分
#include <bits/stdc++.h>
using namespace std;
#define FI first
#define SE second
typedef long long LL;
const int N = 1e5 + 10;
struct Car{
int p, v, t;
int id;
LL pos;
}car[N], tmp[N];
int n, k;
bool cmp1(Car a, Car b) {
return a.p < b.p;
}
bool cmp2(Car a, Car b) {
return a.pos < b.pos;
}
PII seg[N];
bool check(LL t) {
for (int i = 1; i <= n; i ++ )
tmp[i].pos = tmp[i].p + 1ll * tmp[i].v * t;
sort(tmp + 1, tmp + n + 1, cmp2);
bool ok = 1;
for (int i = 1; i <= n; i ++ ) {
if (i != 1 && tmp[i - 1].pos == tmp[i].pos && tmp[i].t != tmp[i - 1].t) {
ok = 0;
break;
}
if ((i < seg[tmp[i].id].FI || i > seg[tmp[i].id].SE)) {
ok = 0;
break;
}
}
return ok;
}
int main() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i ++ ) {
scanf("%d%d%d", &car[i].p, &car[i].v, &car[i].t);
tmp[i].p = car[i].p, tmp[i].v = car[i].v, tmp[i].t = car[i].t;
}
if (k == 1) {
puts("-1");
return 0;
}
sort(car + 1, car + 1 + n, cmp1);
sort(tmp + 1, tmp + 1 + n, cmp1);
for (int i = 1; i <= n; i ++ ) tmp[i].id = car[i].id = i;
seg[1].FI = 1, seg[n].SE = n;
for (int i = 2; i <= n; i ++ )
if (car[i].t == car[i - 1].t) seg[i].FI = seg[i - 1].FI;
else seg[i].FI = i;
for (int i = n - 1; i; i -- )
if (car[i].t == car[i + 1].t) seg[i].SE = seg[i + 1].SE;
else seg[i].SE = i;
LL l = 0, r = 1e10;
while (l < r) {
LL mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
if (l == 1e10) cout << -1 << endl;
else cout << l << endl;
return 0;
}
J. Alice and Bob-1
思路
显然,在这种对局之下, A l i c e Alice Alice 会选择当前能使 a b s ( s u m a ) abs(sum_a) abs(suma) 最大的, B o b Bob Bob 也是。很容易先到正序遍历,依次选择,因为 a b s abs abs 所以还要倒序跑一次。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 5e3 + 10;
LL a[N];
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i ++ ) {
cin >> a[i];
}
sort(a + 1, a + n + 1, greater());
LL A = 0, B = 0;
for (int i = 1; i <= n; i ++ )
if (i & 1) A += a[i];
else B += a[i];
LL ans = abs(A) - abs(B);
A = B = 0;
sort(a + 1, a + n + 1);
for (int i = 1; i <= n; i ++ )
if (i & 1) A += a[i];
else B += a[i];
ans = max(ans, abs(A) - abs(B));
cout << ans << endl;
return 0;
}
K. Alice and Bob-2
思路
s e g seg seg 函数,升级的 N i m Nim Nim
本来想双 h a s h hash hash 的,但是单 h a s h hash hash 时间就已经炸了,只能改成取模溢出的 h a s h hash hash 了。
#include <bits/stdc++.h>
using namespace std;
#define PB push_back
typedef unsigned long long ULL;
typedef vector<int> VI;
const int mod = 998244353;
const int base = 131;
int cnt[26];
char ch[50];
unordered_map<ULL, int> ha;
ULL get_hash(VI t) {
ULL ans = 0;
for (int i = 0; i < t.size(); i ++ ) {
if (!t[i]) break;
ans = ans * base + t[i];
}
return ans;
}
int work(VI t) {
sort(t.begin(), t.end(), greater<int>());
ULL h = get_hash(t);
if (ha[h]) return ha[h];
if (h == 0) return 0;
set<int> seg;
for (int i = 0; i < t.size(); i ++ ) {
if (t[i]) {
t[i] --;
seg.insert(work(t));
t[i] ++;
} else break;
}
for (int i = 0; i < t.size(); i ++ ) {
if (!t[i]) break;
for (int j = i + 1; j < t.size(); j ++ ) {
if (!t[j]) break;
t[i] --, t[j] --;
seg.insert(work(t));
t[i] ++, t[j] ++;
}
}
int mex = 0;
for (auto t : seg) {
if (t == mex) mex ++;
else break;
}
ha[h] = mex;
return mex;
}
int main() {
int t;
scanf("%d", &t);
while (t -- ) {
int n;
scanf("%d", &n);
int ans = 0;
for (int i = 1; i <= n; i ++ ) {
scanf("%s", ch);
for (int j = 0; j < 26; j ++ ) cnt[j] = 0;
for (int j = 0; ch[j]; j ++ )
cnt[ch[j] - 'a'] ++;
VI tmp;
for (int i = 0; i < 26; i ++ )
if (cnt[i]) tmp.PB(cnt[i]);
sort(tmp.begin(), tmp.end(), greater<int>());
ans ^= work(tmp);
}
if (ans) puts("Alice");
else puts("Bob");
}
return 0;
}