二分答案,整除分块,dp + 推结论,曼哈顿转切比雪夫 + 4维区间dp
# A. 棍子 sticks
sticks 写成了 stick,遗憾爆零/kk
签到题,就是一道二分答案板子。
不知道是什么原因,大样例卡精度死循环,于是直接 check 了 次,喜提最劣解。
不得不佩服 神机,全场就 一个人有分orz
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
using namespace std;
namespace IO{
inline int read(){
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x * f;
}
template <typename T> inline void write(T x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace IO;
const int N = 60;
int n, m, k;
double a[N], mx;
inline bool check(double mid){
int cnt = 0, tot = 0;
for(int i = 1; i <= n; ++i){
if(tot == m) {cnt += (a[i] >= mid); continue;}
int tmp = floor(a[i] / mid);
if(tmp - 1 + tot >= m) cnt += (m - tot) + 1, tot = m;
else if(tmp > 0) cnt += tmp, tot += (tmp - 1);
}
return cnt >= k && tot <= m;
}
signed main(){
// freopen("sticks.in", "r", stdin);
// freopen("sticks.out", "w", stdout);
n = read(), m = read(), k = read();
for(int i = 1; i <= n; ++i) scanf("%lf", &a[i]), mx = max(mx, a[i]);
double l = 0, r = mx, res = 0;
for(int i = 1; i <= 2e6; ++i){
double mid = (l + r) / 2;
// cout << l << " " << r << " " << mid << " " << r - l << " " << check(mid) << endl;
if(check(mid)) res = mid, l = mid;
else r = mid;
}
printf("%.12lf\n", res);
return 0;
}
# B. 数组 count
看题 后就想到了整除分块,结果还是想假了,把 和 放到一块计算了,不知道当时脑子是怎么想的。
关键样例还都过了?我直接缓缓打出一个?
还有 ,最后自然是喜提一个 0 分。
思路比较简单,差分的思想,算 的答案减去 的答案。对 整除分块,算出 ,跟 的范围取个交即可。
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
using namespace std;
namespace IO{
inline int read(){
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x * f;
}
template <typename T> inline void write(T x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace IO;
int A1, A2, B1, B2, C1, C2;
int ans;
inline void solve_1(){
for(int i = A1; i <= A2; ++i){
int t1 = ceil(1.0 * C1 / i), t2 = C2 / i;
cout << t1 << " " << t2 << endl;
ans += max(0ll, min(t2, B2) - max(t1, B1) + 1);
}
}
inline int solve(int n){
int res = 0;
if(!n) return 0;
for(int l = A1, r = 0; l <= A2; l = r + 1){
if(n / l <= 0) break;
r = min(A2, n / (n / l));
int t = n / l;
if(t >= B1) res += (min(B2, t) - B1 + 1) * (r - l + 1);
}
return res;
}
signed main(){
// freopen("test.in", "r", stdin);
// freopen("test.out", "w", stdout);
scanf("%lld%lld%lld%lld%lld%lld", &A1, &A2, &B1, &B2, &C1, &C2);
write(solve(C2) - solve(C1 - 1)), puts("");
return 0;
}
# C. 十一 eleven
next_permutation 谁不会啊,喜提 20 分。
观察到部分分:每个数字位数相同。
考场上实在是想不出来这玩意有啥用/kk
稍微模拟一下可知:
-
对于偶数位数的数:数字 经过这个数之后形成的数一定是 这样形式的。
-
对于奇数位数的数:数字 经过之后一定是 这样子的。
先考虑部分分。
如果所有数都是偶数位数的,那么最终的数为:
所以我们只需要确定一下所有数的和是否能整除 11 即可,答案就是 。
对于奇数位数,表达式为:
展开之后为:
不难发现这东西正负号是交替的,所以设 表示 个正, 个负,这些数的和 的值为 时的方案数。
转移的时候对于新加进来的数放到正数位置还是负数位置分别考虑:
有转移方程:
但是一个奇数位数的数展开之后的正负号个数是固定的,分别为 (自己推一下吧)
部分分就做完了。
再来看满分如何做。
我们对偶数位数的数也进行一次上述 ,设为 。然后思考如何将两个 数组合并。
我们考虑把偶数位数的数插入到奇数位数的数中间,所以有些偶数位数的数对答案的贡献是正,有些是负的。
枚举有多少个是正的,多少个是负的。
值贡献为 ,但是偶数位数的数还有很多种放法。
-
对于奇数位数中的正数:
有 种排列方式,然后新插入一个数,有 个位置,对于插入的第二个数,有 个位置,以此类推。
所以总方案数为
-
对于计数位数中的负数:
实际上是有 个位置的,因为还要加上 的位置,例子见上面。所以放第一个数时就有 个位置,第二个数有 个位置,以此类推。
总方案数为
别的就没了,具体看代码吧。
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
using namespace std;
namespace IO{
inline int read(){
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x * f;
}
template <typename T> inline void write(T x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace IO;
const int N = 60;
const int mod = 1e9 + 7;
int n, c1, c2, cz, cf, cnt;
char s[N];
int nxt1[N], nxt2[N];
int f[N][N][15], g[N][N][15];
int fac[N], ifac[N];
inline void add(int &x, int y) {x = (x + y >= mod) ? x + y - mod : x + y;}
inline int add(int x) {return (x % 11 + 11) % 11;}
inline int sub(int x) {return x < 0 ? x + 11 : x;}
inline int mul(int x, int y) {return 1ll * x * y % mod;}
inline int qpow(int a, int b){
int res = 1;
while(b){
if(b & 1) res = 1ll * res * a % mod;
a = 1ll * a * a % mod, b >>= 1;
}
return res;
}
inline void init(){
fac[0] = 1;
for(int i = 1; i <= n; ++i) fac[i] = 1ll * fac[i - 1] * i % mod;
ifac[n] = qpow(fac[n], mod - 2);
for(int i = n - 1; i >= 0; --i) ifac[i] = 1ll * ifac[i + 1] * (i + 1) % mod;
}
inline int C(int n, int m){
return 1ll * fac[n] * ifac[m] % mod * ifac[n - m] % mod;
}
signed main(){
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
n = read(), init();
for(int i = 1, len, x = 0; i <= n; ++i, x = 0){
scanf("%s", s + 1), len = strlen(s + 1);
for(int j = 1; j <= len; ++j) x = (x * 10 + s[j] - '0') % 11;
if(len & 1) nxt1[++c1] = x;
else nxt2[++c2] = x;
}
f[0][0][0] = 1;
for(int p = 1; p <= c1; ++p)
for(int i = 0; i < p; ++i){
int j = p - i - 1;
for(int k = 0; k < 11; ++k){
add(f[i + 1][j][add(k + nxt1[p])], f[i][j][k]);
add(f[i][j + 1][add(k - nxt1[p])], f[i][j][k]);
}
}
g[0][0][0] = 1;
for(int p = 1; p <= c2 + 1; ++p)
for(int i = 0; i < p; ++i){
int j = p - i - 1;
for(int k = 0; k < 11; ++k){
add(g[i + 1][j][add(k + nxt2[p])], g[i][j][k]);
add(g[i][j + 1][add(k - nxt2[p])], g[i][j][k]);
}
}
if(!c1){
int x = 0;
for(int i = 1; i <= n; ++i) x = add(x + nxt2[i]);
if(!x) write(fac[n]), puts("");
else puts("0");
return 0;
}
cf = c1 / 2, cz = c1 - cf;
int ans = 0;
for(int i = 0; i <= c2; ++i)
for(int j = 0; j < 11; ++j){
int res = 1ll * f[cz][cf][add(11 - j)] * g[i][c2 - i][j] % mod;
add(ans, mul(mul(res, fac[cf + c2 - i]), mul(fac[cz + i - 1], cz)));
}
write(ans), puts("");
return 0;
}
# D. 棋盘 chess
一道区间 板题,然而考场上并没有想到/kk
首先曼哈顿距离转切比雪夫距离,坐标从 变成了 。
然后所求距离从 变成了
设 表示还剩这个矩形时,消掉这个矩形内所有点的最小代价。
使用记忆化搜索实现,每次转移的时候从四条边分别往里收缩一个单位即可。
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
63
64
65
using namespace std;
namespace IO{
inline int read(){
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x * f;
}
template <typename T> inline void write(T x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace IO;
const int N = 20;
const int inf = 1e9;
int n;
char s[N][N];
int a[N][N], f[N][N][N][N];
inline int dfs(int l, int r, int d, int u){
if(~f[l][r][d][u]) return f[l][r][d][u];
if(l == r && d == u) return 0;
int res = inf;
if(d < u){
int sd = 0, su = 0;
for(int i = l; i <= r; ++i){
if(a[i][u]) su += max(u - d, max(i - l, r - i));
if(a[i][d]) sd += max(u - d, max(i - l, r - i));
}
res = min(res, min(sd + dfs(l, r, d + 1, u), su + dfs(l, r, d, u - 1)));
}
if(l < r){
int sl = 0, sr = 0;
for(int i = d; i <= u; ++i){
if(a[l][i]) sl += max(r - l, max(i - d, u - i));
if(a[r][i]) sr += max(r - l, max(i - d, u - i));
}
res = min(res, min(sl + dfs(l + 1, r, d, u), sr + dfs(l, r - 1, d, u)));
}
return f[l][r][d][u] = res;
}
signed main(){
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
n = 8;
for(int i = 1; i <= n; ++i) scanf("%s", s[i] + 1);
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
if(s[i][j] == '#') a[i + j - 1][i - j + n] = 1;
memset(f, -1, sizeof(f));
write(dfs(1, 15, 1, 15)), puts("");
return 0;
}