题目链接: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
", sum);
}
B - Shashlik Cooking
题意:给一个长度为 (n) 的01串,初始全为0。给定长度 (k) ,每次选一个位置 (x) ,翻转包含 (x) 在内的 ([x-k,x+k]) ,翻转最少的次数使得全部为1。
题解:单次翻转可以翻转 (2k+1) 个位置,所以最少需要 (lceilfrac{n}{2k+1} ceil) 次。设 (c=lceilfrac{n}{2k+1} ceil) ,那么最长是 (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
", c);
for(int i = 1; i <= c; ++i)
printf("%d%c", b + (i - 1) * (2 * k + 1), "
"[i == c]);
}
*C - Timetable
这个C题特别有收获。
题意:有一个长度为 (n(1leq n leq 200000)) 的严格单调上升的正整数序列 (a(1leq a_i leq 10^{18})) ,和一个大正整数 (t(1leq t leq 10^{18})) 。又给一个长度为 (n) 的序列 (x) ,其中 (x_i) 表示 (a_i) 最大能够匹配的是 (b_{x_i}) ,(a_i) 和 (b_j) 匹配是指 (a_i+tleq b_j) 。要求构造单调递增的序列 (b) 。
错误算法1:首先序列 (x) 应该是非严格单调上升的,因为一个出发更晚的车可以选的范围肯定是只有右边的一段。所以先验证序列 (x) 合法。然后有个猜测,就是连续相同的一段 ([x_i,x_j]) 是可以互换的,要求他们可以互换则要 (b_igeq 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], "
"[i == n]);
}
错误算法3:首先要验证几个命题。
命题1:对所有的 (i) 满足 (1leq i leq n-1) ,都满足 (x_i leq x_{i+1}) 。简单来说就是序列 (x) 非降序。
证明:反证法,若存在某些 (i) 满足 (1leq 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}+tleq 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) 满足 (1leq i leq n) ,都满足 (x_i geq i) 。
证明:反证法,若存在某些 (i) 满足 (1leq 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_igeq 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 eq 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], "
"[i == n]);
}
D - Subway Pursuit
题意:地铁铁轨是一个长度为 (n(1leq n leq 10^{18})) 的数轴,在上面找一辆失控的车,它每次都会出现在整数位置。每次询问可以问一个区间,jury回答车是否在区间里面,注意车每次会向左或者向右移动至多 (k(0leq k leq 10)) 个单位长度,不会越界,询问至多4500次。
题解:先用大概60~70次“二分”就可以定位车在一个长度大约 (4k) 的区间里,然后随机抽一个数字进行询问。开始询问的条件应该是再“二分”不会使得区间有显著减少时,也就是 (frac{len}{2}+2kgeq len) ,简单起见可以直接取50。注意到每个数平均会被问40次(在问一次之后下一次一般就要进行“二分”,所以有一半的次数在“二分”),所以不成功的概率应该不高。
char s[1005];
bool query(ll l, ll r) {
printf("%lld %lld
", 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);
}
}
}
}