伯乐论坛网

搜索
查看: 125|回复: 3

Codeforces Round #847 (Div. 3) A

[复制链接]

4

主题

4

帖子

12

积分

新手上路

Rank: 1

积分
12
发表于 2023-2-1 05:44:00 | 显示全部楼层 |阅读模式
A. Polycarp and the Day of Pi 签到

题意:
求出一个数字的前几位和 \pi 相同。
分析:
最后一个样例就是 \pi ,直接抄过来,扫一遍。
代码:
string pi = "314159265358979323846264338327";

void solve()
{
     string s;
     cin >> s;
     int ans = 0;
     for (int i = 0; s; i++ ) {
         if (s != pi) break;
         else ans++ ;
     }
     cout << ans << endl;

}
B. Taisia and Dice 构造

题意:
已知道所有骰子的数量,骰子的点数总和,以及拿走一个最大值的点数总和,请构造所有骰子的点数。
分析:
可以推算出最大值,那么构造方法就是一直用最大值来构造,剩下的用 1 来弥补。
代码:
void solve()
{
     cin >> n >> s >> r;
     int mx = s - r;
     while (s > 0 && n > 0 && s >= n) {
         if (s - mx >= n) cout << mx << " ", s -= mx;
         else cout << s - n + 1 << " ", s -= (s - n + 1);
         n-- ;
     }
     
     for (int i = 1; i <= n; i++ ) cout << 1 << " ";
     cout << endl;
}
C. Premutation

题意:
一个长度为 n 的排列,将 n 个这样的排列每个去掉一位然后打乱。根据打乱后的排列求出原排列。
分析:
分析很简单,但是代码写了一坨大便。
每个数能够出现的位置就是 i  \space or \space i+1 。假如我们能够得知第一位的数是什么,就可以顺推出下一位,因为下一位出现的两个数的其中一个已经在前一位被推出来了。
第一位的判断依据就是出现次数最多的数,然后依次顺推。
代码:
void solve()
{
     cin >> n;
     for (int i = 1; i <= n; i++ )
         for (int j = 1; j < n; j++ )
             cin >> a[j];

     int last = -1;
     for (int j = 1; j < n; j++ ) {
         int t1 = -1, t2 = -1;
         int cnt1 = 0, cnt2 = 0;
         for (int i = 1; i <= n; i++ ) {
             if (t1 == -1) t1 = a[j], cnt1++ ;
             else if (t1 == a[j]) cnt1++ ;
             else if (t2 == -1 && a[j] != t1) t2 = a[j], cnt2++ ;
             else if (t2 == a[j]) cnt2++ ;
         }
         
         if (last == t1) {
             ans[j] = last;
             last = t2;
         } else if (last == t2) {
             ans[j] = last;
             last = t1;
         } else {
             if (cnt1 >= cnt2) ans[j] = t1, last = t2;
             else ans[j] = t2, last = t1;
         }

     }
     ans[n] = last;
     for (int i = 1; i <= n; i++ ) cout << ans << " \n"[i == n];

}
D. Matryoshkas

题意:
给定一个集合,将该集合分成若干个由连续正整数组成的集合,求分成集合的数量的最小值。
分析:
贪心策略是每次都选择最长的连续整数作为一组,代码的话用 map 从小到大遍历所有出现过的数,每个数对答案的贡献是这个数出现的次数减去上一个数产生的次数。
代码:
void solve()
{
     cin >> n;
     map<int, int> mp;
     int mx = 0;
     for (int i = 1; i <= n; i++ ) cin >> a, mp[a]++ ;
     
     int last = -1, cur = 0, ans = 0;
     for (auto [x, y] : mp) {
         if (last + 1 != x) cur = 0;
         ans += max(0, y - cur);
         last = x;
         cur = y;
     }
     cout << ans << endl;
}
E. Vlad and a Pair of Numbers

题意:
给定一个数 n ,求出两个数字 a, b 满足 a \oplus b = \frac{a + b}{2} 。
分析:
对于 n 的每一位,如果这一位是 1 ,说明 a, b 在这一位上一个是 1 ,一个是 0 。 1 给谁是不影响答案的,我们默认 1 都给 a 。
对于 n 的某些位为 0 ,我们需要考虑另一个限制条件。想让两数之和右移一位变成原数,相当于最终的相加的结果是所有 1 往右移动一位,那么只需要在所有 n 为 1 的位的小一位的地方填上两个 1 ,此时这一位的 1 就可以向左移动一位。
样例解释
n: 0100100
a: 0110110
b: 0010010
a+b: 1001000
n<<1: 1001000
a+b>>1: 100100 = n对于每一个1的小一位如果还是1,则无法将这个两个1向左移动,无解。
代码:
void solve()
{
     cin >> n;
     int a = 0, b = 0;
     if (n & 1) {
         cout << -1 << endl;
         return ;
     }
     for (int i = 1; i <= 30; i++ ) {
         if (n >> i & 1) {
             if (n >> (i - 1) & 1) {
                 cout << -1 << endl;
                 return ;
             }
         }
     }

     for (int i = 1; i <= 30; i++ ) {
         if (n >> i & 1) {
             a += 1 << i;
             a += 1 << (i - 1);
             b += 1 << (i - 1);
         }
     }

     cout << a << " " << b << endl;
}

F. Timofey and Black-White Tree

题意:
给定一颗 n 个节点的树,刚开始所有节点都是白色的,但是只有一个节点 c 是黑色的。每次操作给定一个节点染黑,整颗树的权值定义为两个黑色节点的距离的最小值。求 [1, n - 1] 次操作每次的权值。
分析:
来自jiangly的思路,可能说的不太清楚但是真的很巧妙。
不知道自己在胡言乱语什么,我自己都听不懂了。
我们以刚开始的黑色的点为根,定义数组 dist_i 表示i点,在它的祖先节点中的的第一个黑点的距离。
在每一次操作加入一个新的节点的时候,是不会影响到这个点到祖先的路径上的点的 dist_i 值的,只会影响这个以这个点为子树的 dist_i 值。第一种做法就是直接暴力宽搜遍历子树,每一次的答案都取一次最小值。但是这样的时间复杂度是 O(n^2) ,会直接被树链卡住。
这道题的精华就在于对 ans 的把握。仔细思考我们会发现每一次操作后, ans 的大小都会减少成最多 \frac{n}{i} ,我们每次都往下宽搜一个不超过 ans 的距离,其中第i轮操作后的ans一定是小于等于 \frac{n}{i} 的。这些超过ans的节点的 dist_i 和 ans 进行取 min 更新是没有意义的。因此,每次更新的距离是一个 n ,\frac{n}{2}, \frac{n}{3}, \frac{n}{4} \dots, \frac{n}{n}  级别的东西,但是它和实际的复杂度没有关系。
但是宽搜x层不代表只有 x 个点被更新了。我们需要想想对于每一个点会被更新多少次。我自己也有点不太理解这个根号的问题,目前的感性理解是这样:(胡言乱语)
对于距离小于根号级别的更新,就算更新 n 次,他的数量级也不会很大,因此不做考虑。我们只考虑更新距离大于根号级别的次数,这样的次数一定是少于根号级别的。
考虑每个点会被更新多少次。
假如有根号级别个点,他的周围距离为根号范围以内都没有黑点(这样的状态下,需要每次都更新根号次)。此时所有点的数量为根号乘根号,是大于 n 级别的点的数量。因此可以发现更新距离大于根号级别的次数是少于根号级别的。并且每一次在更新后,答案也会变成根号级别,所以实际的更新应该是远少于这个值的。
结果就是:在前根号次更新中,答案会被降至根号级别。最坏情况下的更新量级是 On ,因此复杂度为 On\sqrt n 。更新距离为根号级别以下的次数为 On ,因为有距离限制,每个点的更新次数一定小于等于根号,因此复杂度还是 On\sqrt n 。
总结:每一个点被更新的次数不会超过根号次。
代码:抄的jiangly
int n, m, a[N], c[N];
int h[N], e[M], ne[M], idx;
int dist[N];

void add(int a, int b) {
     e[idx] = b, ne[idx] = h[a], h[a] = idx++ ;
}

void solve()
{
     cin >> n >> c[1];
     for (int i = 1; i <= n; i++ ) dist = INF, h = -1;
     for (int i = 2; i <= n; i++ ) cin >> c;
     for (int i = 1; i < n; i++ ) {
         int a, b;
         cin >> a >> b;
         add(a, b), add(b, a);
     }

     

     int ans = n;
     for (int i = 1; i <= n; i++ ) {
         ans = min(ans, dist[c]);
         if (i > 1) cout << ans << " \n"[i == n];
         queue<int> q;
         q.push(c);
         dist[c] = 0;

         while (q.size()) {
             auto t = q.front();
             q.pop();

             for (int i = h[t]; ~i; i = ne) {
                 int j = e;
                 if (dist[j] > dist[t] + 1 && ans > dist[t] + 1) {
                     dist[j] = dist[t] + 1;
                     q.push(j);
                 }
             }
         }
     }

}
回复

使用道具 举报

1

主题

4

帖子

5

积分

新手上路

Rank: 1

积分
5
发表于 2023-2-1 05:44:54 | 显示全部楼层
什么时候跳舞
回复

使用道具 举报

2

主题

3

帖子

7

积分

新手上路

Rank: 1

积分
7
发表于 2023-2-1 05:45:14 | 显示全部楼层
G 的难度也不高啊
回复

使用道具 举报

1

主题

3

帖子

5

积分

新手上路

Rank: 1

积分
5
发表于 2023-2-1 05:46:01 | 显示全部楼层
F把我榨干了
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Copyright © 2001-2013 Comsenz Inc.Powered by Discuz!X3.4
快速回复 返回顶部 返回列表