扩大
缩小

牛客周赛 Round 62 全部题

比赛链接

A 小红的字符移动

statement: 有一个 5 个字符的串,交换前两个字符输出。

solution: 直接模拟即可。由于过于简单就不放代码了。

B 小红的数轴移动

statement: 对于序列 $a_1,a_2,...,a_n$,小红一开始在 $x$,每次向原点的方向移动 $a_i$,如果到原点就停止。问一共走了多少路程。

solution: 依旧按题意模拟即可。

Code

int n, x, a[100010], ans;
int main(){
    cin >> n >> x;
    for(int i = 1; i <= n; i++)
        cin >> a[i];
    int i = 1;
    while(x != 0){
        if(x > 0) ans += a[i], x -= a[i];
        else ans += a[i], x += a[i];
        i++;
        if(i > n) break;
    }
    cout << ans << endl;
    return 0;
}

C 小红的圆移动

statement: 

平面上有 $n$ 个圆。小红可以进行任意次操作,每次操作可以选择一个圆,将它向任意方向移动若干距离。该操作的代价为该圆面积乘以移动的距离。

小红希望最终包含原点的圆数量不超过 $k$,请你帮小红算出她操作的最小总代价。$1 \le k \le n \le 10^5$。

Solution:

先算出每个圆的面积,第 $i$ 个圆需要移动的距离等于 $\max(0,r_i-\sqrt{x_i^2+y_i^2})$。

相乘后排序,把最小的 $n-k$ 个数加起来即可。

Code

int n, k;
double x[100010], y[100010], r[100010], S[100010], ans;
int main(){
    cin >> n >> k;
    for(int i = 1; i <= n; i++){
        cin >> x[i] >> y[i] >> r[i];
        S[i] = 1.0 * acos(-1) * r[i] * r[i];
        S[i] = S[i] * (r[i] - sqrt(x[i] * x[i] + y[i] * y[i]));
        if(S[i] < 0.0) S[i] = 0.0;
    }
    sort(S + 1, S + n + 1);
    for(int i = n - k; i >= 1; i--)
        ans += S[i];
    printf("%.10lf\n", ans);
    return 0;
}

D 小红的树上移动

statement: 

小红拿到了一棵 $n$ 个节点的树,初始所有节点均为白色。小红初始站在1号节点,并将1号节点染红。小红将不断进行移动直到停止:

1. 若当前节点的邻点中存在白色节点,则小红随机移动到某个相邻的白色节点,并将该节点染红。

2. 若当前节点的邻点中不存在白色节点,则停止移动。

请你帮小红求出最终红点数量的期望。答案在模 $10^9+7$ 意义下计算。$1 \le n \le 10^5$。

solution: 

观察到从 $1$ 出发到每个点的路径是唯一的。所以,答案等于每个点到达的概率(设为 $p_i$)之和

以 $1$ 为根,建一棵有根树。然后对于每个节点 $i$,统计它的儿子数量 $sz(i)$。这样对于节点 $i$ 的儿子 $j$,有 $p_j=\frac{p_i}{sz(i)}$。

DFS 一遍,算概率的时候求个逆元,最后累加起来就是答案了。

Code:

#define int long longconst int INF = 1e9, MOD = 1000000007;
int n, s[100010], ans;
vector <int> e[100010];
int qpow(int a, int b){
    int t = 1;
    while(b){
        if(b & 1) t = t * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return t;
}
void dfs(int x, int fa, int p){
    ans = (ans + p) % MOD;
    for(int i : e[x])
        if(i != fa)
            s[x]++;
    int t = qpow(s[x], MOD - 2);
    for(int i : e[x])
        if(i != fa){
            dfs(i, x, p * t % MOD);
        }
}
signed main(){
    cin >> n;
    for(int i = 1, u, v; i < n; i++){
        cin >> u >> v;
        e[u].push_back(v);
        e[v].push_back(u);
    }
    dfs(1, 0, 1);
    cout << ans << endl;
    return 0;
}

E&F 小红的中位数查询

statement: 

有一个序列 $a_1,a_2,...a_n$,$q$ 次查询,每次查询一个长度为奇数的子区间 $l,r$ 的中位数。

$1 \le n,q \le 10^5$,$1 \le a_i \le 10^9$。

solution 1: 

先把 $a_i$ 离散化(但要输出的是原值)假设权值的区间是 $1 \sim m$,然后对于每个权值记 vector:$v_1,v_2,...,v_m$,记录所有等于它的数的下标。

一种暴力的思想是,把 $i=1\sim m$ 每个数都枚举一次,每次二分求出区间 $[l,r]$ 内数字 $i$ 的个数。

然后不断累加,直到数字个数大于等于 $\frac{r-l+2}{2}$。

但是这么做的时间复杂度是 $\mathcal{O}(n^2 \log n)$ 的,太慢了。

但我们可以考虑分块!以 $\sqrt{m}$ 为阈值,假设块长 $L$。

大块的部分记录权值分别在 $[1,L],[L+1,2L],...$ 之中的数字个数随着下标增加的前缀和。

小块的部分和上面的暴力想法一样。

对于每次询问,我们一个个大块跑过去,判断加到哪个块的时候,累计的数字个数大于等于 $\frac{r-l+2}{2}$,记录这个块的左右端点 $p,p+L-1$。

然后我们让 $i$ 从 $p+L-1$ 往 $p$ 减少,每次删掉区间 $[l,r]$ 中 $i$ 的出现次数,直到不满足数字个数大于等于 $\frac{r-l+2}{2}$,这个区间的中位数就是 $i$ 了。

时间复杂度 $\mathcal{O}(n \sqrt{n} \log n)$,可以勉强通过。

Code

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int INF = 1e9;
int n, m, q, sz, blk[100010], mm, s[410][100010]; int a[100010], b[100010], t[400010], l[100010], r[100010]; vector <int> v[100010];
signed main(){ std::ios::sync_with_stdio(
false); std::cin.tie(0); cin >> n >> q; for(int i = 1; i <= n; i++) cin >> a[i], b[i] = a[i]; sort(b + 1, b + n + 1); m = unique(b + 1, b + n + 1) - b - 1; sz = (int)sqrt(m); for(int i = 1; i <= n; i++){ a[i] = lower_bound(b + 1, b + m + 1, a[i]) - b; blk[i] = (i - 1) / sz + 1; v[a[i]].push_back(i); } mm = blk[n]; for(int i = 1; i <= n; i++) s[blk[a[i]]][i]++; for(int i = 1; i <= mm; i++) for(int j = 1; j <= n; j++) s[i][j] += s[i][j - 1]; for(int i = 1, now, L, R; i <= n; i++){ cin >> l[i] >> r[i], now = 0; for(int j = 1; j <= mm; j++){ now += s[j][r[i]] - s[j][l[i] - 1]; if(now > (r[i] - l[i] + 1) >> 1){ L = (j - 1) * sz + 1; R = j * sz; break; } } if(now <= (r[i] - l[i] + 1) >> 1) continue; for(int j = R, p1, p2; j >= L; j--){ p2 = upper_bound(v[j].begin(), v[j].end(), r[i]) - v[j].begin(); p1 = lower_bound(v[j].begin(), v[j].end(), l[i]) - v[j].begin(); now -= p2 - p1; if(now <= (r[i] - l[i] + 1) >> 1){ printf("%d\n", b[j]); break; } } } return 0; }

Solution 2

莫队 + 值域分块,$\mathcal{O}(n \sqrt{n})$

Solution 3

主席树,暂时没写,可以去看其它博客,$\mathcal{O}(n \log^2 n)$。

G 小红的数轴移动(二)

statement: 

对于序列 $a_1,a_2,...,a_n$,小红一开始在 $x$,每次向原点的方向依次移动 $a_i$,如果到原点就停止。

你现在可以改变 $a_i$ 的顺序,问最少要走多少路程,并输出方案。

$1 \le n,a_i \le 100$。

solution

直接模拟退火,多调几次参数就过了。

下面这份代码可能你再交不一定是满分,但多交几次总可以的(

Code

#include <bits/stdc++.h>
using namespace std;
mt19937 rnd(time(nullptr));
const double eps = 1e-8;
 
int n, X, now, A[110], P[110], ANS = 100000, a[110], p[110], b[110];
double T;
 
void sa(){
    for(int i = 1; i <= n; i++) p[i] = i;
    shuffle(p + 1, p + n + 1, rnd);
    for(int i = 1; i <= n; i++) a[i] = A[p[i]];
    double T = 3000.0, d;
    int ss, delta, sum, ans = 100000;
    while(T > eps){
        T *= 0.98;
        sum = 0;
        int x = rnd() % n + 1, y = rnd() % n + 1;
        swap(a[x], a[y]), swap(p[x], p[y]);
        now = X;
        for(int i = 1; i <= n; i++){
            if(now == 0) break;
            if(now > 0) now -= a[i], sum += a[i];
            else now += a[i], sum += a[i];
        }
        delta = ans - sum;
        if(delta > 0){
            ans = sum;
        }
        else{
            if(exp(1.0 * (-delta) / T) * RAND_MAX > rand())
                swap(a[x], a[y]), swap(p[x], p[y]);
        }
    }
    if(ANS > ans){
        ANS = ans;
        for(int j = 1; j <= n; j++)
            P[j] = p[j];
    }
}
 
int main(){
    double beg = clock();
    cin >> n >> X;
    for(int i = 1; i <= n; i++)
        cin >> a[i], A[i] = a[i], p[i] = i;
    while((clock() - beg) / CLOCKS_PER_SEC < 0.84)     // 卡时
        sa();
    cout << ANS << endl;
    for(int i = 1; i <= n; i++)
        cout << P[i] << " ";
    cout << endl;
    return 0;
}
posted @ 2024-09-29 20:31  HoshizoraZ  阅读(191)  评论(2编辑  收藏  举报