Codeforces Round #503 (by SIS, Div. 2) Solution

时间:2023-03-09 21:42:54
Codeforces Round #503 (by SIS, Div. 2)  Solution

瞎扯

  例行快速切A、B、C。

  然后发现D是交互。E也不像是我能做的题,感觉完蛋了。

  最后8分钟想出D。狂码代码,然后比赛结束后1分钟过样例。

  第二天早上再花4分钟AC。我真是个大菜逼。。

  于是这场cf比赛变成了真·手速场。几个friends手速比我快,然后rank就比我高。。。

  (获得成就:在官方题解出来之前写完也许是假的题解)

Problem A New Building for SIS

题目大意

  有$n$栋高度均为$h$的塔排成1排。相邻的塔之间第$a$层到第$b$层之间有通道。上下楼层或者在通道中移动均会花费1的单位时间。多次询问从一座塔的某一层到另一个塔的一层需要的最少耗时。

  大力分类讨论。我居然WA了一次。'

Code

 /**
* Codeforces
* Problem#1020A
* Accepted
* Time: 31ms
* Memory: 0k
*/
#include<bits/stdc++.h>
using namespace std;
typedef bool boolean; int n, h, a, b, q; inline void init() {
scanf("%d%d%d%d%d", &n, &h, &a, &b, &q);
} inline void solve() {
while(q--){
int x1, y1, x2, y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
if(y1 > y2)
swap(y1, y2);
if (x1 == x2) {
printf("%d\n", abs(y1 - y2));
} else {
int ans = abs(x1 - x2);
if (y1 <= b && y2 >= a) {
ans += abs(y1 - y2);
}else{
ans += min(abs(y1 - a) + abs(y2 - a), abs(y1 - b) + abs(y2 - b));
}
printf("%d\n", ans);
}
}
} int main(){
init();
solve();
return ;
}

Problem A

Problem B Badge

题目大意

  给定基环内向树。问从每个点出发,第二次到达的点是什么。

  直接模拟。

Code

 /**
* Codeforces
* Problem#1020B
* Accepted
* Time: 31ms
* Memory: 0k
*/
#include <bits/stdc++.h>
using namespace std;
typedef bool boolean; int n;
int *ps;
boolean *vis; inline void init() {
scanf("%d", &n);
ps = new int[(n + )];
vis = new boolean[(n + )];
for (int i = ; i <= n; i++)
scanf("%d", ps + i);
} inline void solve() {
for (int i = ; i <= n; i++) {
int j = i;
memset(vis, , sizeof(boolean) * (n + ));
while (!vis[j]) {
vis[j] = true;
j = ps[j];
}
printf("%d ", j);
}
} int main() {
init();
solve();
return ;
}

Problem B

Problem C Elections

题目大意

  有$n$个人为$m$个政党投票。每个人初始投票的政党为$p_{i}$,你可以花费$v_{i}$将他投票的政党改为你指定的政党。如果使1号政党的投票严格大于其他政党,问最少花费。(这些人不正直,居然拿钱可以收买)

  暴力枚举1号党最少的选票。然后贪心地收买投其他政党的人。

Code

 /**
* Codeforces
* Problem#1020C
* Accepted
* Time: 46ms
* Memory: 200k
*/
#include <bits/stdc++.h>
#ifndef WIN32
#define Auto "%lld"
#else
#define Auto "%I64d"
#endif
using namespace std;
typedef bool boolean;
#define ll long long const int N = ;
const signed ll llf = (signed ll) (~0ull >> ); typedef class Voter {
public:
int to;
int v;
int id; Voter() { }
Voter(int to, int v, int id):to(to), v(v), id(id) { } boolean operator < (Voter b) const {
return v < b.v;
}
}Voter; int n, m;
ll res = llf;
Voter ar[N];
vector<Voter> vs[N]; inline void init() {
scanf("%d%d", &n, &m);
for (int i = , p, v; i <= n; i++) {
scanf("%d%d", &p, &v);
vs[p].push_back(Voter(p, v, i));
ar[i] = Voter(p, v, i);
}
} boolean sec[N];
inline void solve() {
sort(ar + , ar + n + );
for (int i = ; i <= m; i++)
sort(vs[i].begin(), vs[i].end());
for (int p1 = vs[].size(); p1 <= n; p1++) {
ll cmp = ;
int got = vs[].size();
memset(sec, false, sizeof(sec));
for (int i = ; i <= m; i++) {
int s = vs[i].size() - p1 + ;
for (int j = ; j < s && j < vs[i].size(); j++)
cmp += vs[i][j].v, got++, sec[vs[i][j].id] = true;
}
int p = ;
while (got < p1) {
if (!sec[ar[p].id] && ar[p].to > ) {
cmp += ar[p].v;
sec[ar[p].id] = true;
got++;
}
p++;
}
res = min(res, cmp);
}
printf(Auto, res);
} int main() {
init();
solve();
return ;
}

Problem C

Problem D The hat

题目大意

  $2n$个数围成一圈,顺时针依次记为$a_{1}, a_{2}, \cdots, a_{2n}$,满足相邻两个数的差恰好为1。问是否存在一个$i(1\leqslant i \leqslant n)$,使得$a_{i} = a_{i + n}$。

  你可以从标准输入中读取$2n$。然后你可以通过 "? x" 来询问$a_{x}$的值。最后通过 "! i" 输出你找到的$i$。如果不存在输出 "! -1" 。

  你至多可以询问59次。

  将每一个数和后一个数作差可以得到一个$+1, -1$构成的圈。从适当的位置剖开,等价于询问是否存在一个长度为$n$的一段和为$0$。

  显然,当$n$为奇数的时候无解。因为$+1, -1$在长度为$n$的序列中必然不相等。

  考虑$a_{1}$和$a_{n + 1}$。不妨先假设$a_{n + 1} - a_{1} = x\ \ (x > 0)$(如果取等直接输出答案)

  如果存在解,如果它们的位置是$p_{1}, p_{2}\ (p_{1} < p_{2})$。那么$a_{p_{1}} - a_{1} = a_{p_{2}} - a_{1} = a_{p_{2}} + x - a_{n + 1}$.

  移项可得:$a_{p_{1}} - a_{1} - (a_{p_{2}} - a_{n + 1}) = x$。

  感觉没啥用。不过继续吧。

  对于一个长度$k$,设$d =  a_{k + 1} - a_{1} - (a_{k + n + 1} - a_{n + 1})$。

  • 如果$d = x$,那么$k + 1$就是答案。
  • 如果$d > x$,感受一下,就是$n + 1$到$n + k$的地方$+1$取多了。退回去一些就能找到答案。
    现在来证明这个答案一定存在。
    容易证明$d, x$一定是偶数。考虑每次将$k$减少,$d$可能的变化量有$-2, 0, 2$。
    当$k = 0$的时候,$d_{0} = 0$。$d$从一个大于$x$的偶数,每次变化为$-2, 0, 2$。
    显然一定存在某个时刻$d = x$。(否则任意$d' \leqslant x - 2$无法被取到,但$k = 0$时$d = 0 \leqslant x - 2$)。
  • 如果$d < x$。我们考虑增加$k$,当$k = n$是$d_{n} = x - (-x) = 2x$。因为$x > 0$,同理可以证得存在某个时刻$d = x$。

  根据以上讨论,发现每次指定$k$后,能确定答案至少存在于一半的区间。因此可以二分。

  对于$x < 0$的情况作类似的讨论也可以得出类似的结论。


  UPD 2019.9.1

  觉得自己当时非常地蠢。其实这个问题很傻逼。

  考虑 $g_i = a_{i + n/2} - a_{i} $。$g_{i + 1} - g_{i}$的差要么是0,要么是正负2.

  可以先求出$g_1$,如果$g_1$是奇数,答案是-1。如果$g_1$是0,那么做完了。

  根据初中数学只是我们知道,这个函数一定存在零点。

  用$g_l g_r < 0$来判就行。这个显然可以二分。

Code

 /**
* Codeforces
* Problem#1020D
* Accepted
* Time: 31ms
* Memory: 0k
*/
#include <bits/stdc++.h>
using namespace std;
typedef bool boolean; int n, hn; int ask(int p) {
printf("? %d\n", p);
fflush(stdout);
int x;
scanf("%d", &x);
return x;
} inline void init() {
scanf("%d", &n);
hn = (n >> );
} inline void solve() {
if (hn & ) {
puts("! -1");
return;
}
int x = ask(), y = ask( + hn);
if (x == y) {
puts("! 1");
return;
}
int l = , r = hn - ;
if (x < y) {
while (l <= r) {
int mid = (l + r) >> ;
int a = ask( + mid) - x, b = ask( + mid + hn) - y;
if (a == b + (y - x)){
printf("! %d", + mid);
return;
}
if (a < b + (y - x))
l = mid + ;
else
r = mid - ;
}
} else {
while (l <= r) {
int mid = (l + r) >> ;
int a = ask( + mid) - x, b = ask( + mid + hn) - y;
if (a == b + (y - x)){
printf("! %d", + mid);
return;
}
if (a > b + (y - x))
l = mid + ;
else
r = mid - ;
}
}
puts("! -1");
} int main() {
init();
solve();
return ;
}

Problem D

Problem E Sergey's problem

题目大意

  给定一个有向无自环的图。要求选出一个点集满足:

  • 点集中任意两点不能通过一条边直接到达。
  • 不在点集中的某个点可以通过点集中的点经过至多2条边到达。

  做法感觉很神的样子。

  1. 首先按标号从小到大枚举点,当一个点没有被标记时,将它加入集合$A$,然后删去它经过1条出边能够到达的点。
  2. 然后按标号从大到小枚举$A$中的点,如果它不在答案点集$V$中并且它所有入边的起点均不在$V$中,则将它加入$V$。

  然后来证明一下它的正确性:

  • 点集中任意两点不能通过一条边直接到达

    假设结论不成立。那么存在一条边两端的两个点同时被选了。设这条边是$(u, v)$。
    由第一步可知:$v < u$。否则$v$会在选$u$的时候被标记。
    由第二步可知:$u$会被先选入$V$,之后$v$不会被选入$V$,矛盾。

  • 不在点集中的某个点可以通过点集中的点经过至多2条边到达。

    仍然假设结论不成立。那么必然存在一个点$p$,所有$v \in V$到它的最短路的长度至少为$3$。
    考虑$p$的一个直接前驱$q$。所有$v \in V$到$q$的最短路的长度至少为$2$。
    所以$q \in A$。

    Codeforces Round #503 (by SIS, Div. 2)  Solution

    如上图橙色的点是$V$中的点(显然这个选择方案是有问题的)。然后考虑$q$在第二轮中是否能被选中。
    因为$q$的直接前驱都不在$V$中,所以$q$一定能被选中。这与假设矛盾。

Code

 /**
* Codeforces
* Problem#1020E
* Accepted
* Time: 763ms
* Memory: 52100k
*/
#include <bits/stdc++.h>
using namespace std;
typedef bool boolean; int n, m;
boolean *lab1, *lab2;
vector<int> *g, *rg; inline void init() {
scanf("%d%d", &n, &m);
lab1 = new boolean[(n + )];
lab2 = new boolean[(n + )];
g = new vector<int>[(n + )];
rg = new vector<int>[(n + )];
memset(lab1, false, sizeof(boolean) * (n + ));
memset(lab2, false, sizeof(boolean) * (n + ));
for (int i = , u, v; i <= m; i++) {
scanf("%d%d", &u, &v);
g[u].push_back(v);
rg[v].push_back(u);
}
} vector<int> curans;
inline void solve() {
for (int i = ; i <= n; i++) {
if (lab1[i])
continue;
for (int j = ; j < (signed) g[i].size(); j++)
lab1[g[i][j]] = true;
lab1[i] = true;
curans.push_back(i);
} for (int i = curans.size() - ; ~i; i--) {
int p = curans[i];
lab2[p] = true;
for (int j = ; j < (signed) rg[p].size() && lab2[p]; j++)
if (lab2[rg[p][j]])
lab2[p] = false;
} curans.clear();
for (int i = ; i <= n; i++)
if (lab2[i])
curans.push_back(i); printf("%d\n", (signed) curans.size());
for (int i = ; i < (signed) curans.size(); i++)
printf("%d ", curans[i]);
} int main() {
init();
solve();
return ;
}

Problem E