http://codeforces.com/contest/1458

B. Glass Half Spilled

 

题意:有 nn 杯水,每杯最大容量为 aia_i,杯中已经有了 bib_i 的水,可以从任意杯中倒水至任意杯中,每次倒水时会洒出一半,问对于每个 1in1\le i\le n,选择 ii 杯,最多能有多少水。

dp

起初一直想着直接dp出答案,但是这样需要很多分类讨论,而且还涉及到小数的问题。

但是可以换一种思路,在dp时先不倒水,dp[i][j][k]dp[i][j][k] 表示前 ii 杯,选择 jj 杯,选中的杯子的总容量为 kk 的最大水量。

则转移就很简单了 dp[i][j][k]=dp[i1][j][k]dp[i][j][k]=dp[i-1][j][k] 不选当前杯子,dp[i][j][k]=dp[i1][j1][ka[i]]+b[i]dp[i][j][k]=dp[i-1][j-1][k-a[i]]+b[i] 选当前杯子。

再考虑答案,在考虑答案的时候再倒水。

对于 dp[i][j][k]dp[i][j][k],总容量为 kk,已经装了 dp[i][j][k]dp[i][j][k] 的水,未被选中的杯子中的水必然都要倒入被选中的杯子里,未被选中的杯子中的总水量为 bdp[i][j][k]\sum b - dp[i][j][k],所以被选中的杯子中最大水量就是 min(k,dp[i][j][k]+bdp[i][j][k]2)\min(k,dp[i][j][k]+\frac{\sum b - dp[i][j][k]}{2}).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <bits/stdc++.h>
#define debug(x) cout << #x << ":\t" << (x) << endl;
using namespace std;
#define ll long long
#define ull unsigned ll
const int N = 40010;
const int INF = 0x3f3f3f3f;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
const ll inv2 = (mod + 1) / 2;
int n;
int a[N], b[N];
int dp[110][N], tmp[110][N];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++)scanf("%d%d", &a[i], &b[i]);
int suma = 0, sumb = 0;
for (int i = 0; i <= n; i++)for (int j = 0; j < N; j++)dp[i][j] = -INF;
dp[0][0] = 0;
for (int i = 1; i <= n; i++) {
suma += a[i];
sumb += b[i];
for (int j = 0; j <= i; j++)for (int k = 0; k <= suma; k++)tmp[j][k] = dp[j][k];
for (int j = 1; j <= i; j++) {
for (int k = a[i]; k <= suma; k++) {
dp[j][k] = max(dp[j][k], tmp[j - 1][k - a[i]] + b[i]);
}
}
}
for (int i = 1; i <= n; i++) {
double ans = 0;
for (int j = 0; j < N; j++)ans = max(ans, min(dp[i][j] + (sumb - dp[i][j])*0.5, 1.0*j));
printf("%.9lf%c", ans, " \n"[i == n]);
}
return 0;
}

 

C. Latin Square

 

题意:给定一个拉丁矩阵,即每行每列都是 1 到 n 的排列,m 次操作,每次把矩阵整体循环右移一项,或循环左移一项,或循环上移一项,或循环下移一项,或对每一行 p[i]=jp[i]=j 变成 p[j]=ip[j]=i,或对每一列 p[i]=jp[i]=j 变成 p[j]=ip[j]=i,问最终矩阵。

思维

算是一个比较有趣的套路。

矩阵可以看作一个三元组 (i,j,a[i][j])(i,j,a[i][j])。每次操作都是对所有元素操作,所以可以对这个三元组进行维护。

例如右移:(i,j,a[i][j])(i,j,a[i][j]) 变为 (i,j+1,a[i][j])(i,j+1,a[i][j])

例如对每行的变换:(i,j,a[i][j])(i,j,a[i][j]) 变为 (i,a[i][j],j)(i,a[i][j],j)

所以设定三个变量分别代表这哥三元组中三个位置的增量。又由于会有交换三元组中某两个位置的操作,所以还要有三个变量分别指向对应的增量。

这三个数始终是 1 到 n 范围内的,包括 a[i][j]a[i][j],所以最终结果也必须取模到 1 到 n 范围内。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <bits/stdc++.h>
#define debug(x) cout << #x << ":\t" << (x) << endl;
using namespace std;
#define ll long long
#define ull unsigned ll
const int N = 2e6 + 10;
const int INF = 0x3f3f3f3f;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
const ll inv2 = (mod + 1) / 2;
int T;
int n, m;
int a[1010][1010], b[3], ad[3], tmp[3], ans[1010][1010];
char s[N];
int main() {
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++)
scanf("%d", &a[i][j]);
}
b[0] = 0; b[1] = 1; b[2] = 2;
ad[0] = ad[1] = ad[2] = 0;
scanf("%s", s);
for (int i = 0; i < m; i++) {
if (s[i] == 'R') {
ad[b[1]]++;
}
else if (s[i] == 'L') {
ad[b[1]]--;
}
else if (s[i] == 'D') {
ad[b[0]]++;
}
else if (s[i] == 'U') {
ad[b[0]]--;
}
else if (s[i] == 'I') {
swap(b[1], b[2]);
}
else if (s[i] == 'C') {
swap(b[0], b[2]);
}
}
ad[0] = (ad[0] + n) % n;
ad[1] = (ad[1] + n) % n;
ad[2] = (ad[2] + n) % n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
tmp[0] = i, tmp[1] = j, tmp[2] = a[i][j];
ans[(tmp[b[0]] + ad[b[0]] - 1 + n) % n + 1][(tmp[b[1]] + ad[b[1]] - 1 + n) % n + 1] = (tmp[b[2]] + ad[b[2]] - 1 + n) % n + 1;
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
printf("%d%c", ans[i][j], " \n"[j == n]);
}
}
}
return 0;
}