Codeforces Round #593 (Div. 2)
A - Stones
题意:有3堆石头a个,b个,c个,可以每次拿1个a堆的,2个b堆的,或者拿1个b堆的,2个c堆的。
题解:b堆是唯一的公共资源,考虑尽可能充分利用b堆。每2个b堆的参与操作2可以得6个,参与操作1只能得3个,而且参与操作2只需要b堆还剩下一个就可以执行,所以先做操作2。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int t;
scanf("%d", &t);
while(t--) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
int n1 = min(b, c / 2);
b -= n1;
int n2 = min(a, b / 2);
printf("%d\n", 3 * (n1 + n2));
}
return 0;
}
B - Alice and the List of Presents
题意:有n种礼物,m个盒子。每个盒子每种礼物个至多一个。每种礼物总共至少放一个。求方案数。
题解:把每种礼物不放的那种去除。每个盒子放和不放有两种,每种礼物有2^m-1种,全部乘起来。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
ll pow_mod(ll x, int n) {
ll res = 1;
while(n) {
if(n & 1)
res = res * x % mod;
x = x * x % mod;
n >>= 1;
}
return res;
}
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int n, m;
scanf("%d%d", &n, &m);
printf("%lld\n", pow_mod(pow_mod(2, m) - 1, n));
}
C - Labs
题意:有n*n个数,均分成n组,每个大数向小数连一条流量为1的边。组X与组Y间的单向流量就是X的每个元素流给Y的每个元素的和。分配这些元素使得流量的最小值最大。
题解:流量的最小值最大意思就是尽可能平均分配的意思。一开始以为是
1 4 7
2 5 8
3 6 9
这样分配,但很显然是不对的,居然还给交了一发。
这种题可以考虑模仿样例的做法,给样例排序,变成:
1 6 7
2 5 8
3 4 9
是个蛇形填数?为什么蛇形填数是最平均的呢?我们考虑只有偶数列的情况(n为偶数的情况),每相邻两列内部的流量都是2,而与其他列的流量都相等。但奇数的话会始终多1列出来没有办法解决。而可以证明出已经达到了最优解(把所有的流量均分n份的最小值已经达到)。
所以写个蛇形填数。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int g[305][305];
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int n;
scanf("%d", &n);
int cnt = 0, i = 1, j = 1, di = 1, dj = 0;
while(cnt < n * n) {
g[i][j] = ++cnt;
i += di;
j += dj;
if(i == n && di == 1) {
di = 0;
dj = 1;
} else if(i == n && dj == 1) {
di = -1;
dj = 0;
} else if(i == 1 && di == -1) {
di = 0;
dj = 1;
} else if(i == 1 && dj == 1) {
di = 1;
dj = 0;
}
}
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= n; ++j) {
printf("%d%c", g[i][j], " \n"[j == n]);
}
}
}
D - Alice and the Doll
题意:一个n*m格子矩阵,放一个人偶在左上角向右走,只能在每个格子最多右转一次,有k个障碍物。求是否能够一次走完矩阵的所有非障碍物格然后停留在任意位置。
题解:在每个格子最多右转一次,相当于每个格子只能走一次,否则就出不来了。容易想到障碍物必须也是占据一些蛇形的片段,并且留下的位置刚好可以让人偶走一个蛇形的绕到中心,但是怎么判断障碍物的形状呢?百思不得其解遂看题解。题解表示观察到人偶撞墙或者撞障碍物必转向(自己走过的路也是墙)。然后暴力模拟一遍,每次在数据结构里面搜索前进方向上最近的墙/障碍物,走到它面前。至多进行n+m次。
所以选择一个结构就是set,维护行和列分别两堆set,一个障碍物同时插入行列两个set里面。然后墙/自己走过形成的墙就设置一个最值把多走的部分截断就可以了。判断答案的方法就是暴力统计走过的格子数是否等于空格数。
注:实现的时候卡在了样例4,因为会让人偶走回头路,但是也不能够简单让人偶移动0格就退出,比如一条竖线的矩阵。修复的方法是在人偶的后方一个格子塞一个障碍物。然后卡在样例9,看了dalao的代码才知道错在哪里,首先while循环的停止条件错了,不应该是d>u||r>l,应该是d>=u&&r>=l,因为是闭区间。这个改进再加上后面堵一个障碍物就可以防止原路返回了,人偶会在中间不断转圈圈。不过最简单的办法是记录人偶原地不动的次数,>=2次就break掉。
其实并不需要堵格子,记录原地不动的次数之后就不会原路返回了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
set<int> sr[100005], sc[100005];
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int n, m, k;
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= n; ++i) {
sr[i].insert(0);
sr[i].insert(m + 1);
}
for(int i = 1; i <= m; ++i) {
sc[i].insert(0);
sc[i].insert(n + 1);
}
for(int i = 1; i <= k; ++i) {
int r, c;
scanf("%d%d", &r, &c);
sr[r].insert(c);
sc[c].insert(r);
}
int u = 1, d = n, l = 1, r = m;
int x = 1, y = 1, dir = 1, stay = 0;
ll cnt = 1;
while(d >= u && r >= l) {
if(dir == 1) {
int ny = min(r, *sr[x].upper_bound(y) - 1);
cnt += ny - y;
if(ny == y) {
if(stay == 0)
stay = 1;
else
break;
} else
stay = 0;
y = ny;
++u;
r = y;
dir = 2;
} else if(dir == 2) {
int nx = min(d, *sc[y].upper_bound(x) - 1);
cnt += nx - x;
if(nx == x) {
if(stay == 0)
stay = 1;
else
break;
} else
stay = 0;
x = nx;
--r;
d = x;
dir = 3;
} else if(dir == 3) {
int ny = max(l, *(--(sr[x].lower_bound(y))) + 1);
cnt += y - ny;
if(ny == y) {
if(stay == 0)
stay = 1;
else
break;
} else
stay = 0;
y = ny;
--d;
l = y;
dir = 4;
} else {
int nx = max(u, *(--(sc[y].lower_bound(x))) + 1);
cnt += x - nx;
if(nx == x) {
if(stay == 0)
stay = 1;
else
break;
} else
stay = 0;
x = nx;
++l;
u = x;
dir = 1;
}
}
puts((cnt == 1ll * n * m - k) ? "YES" : "NO");
}