Codeforces Global Round 3
题目链接:https://codeforces.com/contest/1148
当年我不会做这个B题,却把CD过了。这个E题想错了。
A - Another One Bites The Dust
题意:有x个"a",y个"b",z个"ab",求最长的一个连续两个字符都不相同的字符串。
题解:肯定是先把z全部用了,易知'a'的数量和'b'的数量的相差不能超过1。
*B - Born This Way
这个题感觉不简单,没想到是1700难度的。
题意:要从A坐飞机中转B再到达C。A有n种航班飞到B,起飞时间分别是ai,飞行时间都是ta。B有m种航班飞到C,起飞时间分别是bj,飞行时间都是tb。取消不超过k种航班,使得最早的到达C的时间最迟。
题解:一开始想着贪心,但是怎么知道要使得这个B航班失效,是直接把他取消还是把能到他的A航班都取消呢?这个不能贪心。但是观察一段时间之后会发现,A航班总是取消最早的连续x种是最好的,因为假如也是取消x种,假如不是最早的连续x种,那么后面的就白白浪费取消的次数。然后又可以惊人的发现,枚举x之后,再取消第一个没有被取消的A航班能接上的接下来的k-x种B航班,就是最优的。也就是说是贪心取消掉最早的A航班,然后贪心取消掉剩下的有用的B航班里面最早的。
枚举这个x,然后双指针推一下。
不要溢出。
ll a[200005];
ll b[200005];
void TestCase() {
int n, m, k;
ll ta, tb;
scanf("%d%d%lld%lld%d", &n, &m, &ta, &tb, &k);
for(int i = 1; i <= n; ++i)
scanf("%lld", &a[i]);
for(int j = 1; j <= m; ++j)
scanf("%lld", &b[j]);
if(k >= n || k >= m) {
puts("-1");
return;
}
ll ans = 0;
int curj = 1;
//由于下面是枚举取消A航班的数量,所以上界是k+1,对应全部取消,易知a[k+1]一定存在
for(int i = 1; i <= k + 1; ++i) {
//取消掉前面的前i-1种A航班
while(curj <= m && a[i] + ta > b[curj]) {
//双指针找到第i种A航班能使用的第1个B航班,记为curj
++curj;
}
int t = k - (i - 1);
//若剩余的B航班可以全部取消,输出-1
if((m - curj + 1) <= t) {
puts("-1");
return;
}
//取消[curj,curj+t-1]的B航班
ans = max(ans, b[curj + t] + tb);
}
printf("%lld\n", ans);
}
C - Crazy Diamond
这个题也比较有想法。
题意:给一个n个数的排列,n是偶数,使用不超过5n次交换,给这个数组排序,要求每次交换的时候,选择的两个下标(i,j),满足2|i-j|>=n,换言之就是两个要隔得足够远,至少要隔半个数列的长度。
题解:这样看见“半个数列的长度”,就想到可以充分利用第1个元素和第n个元素来搞事,易知前半区间[1,n/2]的都可以和第n个元素交换,后半区间[n/2+1,n]的都可以和第1个元素交换。
先写一个交换函数,这样就不怕搞错了。
void myswap(int x, int y) {
swap(a[x], a[y]);
swap(pos[a[x]], pos[a[y]]);
ans[++atop] = {x, y};
}
每次调整第i个元素的时候,都满足前i-1个元素复位。
那么设数字i的位置为x,若2|x-i|>=n,就可以直接交换。
否则就要借助一些外力了。
以n=8为例:
1、若i<=n/2,且x>n/2:
由于他们离得比较近,所以i不会是1,x也不会是n
1.i.x..n
先交换(x,1)
x.i.1..n
再交换(1,n)
n.i.1..x
再交换(i,n)
n.x.1..i
再交换(x,1)
1.x.n..i
这样就完成了任务,至于n和i是怎么样的,就留给后面的迭代自己处理。
2、若i<=n/2,且x<=n/2:
1.ix...n
先交换(i,n)
1.nx...i
再交换(x,n)
1.ni...x
再交换(i,n)
1.xi...n
又完成了任务。
否则,两者都必定在右半区间,把上面的第2种情况反过来就可。
int n;
int a[300005];
int pos[300005];
pii ans[1500005];
int atop;
void myswap(int x, int y) {
swap(a[x], a[y]);
swap(pos[a[x]], pos[a[y]]);
ans[++atop] = {x, y};
}
void solve(int i) {
if(a[i] == i)
return;
int x = pos[i];
if(2 * (x - i) >= n) {
myswap(x, i);
return;
}
if(i <= n / 2) {
if(x > n / 2) {
myswap(x, 1);
myswap(1, n);
myswap(n, i);
myswap(x, 1);
return;
} else {
myswap(i, n);
myswap(x, n);
myswap(i, n);
return;
}
} else {
myswap(i, 1);
myswap(x, 1);
myswap(i, 1);
return;
}
}
void TestCase() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
pos[a[i]] = i;
}
atop = 0;
for(int i = 1; i <= n; ++i)
solve(i);
printf("%d\n", atop);
for(int i = 1; i <= atop; ++i)
printf("%d %d\n", ans[i].first, ans[i].second);
return;
}
*D - Dirty Deeds Done Dirt Cheap
这道题这么难,我当年居然会?
题意:给n对数字<ai,bi>,每个数字都是不同的,且正好是[1,2n],选出最长的一个集合,使得他们可以排列成一个“好串”。一个“好串”,是符号为"a1
题解:可以确定每对数字的符号是'<'还是'>',显然不同种符号是不可以接在一起的。那么就先按内部符号分成两类,然后我惊人地发现,这些可以全部排在一起!比如都是'<'号的,中间都必须用'>'号连接,那么假如把这些按右边的数字bi的大小从大到小排序,那么b1>b2,而a2<b2,所以一定是能接上的,对称的情况同理。
struct Node {
int id;
int l, r;
} nd[300005];
pii p1[300005],p2[300005];
void TestCase() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
nd[i].id = i;
scanf("%d%d", &nd[i].l, &nd[i].r);
}
int m1 = 0, m2 = 0;
for(int i = 1; i <= n; ++i) {
if(nd[i].l < nd[i].r) {
++m1;
p1[m1].first = -nd[i].r;
p1[m1].second = nd[i].id;
} else {
++m2;
p2[m2].first = nd[i].r;
p2[m2].second = nd[i].id;
}
}
if(m1 >= m2) {
sort(p1 + 1, p1 + 1 + m1);
printf("%d\n", m1);
for(int i = 1; i <= m1; ++i)
printf("%d%c", p1[i].second, " \n"[i == m1]);
}
else{
sort(p2 + 1, p2 + 1 + m2);
printf("%d\n", m2);
for(int i = 1; i <= m2; ++i)
printf("%d%c", p2[i].second, " \n"[i == m2]);
}
}
*E - Earth Wind and Fire
题意:给n个石头,每次可以选两个石头,让他们彼此靠近一定距离,准确的说,选择两个(i,j),下标为pi,pj且pi<pj,然后选择一个d满足0<=2d<=pj-pi,再换成pi+d和pj-d,也就是挨近了一点。要求使用不超过5n次操作从石头序列s构造出石头序列t。注意石头序列t是无序的。
题解:注意到操作不会改变所有元素的和,所以要是s序列的和与t序列的和不一样就是NO。然后假如某次t序列的无法构造,也是NO。一开始觉得是每次取s序列两端的出来向中间靠,直到碰到第一个t序列石头t为止,这样每次可以至少消除一个t序列的石头。
写出来是这样的:
map<int, vector<int> > MAP;
deque<int> DEQUE;
int t[300005];
struct Ans {
int i, j, d;
Ans(int i = 0, int j = 0, int d = 0): i(i), j(j), d(d) {}
} ans[300005];
int atop;
void TestCase() {
int n;
scanf("%d", &n);
ll sum1 = 0;
for(int i = 1; i <= n; ++i) {
int x;
scanf("%d", &x);
MAP[x].push_back(i);
sum1 += x;
}
ll sum2 = 0;
for(int i = 1; i <= n; ++i) {
scanf("%d", &t[i]);
sum2 += t[i];
}
if(sum1 != sum2) {
puts("NO");
return;
}
sort(t + 1, t + 1 + n);
for(int i = 1; i <= n; ++i)
DEQUE.push_back(t[i]);
while(DEQUE.size()) {
int pos1 = MAP.begin()->first;
int id1 = MAP.begin()->second.back();
int ypos1 = DEQUE.front();
if(pos1 == ypos1) {
MAP.begin()->second.pop_back();
DEQUE.pop_front();
if(MAP.begin()->second.empty())
MAP.erase(pos1);
continue;
}
int pos2 = MAP.rbegin()->first;
int id2 = MAP.rbegin()->second.back();
int ypos2 = DEQUE.back();
if(pos2 == ypos2) {
MAP.rbegin()->second.pop_back();
DEQUE.pop_back();
if(MAP.rbegin()->second.empty())
MAP.erase(pos2);
continue;
}
MAP.begin()->second.pop_back();
MAP.rbegin()->second.pop_back();
if(MAP.begin()->second.empty())
MAP.erase(pos1);
if(MAP.rbegin()->second.empty())
MAP.erase(pos2);
if(pos1 > ypos1 || pos2 < ypos2) {
puts("NO");
return;
}
int dis = min(ypos1 - pos1, pos2 - ypos2);
pos1 += dis;
pos2 -= dis;
if(pos1 == ypos1) {
DEQUE.pop_front();
if(pos2 == ypos2)
DEQUE.pop_back();
else
MAP[pos2].push_back(id2);
} else {
assert(pos2 == ypos2);
DEQUE.pop_back();
MAP[pos1].push_back(id1);
}
ans[++atop] = Ans(id1, id2, dis);
}
puts("YES");
printf("%d\n", atop);
for(int i = 1; i <= atop; ++i)
printf("%d %d %d\n", ans[i].i, ans[i].j, ans[i].d);
return;
}
但是这样是错的,比如下面的例子:
4
1 3 5 7
2 2 6 6
假如按照上面的算法,会先变成(1,7)->(2,6):
2 3 5 6
然后报告NO。
但是实际上可以用(1,3)->(2,2),(5,7)->(6,6)。
另一种思路是,先给s序列和t序列排序,那么他们就是对应的一一对应的移动关系,这样每个石头有可能是需要左移,也有可能是需要右移。但是第一个石头绝对是要右移,那么就找到第一个左移的石头,移动min(石头2左移的距离,石头1右移的距离),这样至少会把一个石头归位。易知需要左移的总和和需要右移的总和是相等的,所以是一定可以构造的。
pii s[300005];
int t[300005];
struct Ans {
int i, j, d;
Ans(int i = 0, int j = 0, int d = 0): i(i), j(j), d(d) {}
} ans[300005];
int atop;
void AddAns(int i, int j, int d) {
s[i].first += d;
s[j].first -= d;
ans[++atop] = Ans(s[i].second, s[j].second, d);
}
void TestCase() {
int n;
scanf("%d", &n);
ll sums = 0;
for(int i = 1; i <= n; ++i) {
scanf("%d", &s[i].first);
s[i].second = i;
sums += s[i].first;
}
ll sumt = 0;
for(int i = 1; i <= n; ++i) {
scanf("%d", &t[i]);
sumt += t[i];
}
if(sums != sumt) {
puts("NO");
return;
}
sort(s + 1, s + 1 + n);
sort(t + 1, t + 1 + n);
atop = 0;
for(int i = 1, j = 1; i <= n; ++i) {
if(s[i].first > t[i]) {
puts("NO");
return;
}
if(s[i].first == t[i])
continue;
while(s[i].first < t[i]) {
int d1 = t[i] - s[i].first;
j = max(j, i + 1);
while(j <= n && s[j].first <= t[j])
++j;
if(j > n) {
puts("NO");
return;
}
int d2 = s[j].first - t[j];
AddAns(i, j, min(d1, d2));
}
}
puts("YES");
printf("%d\n", atop);
for(int i = 1; i <= atop; ++i)
printf("%d %d %d\n", ans[i].i, ans[i].j, ans[i].d);
}