文章目录

  • Codeforces补题记录(1)
    • 1、Codeforces Round #632 (Div. 2)(2020.4.11)
      • A、Little Artem
      • B、Kind Anton
      • *C、Eugene and an array
    • 2、Educational Codeforces Round 85 (Rated for Div. 2)(2020.4.20)
      • A、Level Statistics
      • B、Middle Class
      • *C、Circle of Monsters
      • *D、Minimum Euler Cycle
    • 3、Codeforces Round #633 (Div. 2)(2020.4.28)
      • A、Filling Diamonds
      • B、Sorted Adjacent Differences
      • *C、Powered Addition
      • D、Edge Weight Assignment
    • 4、Codeforces Round #635 (Div. 2)(2020.4.29-2020.4.30)
      • A、Ichihime and Triangle
      • B、Kana and Dragon Quest game
      • *C、Linova and Kingdom
        • Cow Acrobats(POJ 3045)
      • *D、Xenia and Colorful Gems
        • [Find My Family](https://nanti.jisuanke.com/t/44322)

Codeforces补题记录(1)

实在受不了每次比赛掉分的感觉了。于是怒开始补CF。

大概两到三天补一场,题目不一定全补。

和dp里一样,打星号的题比较重要。

1、Codeforces Round #632 (Div. 2)(2020.4.11)

A、Little Artem

看起来很吓人,后来发现只要染一个角就好了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{int t; cin >> t;while (t--){int n, m; scanf("%d%d", &n, &m);printf("W");for (int i = 2; i <= m; ++i) printf("B");cout << endl;for (int i = 2; i <= n; ++i){for (int j = 1; j <= m; ++j)printf("B");cout << endl;}}return 0;
}

B、Kind Anton

从后往前操作,如果 b b b数组中的对应元素比 a a a中的小,那么 a a a中前面一定要有 − 1 -1 −1;如果对应元素比 a a a中的元素大那么前面一定要有 1 1 1

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{int t; cin >> t;while (t--){int n; cin >> n;static int a[100010], b[100010];for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);for (int i = 1; i <= n; ++i) scanf("%d", &b[i]);bool plus = 0, minus = 0;bool is = 1;for (int i = 1; i <= n; ++i){if (b[i] > a[i])if (!plus){is = 0; break;}if (b[i] < a[i])if (!minus){is = 0; break;}if (a[i] == 1) plus = 1;if (a[i] == -1) minus = 1;}if (!is) printf("NO\n");else printf("YES\n");}return 0;
}

*C、Eugene and an array

如果真的去打这场的话从这题开始等死。

考虑前缀和,如果 [ l , r ] [l,r] [l,r]是好序列那么 s r = s l − 1 s_r=s_{l-1} sr​=sl−1​。然后就变成找一些区间,使里面的前缀和数字互不相同。

到这里都能考虑到,实现只会 O ( n 2 ) O(n^2) O(n2)。

去网上看题解,用了 m a p map map。

具体实现是每次读个数进来,加到前缀和里。记最长区间左端点为 p p p。

如果这个前缀和在前面已经出现过了,那我们就尝试更新一下 p p p,取更靠近当前位置的那个。

然后每次把区间长度加到 a n s ans ans上。

(这是人想的?)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{int n; cin >> n;map <ll, ll> mp;mp[0] = 0;ll s = 0, p = -1, ans = 0;for (int i = 1; i <= n; ++i){int x; scanf("%d", &x);s += x;if (mp.count(s)) p = max(p, mp[s]);ans += i - p - 1;mp[s] = i;}cout << ans << endl;return 0;
}

(2020.4.30后补)

最近 m a p map map用得也算比较多了,熟练一点之后再回头看这题发现这个东西就比较显然。 m a p map map只是听名字吓人,什么一对一的键值配对,听着就头大。在实际操作中其实不要想那么复杂,把 m a p map map看成一个数组就可以了。这样再回过头去看 m a p map map的一些定义就非常容易理解了,比如键值不能重复——你见过哪个数组有两个一样的下标吗?并且由于是红黑树实现, m a p map map自身还提供了快速的查找,并且还能自定义容器内部的排序方法,甚至支持双下标索引(就是把键值定义成pair)。这就是一个除了修改元素有点慢(普通数组是 O ( 1 ) O(1) O(1), m a p map map是 O ( l o g n ) O(logn) O(logn))的超级数组,而且在元素个数很小的时候这个 l o g log log几乎可以不计。所以效率巨高。

在某些题中用 m a p map map代替数组还有许许多多的好处。比如这题,假如我们定义一个数组 a [ i ] a[i] a[i],表示前缀和为 i i i时的最大下标,那么这个题解可以说是显而易见的,就是在不断的读入过程中更新 a [ i ] a[i] a[i]的值和答案,最后直接输出答案。但是问题出现在没有那么大的空间给你去开这个 a [ ] a[] a[],因为这个数字最大可以达到 2 e 14 2e14 2e14并且还会出现负下标,先不说越界的问题,这空间肯定是炸到月球上去了。然后 m a p map map这时就充当了 a [ ] a[] a[],这里的 m a p map map可以看成不连续下标的数组。因为这里的 n n n最多只有 2 e 5 2e5 2e5,也就是说不相同的前缀和最多只有 2 e 5 2e5 2e5个, a [ ] a[] a[]数组里面除了这 2 e 5 2e5 2e5个剩下的空间都是完全没用的。这时我们用 m a p map map就可以把多余的下标全部压掉。

2、Educational Codeforces Round 85 (Rated for Div. 2)(2020.4.20)

A、Level Statistics

还好没打这场,不然A就要WA到吐

看懂题之后其实就很简单,模拟一下就行了。

plays硬生生看成players还觉得我这么完美的程序哪里错了 佛了

(要不是去翻了提交记录下的输出对比我还真看不出是这里看错了)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{int t; cin >> t;while (t--){int n; cin >> n;int prevp = 0, prevc = 0;bool flag = 0;for (int i = 1; i <= n; ++i){int p, c; scanf("%d%d", &p, &c);if (p - prevp < c - prevc || p < prevp || c < prevc)flag = 1;prevp = p; prevc = c;}if (!flag) cout << "YES" << endl;else cout << "NO" << endl;}return 0;
}

B、Middle Class

很好想,就是从大到小排个序,然后输出一下最大人数就行了。

(看错数据范围开了int×1)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
bool cmp(int a, int b)
{return a > b;
}
int main()
{int t; cin >> t;while (t--){int n, x; cin >> n >> x;static int a[100010];for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);sort(a + 1, a + 1 + n, cmp);double aver = 0; ll sum = 0, ans = -1;for (int i = 1; i <= n; ++i){sum += a[i];aver = (double)sum / i;if (aver < x){ans = i - 1; break;}}if (ans == -1) ans = n;cout << ans << endl;}return 0;
}

*C、Circle of Monsters

这题的T把我吓死了。我还以为要想个 O ( l o g n ) O(logn) O(logn)的算法,结果发现n总数不超过 3 e 5 3e5 3e5…

那没事了。

想倒是还有点难想。一开始以为是去找能够消掉的若干链,每次枚举第一次打哪个链头的第一个元素,然后再加一些特判。

后来发现写出来样例都过不了…给的样例就是个反例。

但是仔细一想发现我一开始那个错误方法里面在枚举第一个打哪个链头元素的方法其实不需要先找链,直接在n里面暴搜就可以了,而且复杂度还是 O ( n ) O(n) O(n)的。

于是写完之后愉快地WA了。

然后又百思不得其解,想了半天又去翻输出对比。

然后发现数据范围又双叒叕看错了。

再改。又错了。再改。还错。再改。AC了。

佛了。第一次WA是没开ll,第二次WA是没改INF,第三次WA是改了INF没把const int改成const ll…

思路就是一个预处理。先把不管哪次枚举都会重复累加的东西都算好,这样就不要每次计算的时候再去扫一遍整条链了。

对于某个输入,每次都需要重复累加的是 Σ ( a [ i ] − b [ ( i − 1 + n ) % n ] ) \Sigma(a[i]-b[(i-1+n)\%n]) Σ(a[i]−b[(i−1+n)%n])。用文字很难表达为什么是这个东西,大概就是除去第一个打的怪物至少需要这么多子弹。意会一下。

然后对于某一项为负的情况就变成加0。

不过我这程序下标是从1开始的,后面那个模要稍微改一改。具体见程序。

然后我们枚举第一次打某个怪物的时候只要把它的贡献加到我们预先算好的cost里,取所有情况的最小值就行了。

对于 a [ i ] − b [ ( i − 1 + n ) % n ] < 0 a[i]-b[(i-1+n)\%n]<0 a[i]−b[(i−1+n)%n]<0,贡献为 a [ i ] a[i] a[i]。因为这个在cost的计算里是被当作爆炸消掉的,所以把整个血量都加上去。

对于 a [ i ] − b [ ( i − 1 + n ) % n ] > 0 a[i]-b[(i-1+n)\%n]>0 a[i]−b[(i−1+n)%n]>0,贡献为 b [ ( i − 1 + n ) % n ] b[(i-1+n)\%n] b[(i−1+n)%n],就是把少打的补上。

下面上代码。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 3e5 + 10;
const ll INF = 1e18;//以后ll的INF全部搞成1e18吧...这WA得太难受了
int main()
{int t; cin >> t;while (t--){int n; scanf("%d", &n);static ll a[MAXN], b[MAXN];for (int i = 1; i <= n; ++i)scanf("%lld%lld", &a[i], &b[i]);ll cost = 0;for (int i = 1; i <= n; ++i){ll w = a[i] - b[(i - 2 + n) % n + 1];cost += w > 0? w: 0;}ll ans = INF;for (int i = 1; i <= n; ++i){ll w = a[i] - b[(i - 2 + n) % n + 1];ans = min(ans, cost + (w > 0? b[(i - 2 + n) % n + 1]: a[i]));//加上贡献}cout << ans << endl;}return 0;
}

*D、Minimum Euler Cycle

这种题永远是我的克星。

就是一个贪心找规律+数列输出。

一开始看完题以为是 1 , 2 , 1 , 3 , 2 , 3 , 1 , 4 , 2 , 4 , 3 , 4 1,2,1,3,2,3,1,4,2,4,3,4 1,2,1,3,2,3,1,4,2,4,3,4这种规律,然后写了半天交上去WA了…

后来一看发现 1 , 2 , 1 , 3 , 1 , 4 , 2 , 3 , 2 , 4 , 3 , 4 1,2,1,3,1,4,2,3,2,4,3,4 1,2,1,3,1,4,2,3,2,4,3,4明明更小,然后又写了半天交上去就对了。

然后输出感觉也没啥技巧,就嗯输。注意最后一个元素是1.

膜了一下dls的代码,发现用前缀和+lowerbound确实比较方便。

上代码。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{int t; cin >> t;while (t--){ll n, l, r; scanf("%lld%lld%lld", &n, &l, &r);ll pos = l, now = 0;for (ll i = 1; i <= n + 1; ++i)if ((2 * n - 1 - i) * i >= l){now = i; break;}while (pos <= r){if (pos > n * (n - 1)){cout << 1;break;}ll right = (2 * n - 1 - now) * now, left = right - 2 * (n - now);if ((pos - left) & 1) printf("%lld ", now);else printf("%lld ", now + (pos - left) / 2);++pos; if (pos > right) ++now;}cout << endl;}return 0;
}

3、Codeforces Round #633 (Div. 2)(2020.4.28)

A、Filling Diamonds

这A就做了我半天(指十分钟)。

还是考虑一个dp,设 d p [ n ] dp[n] dp[n]为到第 n n n个的时候的数量。然后我们发现这 n + 1 n+1 n+1和 n n n相比就多了最右边的四个三角形,然后我们如果用两个躺着的菱形把这四个盖住那么这时的方案数就是 d p [ n ] dp[n] dp[n];如果最右边的那个竖着的两个三角形被一个竖着的菱形盖住,那这个竖着菱形的左边四个三角形就只能用两个躺着的盖住,然后会发现剩下的空白的图形都是一样的…就是一旦最右边是竖着的,那么我们从左往右铺,会发现已经铺好的地方的边界的形状每次都是一样的。所以这种情况只能直接铺到最左边,只有这一种可能。于是 d p [ n + 1 ] = d p [ n ] + 1 dp[n+1]=dp[n]+1 dp[n+1]=dp[n]+1,然后算一下发现 d p [ n ] = n dp[n]=n dp[n]=n。之后写个复读机就可以了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{int t; cin >> t;while (t--){int n; cin >> n;cout << n << endl;}return 0;
}

B、Sorted Adjacent Differences

这题做起来不顺手,感觉怪怪的。

这种构造出一个具有某种单调性的题目多半要排序。于是先排个序。

然后我自己做的时候就是瞎搞了一通就过了,一开始关于正确性的证明也很乱。头疼。

具体思路就是从中间开始左右对称着输出就行了。如果想到了这个再倒回去看正确性,发现证明甚至只要在脑袋里捋一捋就知道这个肯定是对的。

但问题是怎么想到从中间开始输出的

不管了。就当是长见识了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 10;
int main()
{int t; cin >> t;while (t--){int n; scanf("%d", &n);int a[MAXN];for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);sort(a + 1, a + 1 + n);int l = (n + 1) / 2, r = l + 1;for (int i = 1; i <= n; ++i)if (i % 2) cout << a[l--] << ' ';else cout << a[r++] << ' ';cout << endl;}return 0;
}

*C、Powered Addition

这题我蠢了。幸亏我没去打这场Div2,不然这C得把我心态搞崩。

一开始想法是一个数一个数去补和前一个数的差值。因为如果每次从当前处理的位置开始到数组尾都加上某个数,那这些数字的相对大小肯定是不变的。然后我们统计一下差值,统计的过程中更新最大的答案就好了。

然后哇了好几次。前几次是因为写崩了(说实话这个方法还有点难写),然后最后一次写对了又哇了。

后来转念一想这肯定不对啊。如果从某个数开始后面都加上某个数,那么就是说之后所有的数都加上了 1 − 2 a n s − 1 1-2^{ans-1} 1−2ans−1的和,那这肯定造成浪费了。有可能前面的数是从这些数中选几个加上去的。所以我们更新的时候用贪心跑一遍,能加就加,超过了我们就取最小的,这样就保证了前面的数的最大值最小。然后就AC了。

先上代码。(从开始做这题到写出我这份丑陋的代码已经用了快1h了)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 10;
ll qpow(ll a, ll b)
{ll ans = 1;while (b){if (b & 1) ans *= a;a *= a; b >>= 1;}return ans;
}
int main()
{int t; cin >> t;while (t--){int n; scanf("%d", &n);ll a[MAXN];for (int i = 1; i <= n; ++i) scanf("%lld", a + i);ll maxi = a[1], res = 0;for (int i = 2; i <= n; ++i)if (a[i] < maxi){ll up = ceil(log(maxi - a[i] + 1) / log(2));res = max(res, up);ll temp;for (ll j = up; j >= 0; --j){ll sav = qpow(2, j);if (a[i] + sav >= maxi) temp = a[i] + sav;else a[i] += sav;}maxi = temp;}else maxi = a[i];cout << res << endl;}return 0;
}

后来感觉我这个方法实在太丑陋,于是去网上搜了一下题解。

发现的确有更妙写起来也更方便的方法。

我们观察到这个玩意儿每次都是加二的幂次,然后我们把它看成个二进制。这样对于任意两个数的差,我们就总能找到一种相加的方法来得到这个值。方案就是把这个数转成二进制之后1所在的位置。

考虑到这里之后就很简单了。那也就是说任意给定 a , b ( a > b ) a,b(a>b) a,b(a>b),我们总能用一种方法构造出 a − b a-b a−b然后把 b b b变成 a a a。这样就直接把上面的那个贪心省掉了。因为我们只要从前到后扫一遍,前面的最大值一定就是这个元素之后所有比它小元素都会便乘(雾)的那个值,这样能保证最大值最小。所以我们只要找出整个数组里差值的最大值,取个最高位位数就是答案。

如果这样想,代码简直随便写都能过…

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 10;
const int INF = 1e9 + 10;
int main()
{int t; cin >> t;while (t--){int n; scanf("%d", &n);int a[MAXN];for (int i = 1; i <= n; ++i) scanf("%d", a + i);int maxi = -INF, ans = 0;for (int i = 1; i <= n; ++i)if (a[i] <= maxi) ans = max(ans, maxi - a[i]);else maxi = a[i];int res = 0;while (ans) ++res, ans >>= 1;cout << res << endl;}return 0;
}

还是水平太臭。唉。

D、Edge Weight Assignment

无根树性质不太熟,之前没学过。

然后这题直接翻的题解。

无根树我们可以直接随便选取其中一个叶子节点作为根开始遍历。考虑最小值,肯定要么是1要么是3。若从根出发到某个叶子节点的路径长度为奇数那就是3,若都是偶数就是1。路径长度可以跑一遍树深求出来。

最大值如果我自己想肯定想不到…

首先因为这个边权是可以随便赋的,所以我们可以想象成方案一定存在。然后我们考虑每条边都赋成不同的情况,答案就是 n − 1 n-1 n−1。但是如果有若干个叶子节点挂在同一个父节点下面,因为到父节点的异或值肯定是确定的,所以从父结点到叶子节点经过一条边要变成0,那么这所有的边权应该都是相等的。所以我们就遍历一遍所有点,记录父节点然后抠掉这种情况就好了。

vis数组以后可以用bitset,每个元素只占一个位,巨省空间。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 10;
int maxi, mini = 1;
bool tag = 0;
vector <int> G[MAXN];
bitset <MAXN> vis;
void dfs(int now, int height)
{if (tag) return;if (G[now].size() == 1 && !(height & 1)){mini = 3; tag = 1; return;}vis[now] = 1;for (int i = 0; i < G[now].size(); ++i)if (!vis[G[now][i]]) dfs(G[now][i], height + 1);return;
}
int main()
{int n; cin >> n; maxi = n - 1;for (int i = 1; i <= n - 1; ++i){int x, y; scanf("%d%d", &x, &y);G[x].push_back(y); G[y].push_back(x);}int tot[MAXN] = {0};for (int i = 1; i <= n; ++i) if (G[i].size() == 1) tot[G[i][0]]++;for (int i = 1; i <= n; ++i) if (tot[i] > 0) maxi -= tot[i] - 1;for (int i = 1; i <= n; ++i)if (G[i].size() == 1){dfs(i, 1); break;}cout << mini << ' ' << maxi << endl;return 0;
}

4、Codeforces Round #635 (Div. 2)(2020.4.29-2020.4.30)

这场尽显冲国人死宅本色。

A、Ichihime and Triangle

因为要组成三角形,所以最小的两条边尽量大,最大的边尽量小就可以了。所以直接输出b,b,c。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{int t; cin >> t;while (t--){int a, b, c, d; cin >> a >> b >> c >> d;cout << b << ' ' << c << ' ' << c << endl;}return 0;
}

B、Kana and Dragon Quest game

一个简单的贪心。一开始先把削一半血的用掉,最后再看剩下的扣十血的能不能扣完。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{int t; cin >> t;while (t--){int x, n, m; cin >> x >> n >> m;while (x > 20 && n) x = (x >> 1) + 10, n--;if (10 * m >= x) cout << "YES" << endl;else cout << "NO" << endl;}return 0;
}

*C、Linova and Kingdom

一个有点复杂的贪心。而且个人觉得这个方法非常神奇。如果现场做出这题应该就是极限了。

这题首先是个直观的感觉,把所有工业城市放在最底下,这样肯定最大。

那么问题是怎么定义这个“底下”。仍然是个直观感觉,应该是和深度有关系,深度越大越先选。

在选深度最大的一层的时候这个方法完全没有问题。当深度最大的一层全部选完之后,按照这个方法是选择深度次大的。但是问题来了,此时在同层的节点之间有没有其它的优先顺序?

因为比当前节点更深的节点已经全部被选完了,所以我们会发现如果这一层填了某一个节点,那这个节点对答案的贡献是深度减一再减去它的所有孩子个数。所以我们会发现此时应该选孩子更少的节点。

于是第一个贪心策略就出现了:先按照深度贪心,当深度相同时按照孩子个数进行贪心。感觉非常正确,交了交了。

然后又WA了…

之后百思不得其解,于是去翻题解。

看完题解之后再仔细想想,发现按照这个贪心方法其实是能找到反例的。我们考虑这样一棵树。

如果按照我们原先想到的贪心策略,下一步我们会选4;但是这时候选3的答案(16)比选4(12)更优。那么怎么处理呢?

这时候我们再回过去看每个节点的贡献:深度减一再减去它的所有孩子个数。我们发现只要按照深度减去孩子个数这个标准排序,取前k个就一定是最大的了。

但是这个贪心方法真的对吗?对于某个节点,我们推导它贡献的时候有个前提就是它的孩子已经全部选完了。这时它的贡献才是深度减孩子个数减一。如果它的孩子我们有个没选,那么这个贡献值就是错的。

而这个贪心方法神奇就神奇在它还真TM是对的。

我们下面用反证法来证明依据这个贪心方法,选中某个节点时它的所有孩子节点一定全部选中了。

假设我们选了节点 i i i,而 i i i的孩子 j j j没有被选到。

因为 j j j是 i i i的孩子,所以 j j j的高度一定是比 i i i大的,所以 h e i g h t [ i ] < h e i g h t [ j ] height[i]<height[j] height[i]<height[j],于是 h e i g h t [ i ] − s o n [ i ] < h e i g h t [ j ] − s o n [ i ] height[i]-son[i]<height[j]-son[i] height[i]−son[i]<height[j]−son[i]

又因为 i i i的孩子里一定包含了 j j j的孩子,所以 s o n [ i ] ≥ s o n [ j ] + 1 son[i]\ge son[j]+1 son[i]≥son[j]+1,于是 h e i g h t [ j ] − s o n [ i ] < h e i g h t [ j ] − s o n [ j ] height[j]-son[i]<height[j]-son[j] height[j]−son[i]<height[j]−son[j]

所以 h e i g h t [ i ] − s o n [ i ] < h e i g h t [ j ] − s o n [ j ] height[i]-son[i]<height[j]-son[j] height[i]−son[i]<height[j]−son[j],也就是说 j j j的高度减去它的孩子数一定大于 i i i的高度减去它的孩子数,也就是说 j j j此时已经被选到了。与条件矛盾。

然后我们就证明了在这个贪心方法下,这个贡献的公式确实是对的…

然后上代码。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN  = 2e5 + 10;
int height[MAXN] = {0}, son[MAXN] = {0};
bitset <MAXN> vis = {0};
vector <int> G[MAXN];
void bfsh()//求高度
{vis[1] = 1; height[1] = 1;queue <int> que; que.push(1);while (!que.empty()){int p = que.front(); que.pop();for (int i = 0; i < G[p].size(); ++i)if (!vis[G[p][i]]) que.push(G[p][i]), vis[G[p][i]] = 1, height[G[p][i]] = height[p] + 1;}
}
int dfss(int now)//求孩子
{vis[now] = 1;int ans = G[now].size() - 1;for (int i = 0; i < G[now].size(); ++i)if (!vis[G[now][i]]) ans += dfss(G[now][i]);return son[now] = ans;
}
bool cmp(int x, int y)
{return height[x] - son[x] > height[y] - son[y];
}
int main()
{int n, k; cin >> n >> k;for (int i = 1; i <= n - 1; ++i){int x, y; cin >> x >> y;G[x].push_back(y); G[y].push_back(x);}son[1] = 1;vis = 0; bfsh();vis = 0; dfss(1);ll ans = 0;vector <int> temp(n + 1, 0);for (int i = 1; i <= n; ++i) temp[i] = i;sort(temp.begin() + 1, temp.end(), cmp);for (int i = 1; i <= k; ++i) ans += height[temp[i]] - son[temp[i]] - 1;cout << ans << endl;return 0;
}

现在就已经把这题彻底解决了。我们来反思一下这道题。

对于一些难度比较高的贪心而言,策略的选择其实并没有那么显然。这个时候往往会有一个寻找规律的过程。我们往往可以从直觉出发,得到一个不是特别正确的方法,之后由一系列公式推导再回头去修改最开始的贪心方法。比如这题,我们是由直觉出发再到公式推导,最后再由公式推导修改得到正确的贪心策略。下面我再举个例子,这是学长在VJ上挂的一道题。

Cow Acrobats(POJ 3045)

题意就不说了。下面讲的是我自己考虑的过程,如果有大哥能一眼想到证明过程的可以跳过了。

看到这题我的第一个想法就是二分,不过判断可行性有点焦灼。其次感觉尽量把 w w w大的牛放在下面,不过这样想肯定太粗糙了。那么我们不妨就认为此时的贪心策略是正确的,下面来考虑一下证明。

设 R [ k ] R[k] R[k]为第 k k k头牛的risk,那么 R [ k ] = Σ i = 1 k − 1 s [ i ] − w [ k ] R[k]=\Sigma_{i=1}^{k-1}s[i]-w[k] R[k]=Σi=1k−1​s[i]−w[k]。这个式子应该从题目描述里就可以得到了。然后 a n s = m a x ( R [ j ] ) ans=max(R[j]) ans=max(R[j]),并且我们要这个 a n s ans ans最小。

我们发现这时这个 R [ k ] R[k] R[k]里真的出现了减 w [ k ] w[k] w[k]。但是前面这个 Σ i = 1 k − 1 s [ i ] \Sigma_{i=1}^{k-1}s[i] Σi=1k−1​s[i]对于不同的排列一直在变化,没办法得到一个确定的值,所以我们不能直接就说这个结论是正确的。那么为了证明,我们把式子变形一下,变成 R [ k ] = Σ i = 1 k s [ i ] − ( s [ k ] + w [ k ] ) R[k]=\Sigma_{i=1}^{k}s[i]-(s[k]+w[k]) R[k]=Σi=1k​s[i]−(s[k]+w[k])。此时我们发现如果取 k = n k=n k=n,前面的 Σ i = 1 k s [ i ] \Sigma_{i=1}^{k}s[i] Σi=1k​s[i]对于任意一个不同的排列都变成了一个定值。因为实际上就相当于是对所有 s [ i ] s[i] s[i]求和,而输入数据是确定的,所以这个和也是确定的。为了使 R [ k ] R[k] R[k]尽可能小,我们会发现此时并不是让 w [ k ] w[k] w[k]尽量小,而是让 s [ k ] + w [ k ] s[k]+w[k] s[k]+w[k]尽可能小。于是我们再返回修改贪心策略,按照 s [ k ] + w [ k ] s[k]+w[k] s[k]+w[k]进行排序。而若我们 R [ n ] R[n] R[n]确定了,此时 R [ n − 1 ] R[n-1] R[n−1]实际上也确定了:因为 s [ n ] s[n] s[n]此时一定是唯一的,所以前面的和依旧是个定值。我们发现这样就使得 R [ i ] R[i] R[i]尽可能的小。

当然这个证明过程是有问题的,这只是我考虑的过程。不过标准的证明过程比较麻烦,对于解题而言其实并不要考虑这么多。我觉得能发现正确的规律就可以了。

标程这里就不贴了,因为是接近三个月前写的代码了。不光很丑而且码风还有问题,并且还写得很麻烦。思路就是按照 s [ i ] + w [ i ] s[i]+w[i] s[i]+w[i]排序,先预处理出前缀和,在过程中更新答案。

*D、Xenia and Colorful Gems

这题一开始拿到感觉是排序之后再去剩下两个数组里找最接近的,不过这个最接近的怎么找把我卡了…

然后去看题解,发现是枚举最中间的,然后找比它小的最大的和比它大的最小的,分六种情况讨论一下。

这题我的代码写得很丑…核心的三个循环其实可以写成一个函数,就没必要拖这么长了。不过也懒得改了,就这样吧。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 10;
const ll INF = 8e18;
ll cal(ll a, ll b, ll c)
{return (a - b) * (a - b) + (a - c) * (a - c) + (c - b) * (c - b);
}
ll upperbound(ll *a, int size, ll target)//因为STL不能找小于且最大,所以这边干脆全部都手写了
{int l = 1, r = size, ans = -1;while (l <= r){int mid = (l + r) >> 1;if (a[mid] >= target) ans = a[mid], r = mid - 1;else l = mid + 1;}return ans;
}
ll lowerbound(ll *a, int size, ll target)
{int l = 1, r = size, ans = -1;while (l <= r){int mid = (l + r) >> 1;if (a[mid] <= target) ans = a[mid], l = mid + 1;else r = mid - 1;}return ans;
}
ll r[MAXN], g[MAXN], b[MAXN];
int main()
{int t; cin >> t;while (t--){int nr, ng, nb; cin >> nr >> ng >> nb;for (int i = 1; i <= nr; ++i) scanf("%lld", r + i);for (int i = 1; i <= ng; ++i) scanf("%lld", g + i);for (int i = 1; i <= nb; ++i) scanf("%lld", b + i);sort(r + 1, r + 1 + nr);sort(g + 1, g + 1 + ng);sort(b + 1, b + 1 + nb);ll ans = INF;//这下面应该放到函数里的 失策了for (int i = 1; i <= nr; ++i){int x, y;x = upperbound(g, ng, r[i]); y = lowerbound(b, nb, r[i]);if (~x && ~y) ans = min(ans, cal(x, y, r[i]));x = lowerbound(g, ng, r[i]); y = upperbound(b, nb, r[i]);if (~x && ~y) ans = min(ans, cal(x, y, r[i]));}for (int i = 1; i <= ng; ++i){int x, y;x = upperbound(r, nr, g[i]); y = lowerbound(b, nb, g[i]);if (~x && ~y) ans = min(ans, cal(x, y, g[i]));x = lowerbound(r, nr, g[i]); y = upperbound(b, nb, g[i]);if (~x && ~y) ans = min(ans, cal(x, y, g[i]));}for (int i = 1; i <= nb; ++i){int x, y;x = upperbound(g, ng, b[i]); y = lowerbound(r, nr, b[i]);if (~x && ~y) ans = min(ans, cal(x, y, b[i]));x = lowerbound(g, ng, b[i]); y = upperbound(r, nr, b[i]);if (~x && ~y) ans = min(ans, cal(x, y, b[i]));}cout << ans << endl;}return 0;
}

后来仔细考虑了一下,这题没出其实不太应该。因为前不久才刚刚有同学问过我下面这道题,当时确实想了半天,但最后好歹算是做出来了。于是我这边干脆都挂上来。

Find My Family

这题简单来说就是一个序列,问是否存在这样三个数 i < j < k i<j<k i<j<k,使 a [ j ] < a [ i ] < a [ k ] a[j]<a[i]<a[k] a[j]<a[i]<a[k]。

同样也是枚举位置处于中间的数 j j j,然后找前面比它小的最大的和后面整个最大的,比一比就可以了。

不过因为当时STL水平实在是太臭,没想到用 s e t set set和 m a p map map,所以想了好久…而且最后的AC代码也写得非常丑,最后那个最大值还是拿线段树实现的。干脆今天重写了一遍,舒服多了。代码比之前的短,跑得也比之前的快。

上代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 3e5 + 10;
int main()
{int t; cin >> t;vector <int> ans;for (int i = 1; i <= t; ++i){int n; cin >> n;int a[MAXN];for (int j = 1; j <= n; ++j) scanf("%d", a + j);set <int> prev; map <int, int, greater<int> > latt;//规定排序方式,这样最大值就在map的第一个位置for (int j = 1; j <= n; ++j) latt[a[j]]++;for (int j = 1; j <= n; ++j){set <int>::iterator p = prev.lower_bound(a[j]);if (p != prev.end() && *p < latt.begin() -> first){ans.push_back(i); break;}prev.insert(a[j]);if (latt[a[j]] == 1) latt.erase(a[j]);else latt[a[j]]--;}}cout << ans.size() << endl;for (auto &i : ans) cout << i << endl;return 0;
}

以后和这种三元函数有关的极值问题和偏序问题还是得第一反应想到枚举中间元素+二分剩余元素啊…

Codeforces补题记录(1)相关推荐

  1. Codeforces 补题记录

    首先总结一下前段时间遇到过的一些有意思的题. Round #474 (Div. 1 + Div. 2, combined)   Problem G 其实关键就是n这个数在排列中的位置. 这样对于一个排 ...

  2. 2019 CCPC-Wannafly Winter Camp Day8 (Div2, onsite) 补题记录

    一篇来自ACM入门者的补题记录 最近有点懒,想着还有最后一篇博客没完成,是我最大的补题动力. 不过终于在camp过去三个月的时候完成了所有的补题博客,有点欣慰,下一个目标应该是补一补一年前暑期训练的题 ...

  3. Good Bye 2019补题记录

    Good Bye 2019补题记录 A. Card Game 题目描述 Two players decided to play one interesting card game. There is ...

  4. 【Python爬虫实战】codeforces刷题记录小助手

    先看效果图. 输入codeforces的用户名,可以查询用户的rating信息.以及参加比赛的信息(大星参数的不计算在内).还有总的AC数. 一.需求分析 找到显示用户参加contest信息的url. ...

  5. 2019 CCPC-Wannafly Winter Camp Day1 (Div2, onsite)(补题记录)

    一篇来自ACM入门者的补题记录 文章目录 A.机器人 B.吃豆豆 C.拆拆拆数 E.流流流动 F.爬爬爬山 I.起起落落 J.夺宝奇兵 A.机器人 题意:有两条平行线段A,B,长度从1~n,机器人一开 ...

  6. Codeforces 刷题记录(已停更)

    Codeforces 每日刷题记录 (已停更) 打'+'是一些有启发意义的题目,部分附上一句话题解,每日更新3题,大部分题目较水. Day ID Problem Tutorial Note 1 1 + ...

  7. 18-6-2补题记录

    1.一直在TLE的A题 -待补完 题意:第一行输入n和m 第二行输出n个数字 接下来m行输入l.r.d  要求判断l到r间的乘积能否被d整除. 思路:粗看很简单,好像可以直接暴力求解,再看妈耶这范围大 ...

  8. Educational Codeforces Round 100 (Rated for Div. 2)补题记录

    总结:感觉这个educational场好难啊(蒟蒻视角),又被虐了,唉 A. Dungeon 每一枪会造成1点伤害对一个单位,但是当开7的倍数枪时会造成3点伤害 每7次一个轮回,一个完整的轮回共造成9 ...

  9. Codeforces Good Bye 2020 补题记录

    系列文章目录 文章目录 系列文章目录 前言 G. Song of the Sirens 前言 我是傻逼 G. Song of the Sirens 给定n和q,字符串s0和t,t的长度为n sis_i ...

最新文章

  1. 与其焦虑成疾,不如静心学习
  2. Permissions 0644 for '/root/.ssh/id_rsa'
  3. jquery ajax返回Internal server error 500错误解决方案
  4. Jenkins持续集成环境之插件管理和角色管理
  5. oracle 判断如果有符合条件的记录则不插入_Oracle数据库AWR部分报告说明
  6. 数据库如何避免读取脏数据?
  7. 十道解分式方程及答案_解分式方程的题出20道还要带答案
  8. ASP版MD5加密函数及用法
  9. 「硬刚Doris系列」Apache Doris的向量化和Roaring BitMap
  10. [CTF]Dino安全小组第三次内部赛“remix_欧皇的游戏2.0”Writeup
  11. 成都拓嘉启远:如何排查拼多多星级下降的原因
  12. 4个高质量站点推荐值得收藏
  13. 怎么找电脑服务器文档,怎么找到电脑的服务器地址
  14. gerrit的第一次提交记录
  15. 操作系统作业之银行家算法(c语言实现)
  16. 【信号处理】什么是基线漂移/趋势项?如何消除?
  17. 孤立森林(隔离树)译文
  18. PostgreSQL的psql中的AutoCommit
  19. Win10安装程序提示“不能打开要写入的文件xxxxx”如何解决
  20. 阿piu传-文档批量上传客户端-原创力版使用帮助

热门文章

  1. 机动目标跟踪——Jerk模型
  2. 利用人工智能预测乐高包装的LEGO商标尺寸,深入研究竟有惊人发现
  3. mac idea实现全局替换
  4. 级联查询:resultMap完成sql查询结果对java对象的映射
  5. 参加的马拉松比赛记录
  6. 百度主动推送不收录怎么办?
  7. APPIUM安装与使用
  8. 一图详解管理的全过程——定目标、追过程、拿结果
  9. 钢化膜?水凝膜?普通膜?别再傻傻分不清楚了!!!
  10. 使用python 将excel中数据批量生成word周报