2019牛客多校第十场

A. Blackjack

upsolved

题目等价于询问有\(n\)个有权值\(x_i\)的物品的一个随机排列,设\(pos\)为第一个前缀权值和大于\(a\)的位置,求\(\sum \limits_{i = 1}^{pos} x_i<=b\)的概率\((1 <= n <= 500, 1 <= a < b <= 500, \sum x_i>b)\)

\(O(n)\)枚举使得前缀权值和大于\(a\)的物品,设\(dp[i][j][k]\)表示不考虑当前枚举的物品\(p\),前\(i\)个物品使用\(j\)个权值和为\(k\)的方案数,显然满足\(n = i,0 <= j < n,max(0, a - x_p + 1) <= k <= min(a, b - x_p)\)的dp值会贡献到答案里去

注意到此时的\(dp\)值算的仅仅是组合,你要计算排列,方案数就要乘上

\(j!*(n-j-1)!\)

如果每次枚举都重新计算背包,复杂度就是\(O(n^4)\)了,可以考虑退背包,先计算出包含所有物品的背包,然后每次枚举时撤销当前物品,计算完了再还原,时间复杂度就降为\(O(n^3)\),空间复杂度可以利用滚动数组优化成\(O(n^2)\)

做完6题的时候没什么时间了,如果时间足够这题应该还是能做出来的...

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
#include <bits/stdc++.h>
using namespace std;

const int N = 510;

double dp[2][N][N], p[N], ans;
int n, a, b, x[N];

int main() {
scanf("%d%d%d", &n, &a, &b);
dp[0][0][0] = 1;
for(int i = 1; i <= n; ++i) {
scanf("%d", &x[i]);
memcpy(dp[i & 1], dp[(i & 1) ^ 1], sizeof(dp[0]));
for(int j = 1; j <= n; ++j)
for(int k = x[i]; k <= b; ++k)
dp[i & 1][j][k] += dp[(i & 1) ^ 1][j - 1][k - x[i]];
}
p[1] = 1.0 / n;
for(int i = 2; i <= n; ++i)
p[i] = p[i - 1] * (i - 1) / (n - i + 1);
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= n; ++j)
for(int k = x[i]; k <= b; ++k)
dp[n & 1][j][k] -= dp[n & 1][j - 1][k - x[i]];
for(int j = 0; j < n; ++j) {
for(int k = max(0, a - x[i] + 1); k <= a && k + x[i] <= b; ++k)
ans += dp[n & 1][j][k] * p[j + 1];
}
for(int j = n; j >= 1; --j) {
for(int k = b; k >= x[i]; --k)
dp[n & 1][j][k] += dp[n & 1][j - 1][k - x[i]];
}
}
printf("%.15f\n", ans);
return 0;
}

B. Coffee Chicken

solved at 00:13(+1)

考虑一种像斐波那契数列那样的字符串,\(s[1] = "COFFEE", S[2] = "CHICKEN", s[i] = s[i - 2] + s[i - 1](i>2)\)

每次给你\(n, k(1<=n<=500, 1<=k<=1e12)\)

要求输出第\(n\)个字符串的第\(k\)个到第\(k+9\)个字符(没有则不输出)

预处理出\(500\)每个字符串的长度然后递归处理就好了,由于会爆掉\(long long\)可以与\(1e12+9\)取个min

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
#include <bits/stdc++.h>
using namespace std;

const long long MAX = 1e12 + 9;

long long f[510], k;
int T, n;
char s1[] = "COFFEE", s2[] = "CHICKEN";

void get(int x, long long num) {
if(x == 1) cout << s1[num - 1];
if(x == 2) cout << s2[num - 1];
if(x <= 2) return;
if(f[x - 2] >= num) get(x - 2, num);
else get(x - 1, num - f[x - 2]);
}

int main() {
f[1] = 6, f[2] = 7;
for(int i = 3; i <= 500; ++i)
f[i] = min(MAX, f[i - 1] + f[i - 2]);
cin >> T;
while(T--) {
cin >> n >> k;
for(long long i = k; i < k + 10 && i <= f[n]; ++i)
get(n, i);
cout << endl;
}
return 0;
}

D. Han Xin and His Troops

solved at 01:20(+1)

韩信点兵问题,至多\(100\)个同余式,不保证模数互质,判断无解或是没有小于等于\(m\)的解或是输出最小的解,数字范围1e5

抄了个excrt, 然后wa了,意识到可能会爆int, 就用java改写,结果一个括号位置错了调了半天...

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
import java.io.*;
import java.util.*;
import java.math.*;

public class Main {
static int n;
static BigInteger m;
static BigInteger[] a = new BigInteger[110], r = new BigInteger[110];
static BigInteger[] exgcd(BigInteger a, BigInteger b) {
BigInteger[] res = new BigInteger[3];
if(b.equals(BigInteger.ZERO)) {
res[0] = a;
res[1] = BigInteger.ONE;
res[2] = BigInteger.ZERO;
return res;
}
BigInteger[] tmp = exgcd(b, a.mod(b));
res[0] = tmp[0];
res[1] = tmp[2];
res[2] = tmp[1].subtract(a.divide(b).multiply(tmp[2]));
return res;
}
static BigInteger excrt() {
BigInteger M = a[1], R = r[1], x, y, d;
for(int i = 2; i <= n; ++i) {
BigInteger[] rrr = exgcd(M, a[i]);
d = rrr[0]; x = rrr[1]; y = rrr[2];
if(!(R.subtract(r[i]).mod(d)).equals(BigInteger.ZERO)) return BigInteger.valueOf(-1);
x = (R.subtract(r[i]).divide(d).multiply(x)).mod(a[i]);
R = R.subtract(M.multiply(x));
M = M.divide(d).multiply(a[i]);
R = R.mod(M);
}
return R.mod(M).add(M).mod(M);
}
public static void main(String[] args) {
Scanner cin = new Scanner(System.in);
n = cin.nextInt();
m = cin.nextBigInteger();
for(int i = 1; i <= n; ++i) {
a[i] = cin.nextBigInteger();
r[i] = cin.nextBigInteger();
}
BigInteger res = excrt();
if(res.equals(BigInteger.valueOf(-1))) {
System.out.println("he was definitely lying");
}
else if(res.compareTo(m) > 0) {
System.out.println("he was probably lying");
}
else {
System.out.println(res);
}
}
}

E. Hilbert Sort

solved at 01:54

队友做的

F. Popping Balloons

solved at 03:51(+1)

二维平面上有\(n\)个点,你要选择三条横线和三条竖线,相邻横线(竖线)之间距离为定值\(r\),求这些线能穿过的最多点数\((1<=n, r<=1e5, 0 <= x, y <= 1e5)\)

考虑横线竖线只有一条,显然枚举竖线权值线段树维护某一横坐标的点数,枚举到竖线的时候先把这条竖线上的点丢出去计算完了再放回来就好了,三条也照做,只要考虑点能对哪些位置的线产生贡献就好了

ps:可以不写权值线段树直接用multiset

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
#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;
const int lim = 1e5 + 1;

int mx[N << 2], n, r, x, y;

void update(int rt, int l, int r, int pos, int val) {
if(l == r) {
mx[rt] += val;
return;
}
int mid = l + r >> 1;
if(pos <= mid)
update(rt << 1, l, mid, pos, val);
else
update(rt << 1 | 1, mid + 1, r, pos, val);
mx[rt] = max(mx[rt << 1], mx[rt << 1 | 1]);
}

vector<int> c[N];

int main() {
scanf("%d%d", &n, &r);
for(int i = 1; i <= n; ++i) {
scanf("%d%d", &x, &y);
x++, y++;
c[y].push_back(x);
if(y - r >= 1)
c[y - r].push_back(x);
if(y + r <= lim)
c[y + r].push_back(x);
update(1, 1, lim, x, 1);
if(x - r >= 1)
update(1, 1, lim, x - r, 1);
if(x + r <= lim)
update(1, 1, lim, x + r, 1);
}
int ans = 0;
for(int y = 1; y <= lim; ++y) {
for(auto i: c[y]) {
update(1, 1, lim, i, -1);
if(i - r >= 1)
update(1, 1, lim, i - r, -1);
if(i + r <= lim)
update(1, 1, lim, i + r, -1);
}
ans = max(ans, (int)c[y].size() + mx[1]);
for(auto i: c[y]) {
update(1, 1, lim, i, 1);
if(i - r >= 1)
update(1, 1, lim, i - r, 1);
if(i + r <= lim)
update(1, 1, lim, i + r, 1);
}
}
printf("%d\n", ans);
return 0;
}

H. Stammering Chemists

solved at 00:25

给你一个己烷的图表示,判断是哪一种己烷

点度数看一看就好了...

J. Wood Processing

solved at 03:25(+2)

\(n\)块木板拼成\(k\)块(所有的木板必须都参与),拼接的方式是横向粘在一起,纵向选择一个小于等于参与拼接的木板的高度的最小值的值,然后把所有木板砍成这个高度,砍掉的部分扔掉不能重新利用,木板不能旋转,求砍掉的部分最少的方案数\((1<=n<=5000, 1<=k<=3000)\)

首先将木板按高度排序,显然最后的\(k\)块木板一定是\(k\)个连续区间

\(dp[i][j]\)表示前\(i\)块木板拼成\(j\)块的最小浪费值,则\(dp[i][j] = \min\limits_{k=0}^{i-1}(dp[k][j - 1] + sum[i] - sum[k] - (sumw[i] - sumw[k]) * h[k + 1])\)

这个东西直接转移复杂度是\(O(n^2k)\)的,但是可以用斜率优化成\(O(nk)\)

(也可以用四边形不等式优化)

因为while里一个括号位置错了调了一个小时...

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
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 5010;

struct B {
int w, h;
bool operator<(const B &rhs) const {
return h < rhs.h;
}
}a[N];

int n, K;
long long dp[N][2010], sum[N], sw[N];
int que[N], head, tail;

inline LL Y(int t, int x) {return dp[x][t - 1] + sw[x] * a[x + 1].h - sum[x];}
inline LL KY(int t, int i, int j) {return Y(t, i) - Y(t, j);}
inline LL X(int x) {return a[x + 1].h;}
inline LL KX(int i, int j) {return X(i) - X(j);}

int main() {
scanf("%d%d", &n, &K);
for(int i = 1; i <= n; ++i)
scanf("%d%d", &a[i].w, &a[i].h);
sort(a + 1, a + n + 1);
a[n + 1].h = a[n + 1].w = 1e7;
for(int i = 1; i <= n; ++i) {
sum[i] = sum[i - 1] + 1LL * a[i].h * a[i].w;
sw[i] = sw[i - 1] + a[i].w;
}
memset(dp, 0x3f, sizeof(dp));
for(int i = 1; i <= n; ++i)
dp[i][1] = sum[i] - a[1].h * sw[i];
for(int k = 2; k <= K; ++k) {
head = tail = 0;
dp[k][k] = 0;
que[tail++] = k - 1;
for(int i = k; i <= n; ++i) {
while(head < tail - 1 && (__int128)KY(k, que[head + 1], que[head]) <= (__int128)sw[i] * KX(que[head + 1], que[head])) head++;
int j = que[head];
dp[i][k] = dp[j][k - 1] + sum[i] - sum[j] - (sw[i] - sw[j]) * a[j + 1].h;
while(head < tail - 1 && (__int128)KY(k, que[tail - 1], que[tail - 2]) * KX(i, que[tail - 1]) >= (__int128)KY(k, i, que[tail - 1]) * KX(que[tail - 1], que[tail - 2])) tail--;
que[tail++] = i;
}
}
printf("%lld\n", dp[n][K]);
return 0;
}

2019牛客多校结束了,可是题还没补完