Codeforces Round #507 (Div. 2, based on Olympiad of Metropolises)
题目链接:https://codeforces.com/contest/1040
A - Palindrome Dance
int x[1005];
void test_case() {
int n, a, b;
scanf("%d%d%d", &n, &a, &b);
for(int i = 1; i <= n; ++i)
scanf("%d", &x[i]);
int sum = 0;
for(int i = 1; i <= n / 2; ++i) {
if(x[i] == 0) {
if(x[n + 1 - i] == 1) {
puts("-1");
return;
} else if(x[n + 1 - i] == 2)
sum += a;
} else if(x[i] == 1) {
if(x[n + 1 - i] == 0) {
puts("-1");
return;
} else if(x[n + 1 - i] == 2)
sum += b;
} else {
if(x[n + 1 - i] == 0)
sum += a;
else if(x[n + 1 - i] == 1)
sum += b;
else
sum += 2 * min(a, b);
}
}
if(n % 2 == 1 && x[(n + 1) / 2] == 2)
sum += min(a, b);
printf("%d\n", sum);
}
B - Shashlik Cooking
题意:给一个长度为 \(n\) 的01串,初始全为0。给定长度 \(k\) ,每次选一个位置 \(x\) ,翻转包含 \(x\) 在内的 \([x-k,x+k]\) ,翻转最少的次数使得全部为1。
题解:单次翻转可以翻转 \(2k+1\) 个位置,所以最少需要 \(\lceil\frac{n}{2k+1}\rceil\) 次。设 \(c=\lceil\frac{n}{2k+1}\rceil\) ,那么最长是 \(c(2k+1)\) ,最短是 \((c-2)(2k+1)+2(k+1)\) ,恰好覆盖,所以中间一定存在一种构造。下面是字典序最小的构造。
void test_case() {
int n, k;
scanf("%d%d", &n, &k);
int c = (n + (2 * k + 1 - 1)) / (2 * k + 1);
int b;
for(b = 1;; ++b) {
if(b + k + (c - 1) * (2 * k + 1) >= n)
break;
}
printf("%d\n", c);
for(int i = 1; i <= c; ++i)
printf("%d%c", b + (i - 1) * (2 * k + 1), " \n"[i == c]);
}
*C - Timetable
这个C题特别有收获。
题意:有一个长度为 \(n(1\leq n \leq 200000)\) 的严格单调上升的正整数序列 \(a(1\leq a_i \leq 10^{18})\) ,和一个大正整数 \(t(1\leq t \leq 10^{18})\) 。又给一个长度为 \(n\) 的序列 \(x\) ,其中 \(x_i\) 表示 \(a_i\) 最大能够匹配的是 \(b_{x_i}\) ,\(a_i\) 和 \(b_j\) 匹配是指 \(a_i+t\leq b_j\) 。要求构造单调递增的序列 \(b\) 。
错误算法1:首先序列 \(x\) 应该是非严格单调上升的,因为一个出发更晚的车可以选的范围肯定是只有右边的一段。所以先验证序列 \(x\) 合法。然后有个猜测,就是连续相同的一段 \([x_i,x_j]\) 是可以互换的,要求他们可以互换则要 \(b_i\geq a_j+t\) ,注意这些都要让 \(a_{j+1}\) 到不了(不参与互换),因为假如 \(a_{j+1}\) 能到的话,可以让 \(a_{j+1}\) 去 \(b_{j}\) 而 \(a_{j}\) 可以去 \(b_{j+1}\) ,与 \([x_i,x_j]\) 是最长的连续矛盾。
所以还要让 \(b_j<a_{j+1}+t\) 。
如:
3 10
4 6 8
2 2 3
可恰好构造为:
16 17 18
这样一来 8 不能到 16 和 17 ,就只能去 18 ,18 被 8 占领了之后 4 和 6 当然就不能去 18 ,要保证 4 和 6 可互换所以第一个数最小要构造为 6+10=16 ,而这时候恰好可以放下一个 17 。总之就是要让 17 不能被 8 到,所以这段要尽可能小。
所以就按尽可能小来构造,然后验证每段 \([x_i,x_j]\) 是不能到达前一段的。
但是很可惜这个算法是有问题的,首先要加一个验证就是 \(x_i>=i\) ,然后会在这组数据翻车:
10 200
100 101 103 104 106 107 109 111 113 114
2 2 4 4 7 7 7 8 12 12
输出
Yes
301 302 304 305 309 310 311 311 314 315
错误算法2:发现问题就是让311重复了,原因是 106~109 这段是从 309 开始构造的,但是实际上可以从 308 开始构造,因为“连续相同的一段 \([x_i,x_j]\) 是可以互换的”是个假命题,实际上是不需要互换的,只需要循环左移就可以让 106 取得 x=7 。所以这个连续段中除去最后一个的每个 \(b_i\) 只需让 \(a_{i+1}\) 能到就行。
不知道为什么错。
ll a[200005];
int x[200005];
ll b[200005];
void test_case() {
int n;
ll t;
scanf("%d%lld", &n, &t);
for(int i = 1; i <= n; ++i)
scanf("%lld", &a[i]);
for(int i = 1; i <= n; ++i)
scanf("%d", &x[i]);
for(int i = 2; i <= n; ++i) {
if(x[i] < x[i - 1] || x[i] < i) {
puts("No");
return;
}
}
for(int i = 1, nxt; i <= n; i = nxt) {
for(nxt = i + 1; nxt <= n && x[nxt] == x[i]; ++nxt);
for(int j = i; j < nxt - 1; ++j)
b[j] = a[j + 1] + t;
if(nxt <= n)
b[nxt - 1] = a[nxt] + t - 1;
else
b[n] = b[n - 1] + 1;
}
for(int i = 2; i <= n; ++i) {
if(b[i] <= b[i - 1]) {
puts("No");
return;
}
}
puts("Yes");
for(int i = 1; i <= n; ++i)
printf("%lld%c", b[i], " \n"[i == n]);
}
错误算法3:首先要验证几个命题。
命题1:对所有的 \(i\) 满足 \(1\leq i \leq n-1\) ,都满足 \(x_i \leq x_{i+1}\) 。简单来说就是序列 \(x\) 非降序。
证明:反证法,若存在某些 \(i\) 满足 \(1\leq i \leq n-1\) ,但不满足 \(x_i \leq x_{i+1}\) 。
设 \(i\) 是第一个不满足的位置,那么有 \(x_{i}>x_{i+1}\) 。由题目输入限制显然有 \(i>1\) ,由假设可知 \(a_{i-1}\) 能够到达 \(b_{i-1}\) 即 \(a_{i-1}+t\leq b_{i-1}\) ;且 \(a_{i}\) 不能够到达 \(b_{i}\) 即 \(a_{i}+t > b_{i}\) ,但可以到达某个 \(b_{j}<b_{i}\) 即 \(a_{i}+t \leq b_{j}\) 。所以有 \(a_{i}+t \leq b_{j} < b_{i}\) 即 \(a_{i}+t < b_{i}\) 与 \(a_{i}+t > b_{i}\) 矛盾。
命题2:对所有的 \(i\) 满足 \(1\leq i \leq n\) ,都满足 \(x_i \geq i\) 。
证明:反证法,若存在某些 \(i\) 满足 \(1\leq i \leq n\) ,但不满足 \(x_i \geq i\) 。
设 \(i\) 是第一个不满足的位置,那么有 \(x_{i}<i\) 。由题目输入限制显然有 \(i>1\) ,这个 \(i\) 要占用 \([1,i-1]\) 的其中一个,那么前面就必定有一个要去到 \([i,n]\) ,这样就与命题1矛盾。
命题3:某个连续相等的 \(x\) 段,使得序列 \(b\) 尽可能小的构造是循环左移。
直观感觉?要使得第一个位置L能够换去最后一个位置R,那么R要换去[L,R-1],这时只换去R-1是最好的。这样除了最后一个位置以外,每个位置只受到 \(b_i\geq a_{i+1}+t\) 的限制,其他的置换限制会更多。
命题4:两个相邻的连续相等的 \(x\) 段,断层处必定满足 \(b_{x_{1R}}<a_{x_{2L}}+t\) 。
证明:否则 \(x_{2L}\) 就可以来 \(x_{1R}\) 了,而 \(x_{1R}\) 肯定也能去 \(x_{2L}\) ,这样就不是断层了。
所以最末尾的 \(b_n\) 几乎不受任何限制,取最大值,然后每次断层会导致 \(b_i\) 突然下降。同时连续段内的除了最后一个元素之外的 \(b_i\) 还要受到 \(b_i \geq a_{i+1}+t\) 的限制用来循环左移,非连续段内(也就是断层)就是 \(b_i \geq a_i+t\) 且 \(b_i < a_{i+1}+t\) 。后者出现矛盾时无解。
题解:理解错题意了,根本不是这个意思。只有两种位置:1、 \(x_i=i\) 的位置,即连续的段的末尾。这个时候若后面还有数,则不能从 \(a_{i+1}\) 转移,为了让 \(b\) 尽可能递增所以贪心取为 \(a_{i+1}+t-1\) ;否则后面没有数,可以取为 \(a_{n}+t+1\) 到无穷大之间的数 (一定要+1,因为倒数第二个数假如是type2的话就会相等)。2、其他位置。那么后面必定有对应长度的连续段,只需要下一班车 \(i+1\) 可以停过来就可以了,为了让 \(b\) 尽可能递增取为 \(a_{i+1}+t\) 。这样取完之后能够保证每个数至少能够取到 \(x_i\) ,但是不能保证 \(b\) 一定是递增的也不能保证恰好能取到 \(x_i\) (有可能会继续往右边延伸,因为这样构造的 \(b\) 有可能有位置是相等的,也有可能 \(b\) 是严格单调递增的,但是 \(a_i\) 确实被某些 \(>x_i\) 的位置蔓延到)
终于想明白这道题的本质了!
某个位置假如能够被后车占了,则这个位置是“可被后车占领位置”,自己就可以把车开到这一段“可被后车占领位置”的最后一个的下一个位置(这些“可被后车占领位置”逐个被后车占领,自己开去最后一个);假如某个 \(x_i \neq i\) 那么这个位置就必须是连续位置,至少要有 \(b_i \geq a_{i+1}+t\) ;这样只能保证自己是一个可以往后开的,但是能开多远是 无法保证 的。要在最后确认。
否则,这个位置不能被后车占了,那么必须有 \(b_i < a_{i+1}+t\) ,即至多 \(b_i \leq a_{i+1}+t-1\) 。
由贪心法,是从左往右构造,对于每个“至少”的条件要恰好满足,而“至多”的条件取满的就不容易和“至少”的条件碰在一起。
ll a[200005];
int x[200005];
ll b[200005];
void test_case() {
int n;
ll t;
scanf("%d%lld", &n, &t);
for(int i = 1; i <= n; ++i)
scanf("%lld", &a[i]);
for(int i = 1; i <= n; ++i)
scanf("%d", &x[i]);
for(int i = 1; i <= n; ++i) {
if(x[i] < x[i - 1] || x[i] < i) {
puts("No");
return;
}
}
b[n] = a[n] + t + 1;
for(int i = n - 1; i >= 1; --i) {
b[i] = a[i + 1] + t - (x[i] == i);
assert(b[i] <= b[i + 1]);
if(b[i] == b[i + 1]) {
puts("No");
return;
}
}
for(int i = n, lst = n; i >= 1; --i) {
assert(x[i] <= lst);
if(x[i] != lst) {
puts("No");
return;
}
if(b[i - 1] - a[i] < t)
lst = i - 1;
}
puts("Yes");
for(int i = 1; i <= n; ++i)
printf("%lld%c", b[i], " \n"[i == n]);
}
D - Subway Pursuit
题意:地铁铁轨是一个长度为 \(n(1\leq n \leq 10^{18})\) 的数轴,在上面找一辆失控的车,它每次都会出现在整数位置。每次询问可以问一个区间,jury回答车是否在区间里面,注意车每次会向左或者向右移动至多 \(k(0\leq k \leq 10)\) 个单位长度,不会越界,询问至多4500次。
题解:先用大概60~70次“二分”就可以定位车在一个长度大约 \(4k\) 的区间里,然后随机抽一个数字进行询问。开始询问的条件应该是再“二分”不会使得区间有显著减少时,也就是 \(\frac{len}{2}+2k\geq len\) ,简单起见可以直接取50。注意到每个数平均会被问40次(在问一次之后下一次一般就要进行“二分”,所以有一半的次数在“二分”),所以不成功的概率应该不高。
char s[1005];
bool query(ll l, ll r) {
printf("%lld %lld\n", l, r);
fflush(stdout);
scanf("%s", s);
if(s[0] == 'Y')
return 1;
return 0;
}
void test_case() {
srand(time(0));
ll n;
int k;
scanf("%lld%d", &n, &k);
ll L = 1, R = n, C = 4 * k + 4;
while(1) {
if(R - L + 1 >= C) {
ll M = (L + R) / 2;
if(query(L, M)) {
L = max(1ll, L - k);
R = min(n, M + k);
} else {
L = max(1ll, M + 1 - k);
R = min(n, R + k);
}
} else {
int rnd = rand() % (R - L + 1);
if(query(L + rnd, L + rnd))
return;
else {
L = max(1ll, L - k);
R = min(n, R + k);
}
}
}
}