CSP认证2022.12 452分题解
A、现值计算
题解
题目简单易懂,直接写就行了。
import math
n, i = map(float, input().split())
n = int(n)
a = list(map(int, input().split()))
ans = 0.00
for j in range(n + 1):
ans = ans + math.pow(1 + i, -j) * a[j]
print(ans)
B、训练计划
题解
显然是个拓补排序的模板。正着做一遍,反着做一遍就行。
#include <bits/stdc++.h>
using namespace std;
#define N 400010
#define Log 20
#define ll long long
#define int long long
template <class T>
inline T read(T& a){
T x = 0, s = 1;
char c = getchar();
while(!isdigit(c)){ if(c == '-') s = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + (c ^ '0'); c= getchar(); }
a = x * s;
return a;
}
struct node{
int u, v, w, next;
} t[N], t1[N];
int head[N], head1[N];
int bian = 0, bian1 = 0;
inline void addedge(int u, int v, int w){
t[++bian] = (node){u, v, w, head[u]}, head[u] = bian;
return ;
}
inline void addedge1(int u, int v, int w){
t1[++bian1] = (node){u, v, w, head1[u]}, head1[u] = bian1;
return ;
}
int n, m;
int a[N];
int in[N], in1[N];
int dis[N];
void topo(){
queue <int> q;
for(int i = 1; i <= n; i++){
dis[i] = 1;
if(in[i] == 0) q.push(i);
}
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = head[u]; i; i = t[i].next){
int v = t[i].v;
dis[v] = max(dis[v], dis[u] + a[u]);
in[v]--;
if(in[v] == 0) q.push(v);
}
}
return ;
}
void topo1(){
queue <int> q;
for(int i = 1; i <= n; i++)
dis[i] = m - a[i] + 1;
for(int i = 1; i <= n; i++)
if(in1[i] == 0) dis[i] = m - a[i] + 1, q.push(i);
while(!q.empty()){
int u = q.front();
q.pop();
for(int i = head1[u]; i; i = t1[i].next){
int v = t1[i].v;
dis[v] = min(dis[v], dis[u] - a[v]);
in1[v]--;
if(in1[v] == 0) q.push(v);
}
}
return ;
}
signed main(){
cin >> m >> n;
for(int i = 1; i <= n; i++){
int x; cin >> x;
if(x == 0) continue;
addedge(x, i, 0);
in[i]++;
addedge1(i, x, 0);
in1[x]++;
}
for(int i = 1; i <= n; i++){
cin >> a[i];
// cout << in[i] << endl;
}
topo();
// cout << "66666" << endl;
bool flag = 1;
for(int i = 1; i <= n; i++){
cout << dis[i] << " ";
if(dis[i] + a[i] - 1 > m) flag = 0;
}
cout << endl;
if(!flag) return 0;
topo1();
for(int i = 1; i <= n; i++){
cout << dis[i] << " ";
if(dis[i] + a[i] > m) flag = 0;
}
return 0;
}
C、JPEG解码
样例输入
16 11 10 16 24 40 51 61
12 12 14 19 26 58 60 55
14 13 16 24 40 57 69 56
14 17 22 29 51 87 80 62
18 22 37 56 68 109 103 77
24 35 55 64 81 104 113 92
49 64 78 87 103 121 120 101
72 92 95 98 112 100 103 99
26
2
-26 -3 0 -3 -2 -6 2 -4 1 -3 1 1 5 1 2 -1 1 -1 2 0 0 0 0 0 -1 -1
样例输出
62 65 57 60 72 63 60 82
57 55 56 82 108 87 62 71
58 50 60 111 148 114 67 65
65 55 66 120 155 114 68 70
70 63 67 101 122 88 60 78
71 71 64 70 80 62 56 81
75 82 67 54 63 65 66 83
81 94 75 54 68 81 81 87
题解
个人感觉应该是历史上最简单的一个第三题了。说啥做啥就行。唯一的难度就是填图。我这里使用的是\(dfs\)填图,用一个变量表示当前方向(右下、左上),同时判一下转向的边界即可。
四舍五入直接 \(M_{i, j} + 0.5\) 再强转 \(int\) 即可实现。
#include <bits/stdc++.h>
using namespace std;
#define N 400010
#define Log 20
#define ll long long
template <class T>
inline T read(T& a){
T x = 0, s = 1;
char c = getchar();
while(!isdigit(c)){ if(c == '-') s = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + (c ^ '0'); c= getchar(); }
a = x * s;
return a;
}
double Q[9][9];
int a[N];
double M[9][9];
double M1[9][9];
int n, T;
void dfs(int x, int y, int id, int dir){
if(x > 7 || y > 7) return ;
M[x][y] = a[id++];
if(x == 0){
M[x][y+1] = a[id++];
dfs(x + 1, y, id, 2);
return ;
}
if(x == 7){
M[x][y+1] = a[id++];
dfs(x - 1, y + 2, id, 1);
return ;
}
if(y == 0){
M[x+1][y] = a[id++];
dfs(x, y + 1, id, 1);
return ;
}
if(y == 7){
M[x+1][y] = a[id++];
dfs(x + 2, y - 1, id, 2);
return ;
}
if(dir == 1){
dfs(x - 1, y + 1, id, 1);
}
else dfs(x + 1, y - 1, id, 2);
return ;
}
double f(int u){
if(u == 0) return sqrt(0.5);
else return 1;
}
signed main(){
for(int i = 0; i <= 7; i++)
for(int j = 0; j <= 7; j++)
cin >> Q[i][j];
cin >> n;
cin >> T;
for(int i = 1; i <= n; i++)
cin >> a[i];
dfs(0, 0, 1, 1);
if(T == 0){
for(int i = 0; i <= 7; i++){
for(int j = 0; j <= 7; j++){
cout << (int)M[i][j] << " ";
}
cout << endl;
}
}
for(int i = 0; i <= 7; i++){
for(int j = 0; j <= 7; j++){
M[i][j] *= Q[i][j];
}
}
if(T == 1){
for(int i = 0; i <= 7; i++){
for(int j = 0; j <= 7; j++){
cout << (int)M[i][j] << " ";
}
cout << endl;
}
}
for(int i = 0; i <= 7; i++){
for(int j = 0; j <= 7; j++){
for(int u = 0; u <= 7; u++){
for(int v = 0; v <= 7; v++){
double Pi = acos(-1);
M1[i][j] += f(u) * f(v) * M[u][v] * cos(Pi * (i + 0.5) * (double)u / 8.00) * cos(Pi * (j + 0.5) * (double)v / 8.00);
}
}
M1[i][j] /= 4.00;
}
}
for(int i = 0; i <= 7; i++){
for(int j = 0; j <= 7; j++){
M1[i][j] += 128;
// if(M1[i][j] >= 255) M1[i][j] = 255;
// if(M1[i][j] < 0) M1[i][j] = 0;
}
}
if(T == 2){
for(int i = 0; i <= 7; i++){
for(int j = 0; j <= 7; j++){
int x = M1[i][j] + 0.5;
if(x >= 255) x = 255;
if(x < 0) x = 0;
cout << x << " ";
}
cout << endl;
}
}
return 0;
}
D、聚集方差
题解
先考虑对于一个节点,我们暴力的求出其所有儿子之后,会得到一个序列。根据定义我们发现,排序之后,一个数字的贡献只跟他左右两边的两个数有关。所以我们可以使用 \(multiset\) 来实现。
考虑一个节点的所有子树,这显然是一个树上合并问题。所以不妨使用 \(dsu on tree\), 保留重儿子,将所有轻儿子直接暴力插入到重儿子的 \(multiset\) 里面,插一下则重新计算一下(因为插入一个数只会影响其两侧的两个值的贡献)。
但是由于本人写的太丑了,居然 \(T\) 了,只能拿到65分。不过网上同思路的满分做法比较多,优化的地方就是在 \(set\) 中提前插入四个极大和极小值,这样可以极大地减小讨论的成本,然后勉强卡过去(毒瘤卡常出题人)
这里献上65分 \(TLE\) 丑陋的代码。
#pragma O(2)
#include <bits/stdc++.h>
using namespace std;
#define N 1000010
#define ll long long
#define int long long
#define set multiset
template <class T>
inline void read(T& a){
T x = 0, s = 1;
char c = getchar();
while(!isdigit(c)){ if(c == '-') s = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + (c ^ '0'); c = getchar(); }
a = x * s;
return ;
}
int n, m;
struct node{
int u, v, next;
} t[N << 1];
int head[N];
int bian = 0;
inline void addedge(int u, int v){
t[++bian] = (node){u, v, head[u]}, head[u] = bian;
t[++bian] = (node){v, u, head[v]}, head[v] = bian;
return ;
}
int a[N];
int siz[N], fa[N], son[N];
void dfs1(int u, int father){
fa[u] = father;
siz[u] = 1;
int maxn = -1;
for(int i = head[u]; i; i = t[i].next){
int v = t[i].v;
if(v != fa[u]){
dfs1(v, u);
siz[u] += siz[v];
if(siz[v] > maxn){
maxn = siz[v];
son[u] = v;
}
}
}
return ;
}
ll ans[N];
int get_min(int num, multiset <int> &s){
auto it = s.lower_bound(num);
auto it1 = it;
auto it2 = it;
if(it != s.begin()) it1--;
it2++;
int ans = 0;
if(it2 == s.end()){
if(it == s.begin()){
ans = 0;
}
else{
ans = (ll)((*it) - (*it1)) * ((*it) - (*it1));
}
}
else{
if(it == s.begin()) ans = (ll)((*it) - (*it2)) * ((*it) - (*it2));
else ans = min((ll)((*it) - (*it1)) * ((*it) - (*it1)), (ll)((*it) - (*it2)) * ((*it) - (*it2)));
}
return ans;
}
void dfs(int u, multiset <int> &s){
if(son[u]) dfs(son[u], s);
s.insert(a[u]);
ll tmp = 0;
for(auto it = s.begin(); it != s.end(); it++){
auto it1 = it;
auto it2 = it;
if(it != s.begin()) it1--;
it2++;
if(it2 == s.end()){
if(it == s.begin()){
tmp = 0;
}
else{
tmp += (ll)((*it) - (*it1)) * ((*it) - (*it1));
}
}
else{
if(it == s.begin()) tmp += (ll)((*it) - (*it2)) * ((*it) - (*it2));
else tmp += min((ll)((*it) - (*it1)) * ((*it) - (*it1)), (ll)((*it) - (*it2)) * ((*it) - (*it2)));
}
// printf("6666\n");
}
// printf("u: %d tmp: %d\n", u, tmp);
for(int i = head[u]; i; i = t[i].next){
int v = t[i].v;
if(v == son[u] || v == fa[u]) continue;
set <int> s1;
dfs(v, s1);
for(auto num : s1){
auto it = s.lower_bound(num);
if(it == s.begin()){
tmp += (ll)((*it) - num) * ((*it) - num);
int x = (*it);
ll h = get_min(x, s);
tmp -= h;
s.insert(num);
h = get_min(x, s);
tmp += h;
}
else if(it == s.end()){
auto it1 = it;
it1--;
tmp += ((*it1) - num) * ((*it1) - num);
int x = (*it1);
ll h = get_min(x, s);
tmp -= h;
s.insert(num);
h = get_min(x, s);
tmp += h;
}
else{
auto it1 = it;
it1--;
int x = *it1, x1 = *it;
ll h = get_min(*it1, s);
ll h1 = get_min(*it, s);
tmp -= h; tmp -= h1;
s.insert(num);
tmp = tmp + get_min(num, s) + get_min(x, s) + get_min(x1, s);
}
}
}
ans[u] = tmp;
return ;
}
signed main(){
read(n);
for(int i = 2; i <= n; i++){
int f; read(f);
addedge(f, i);
}
for(int i = 1; i <= n; i++){
read(a[i]);
}
dfs1(1, 0);
multiset <int> s;
dfs(1, s);
for(int i = 1; i <= n; i++)
printf("%lld\n", ans[i]);
return 0;
}
E、星际网络
题解
在网上搜了一圈,居然没有找到一篇52分的题解。
题目扯了一大堆,其实就是将\([l_1, r_1]\)中的所有点向 \([l_2, r_2]\) 中的所有点连一条边,然后正反跑两次最短路就行了。
显然直接连边会爆炸。
所以就使用线段树优化建图。先将 \(1\) 号区间连向一个中继点,再将中继点连向 \(2\) 号区间,最后除以 \(2\) 就行。 (注意逆元)。
然而出题人非常的毒瘤,最短路中的边权会非常大。可以发现 \(b == 0\) 时开 \(long long\) 是可以存下的。所以此处仅实现了前 \(52\) 分的情况。
后 \(48\) 分其实就是对边权处理的一个子问题了。本人太懒就懒得写了。(做这些毒瘤题做累了)
52分代码:
#include <bits/stdc++.h>
using namespace std;
#define N 400010
#define Log 20
#define ll long long
#define y1 abdbsdhuhdwiuoh
#define int long long
template <class T>
inline T read(T& a){
T x = 0, s = 1;
char c = getchar();
while(!isdigit(c)){ if(c == '-') s = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + (c ^ '0'); c= getchar(); }
a = x * s;
return a;
}
struct node{
int u, v;
ll w;
int next;
} t[N * Log], t1[N * Log];
int head[N * Log];
int bian = 0;
inline void addedge(int u, int v, ll w){
t[++bian] = (node){u, v, w, head[u]}, head[u] = bian;
return ;
}
int head1[N * Log];
int bian1 = 0;
inline void addedge1(int u, int v, ll w){
t1[++bian1] = (node){u, v, w, head1[u]}, head1[u] = bian1;
return ;
}
int rc[N * Log], lc[N * Log];
int rootin = 0, rootout = 0;
int id = 0;
int n, m;
#define lson rc[o]
#define rson lc[o]
void buildin(int &o, int l, int r){
if(l == r){
o = l;
return ;
}
if(!o) o = ++id;
int mid = l + r >> 1;
buildin(lson, l, mid);
buildin(rson, mid + 1, r);
addedge(o, lson, 0);
addedge(o, rson, 0);
addedge1(lson, o, 0);
addedge1(rson, o, 0);
return ;
}
void buildout(int &o, int l, int r){
if(l == r){
o = l;
return ;
}
if(!o) o = ++id;
int mid = l + r >> 1;
buildout(lson, l, mid);
buildout(rson, mid + 1, r);
addedge(lson, o, 0);
addedge(rson ,o, 0);
addedge1(o, lson, 0);
addedge1(o, rson, 0);
return ;
}
void update(int o, int l, int r, int in, int end, int val, ll k, int type){
if(l > end || r < in) return ;
if(l >= in && r <= end){
if(type == 1){
addedge(o, val, k);
addedge1(val, o, k);
}
else{
addedge(val, o, k);
addedge1(o, val, k);
}
return ;
}
int mid = l + r >> 1;
update(lson, l, mid, in, end, val, k, type);
update(rson, mid + 1, r, in, end, val, k, type);
return ;
}
ll dis[N * Log * 2];
struct Point{
ll dis;
int id;
bool operator < (const Point &a) const{
return dis > a.dis;
}
} ;
bool vis[N];
const ll mod = 1e9 + 7;
ll qpow(ll a, ll b){
ll sum = 1;
while(b){
if(b & 1) sum = (sum * a) % mod;
b >>= 1ll;
a = (a * a) % mod;
}
return sum;
}
void dij(int s){
memset(vis, 0, sizeof(vis));
for(int i = 1; i <= id; i++)
dis[i] = 1e16;
// cout << dis[1] << endl;
priority_queue <Point> q;
dis[s] = 0;
q.push((Point){0, s});
while(!q.empty()){
int now = q.top().id; q.pop();
if(!vis[now]){
vis[now] = 1;
for(int i = head[now]; i; i = t[i].next){
int v = t[i].v;
if(dis[v] > dis[now] + t[i].w){
// printf("%lld\n", dis[v]);
// printf("%lld\n", t[i].w);
dis[v] = dis[now] + t[i].w;
// cout << dis[v] << endl;
if(!vis[v]) q.push((Point){dis[v], v});
}
}
}
}
return ;
}
ll dis1[N];
void dij1(int s){
memset(vis, 0, sizeof(vis));
for(int i = 1; i <= id; i++)
dis1[i] = 1e16;
priority_queue <Point> q;
dis1[s] = 0;
q.push((Point){0, s});
while(!q.empty()){
int now = q.top().id; q.pop();
// printf("now: %lld\n", now);
if(!vis[now]){
vis[now] = 1;
for(int i = head1[now]; i; i = t1[i].next){
int v = t1[i].v;
if(dis1[v] > dis1[now] + t1[i].w){
dis1[v] = dis1[now] + t1[i].w;
if(!vis[v]) q.push((Point){dis1[v], v});
}
}
}
}
return ;
}
signed main(){
// freopen("hh.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
read(n), read(m);
id = n;
buildin(rootin, 1, n);
buildout(rootout, 1, n);
for(int i = 1; i <= m; i++){
id++;
int l1, r1, l2, r2; ll a, b;
cin >> l1 >> r1 >> l2 >> r2 >> a >> b;
update(rootout, 1, n, l1, r1, id, a * qpow(2, b), 1);
update(rootin, 1, n, l2, r2, id, a * qpow(2, b), 2);
}
dij(1);
dij1(1);
for(int i = 2; i <= n; i++){
if(dis[i] >= 1e16 || dis1[i] >= 1e16) cout << -1 << " ";
else cout << ((dis[i] % mod + dis1[i] % mod) % mod * qpow(2, mod - 2)) % mod << " ";
}
return 0;
}