2019牛客多校第七场

荣膺全场罚时最高,为什么今天和昨天都是6题,今天的我却没有了昨天的快乐呢。。。

A. String

solve at 00:28

给你一个\(01\)字符串\(s\),你要将它分割成数量尽可能少的若干个串,使得每个串都是它的所有循环同构串中字典序最小的\((1<=T<=300, 1<=|s|<=200)\) 看上去就感觉贪心是对的,就是每次尽可能选最长的符合条件的串,然后套一个最小表示法就过了

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

int T, n;
char s[210];
vector<pair<int, int>> ans;

bool min_represent(int l, int r) {
static char ss[210];
for(int i = l; i <= r; ++i) {
ss[i - l] = s[i];
}
int i = 0, j = 1, k = 0, len = r - l + 1;
while(i < len && j < len && k < len) {
int t = ss[(i+k) % len] - ss[(j+k) % len];
if(t == 0)
++k;
else {
if(t > 0)
i += k + 1;
else
j += k + 1;
if(i == j)
++j;
k = 0;
}
}
return min(i, j) == 0;
}

void judge() {
int l = 0, r;
while(l < n) {
for(r = n - 1; r >= l; --r) {
if(min_represent(l, r)) {
ans.push_back({l, r});
break;
}
}
l = r + 1;
}
}

int main() {
scanf("%d", &T);
while(T--) {
ans.clear();
scanf("%s", s);
n = strlen(s);
judge();
for(int i = 0; i < ans.size(); ++i) {
for(int j = ans[i].first; j <= ans[i].second; ++j)
putchar(s[j]);
putchar(" \n"[i + 1 == ans.size()]);
}
}
return 0;
}

B. Irreducible Polynomial

solved at 04:50(+38)

是的,这就是我们荣获罚时冠军的原因

题意是给你一个多项式,询问这个多项式能否在实数域内因式分解

然后我就口胡了一个假算法,即判断这个函数有没有零点(这是不对的,x4+2x2+1$没有实数域上的零点,然而显然可以因式分解)

于是我们就写了模拟退火求最小值,牛顿迭代求零点等方法,调了无数次的参。。。

正解是这样的:

首先\(n\)次方程在复数域上有\(n\)个根,若\(f(\omega)=0\),则\(f(\overline \omega)=0\), 即原函数可以提取出\((x-\omega)(x-\overline \omega)\),而这两个东西乘起来就没有虚数部分了,因此三次及以上的多项式在实数域上一定可以因式分解

零次和一次显然不可以,二次看判别式。。。

C. Governing sand

solved at 02:51(+7)

\(n\)种树,每种树有高度,数量以及砍伐代价,你要砍掉一些树使得最高的树的数量超过一半,求最小代价\((1<=n<=1e5)\)

和杭电多校第三场的1007几乎一样,从小到大枚举高度线段树维护数量和代价就好了

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

const int N = 1e5 + 10;

struct GKP {
int h, c, p, cc;
}a[N];
int n, b[N], m;
long long sum[N];

long long cost[N << 2], cnt[N << 2];

void build(int rt, int l, int r) {
cost[rt] = cnt[rt] = 0;
if(l == r) return;
int mid = l + r >> 1;
build(rt << 1, l, mid);
build(rt << 1 | 1, mid + 1, r);
}

void update(int rt, int l, int r, int pos, int val, int num) {
if(l == r) {
cnt[rt] += num;
cost[rt] += 1LL * val * num;
return;
}
int mid = l + r >> 1;
if(pos <= mid) update(rt << 1, l, mid, pos, val, num);
else update(rt << 1 | 1, mid + 1, r, pos, val, num);
cost[rt] = cost[rt << 1] + cost[rt << 1 | 1];
cnt[rt] = cnt[rt << 1] + cnt[rt << 1 | 1];
}

long long query(int rt, int l, int r, long long num) {
if(num <= 0) return 0;
if(l == r)
return cost[rt] / cnt[rt] * num;
int mid = l + r >> 1;
if(cnt[rt << 1] < num) return cost[rt << 1] + query(rt << 1 | 1, mid + 1, r, num - cnt[rt << 1]);
else return query(rt << 1, l, mid, num);
}

int main() {
while(~scanf("%d", &n)) {
for(int i = 1; i <= n; ++i) {
scanf("%d%d%d", &a[i].h, &a[i].c, &a[i].p);
b[i] = a[i].c;
}
sort(a + 1, a + n + 1, [&](GKP &i, GKP &j){return i.h < j.h;});
sum[n + 1] = 0;
for(int i = n; i; --i) {
sum[i] = sum[i + 1] + 1LL * a[i].c * a[i].p;
}
long long ans = 1e18, cnt = 0, tot = 0;
sort(b + 1, b + n + 1);
m = unique(b + 1, b + n + 1) - b - 1;
stack<GKP> s;
for(int i = 1; i <= n; ++i)
a[i].cc = lower_bound(b + 1, b + m + 1, a[i].c) - b;
build(1, 1, m);
for(int i = 1; i <= n; ++i) {
tot += a[i].p;
if(a[i].h == a[i - 1].h) {
cnt += a[i].p;
s.push(a[i]);
}
else {
while(!s.empty()) {
GKP u = s.top(); s.pop();
update(1, 1, m, u.cc, u.c, u.p);
}
s.push(a[i]);
cnt = a[i].p;
}
ans = min(ans, query(1, 1, m, tot - 2 * cnt + 1) + sum[i + 1]);
}
cout << ans << endl;
}
return 0;
}

D. Number

solved at 00:11

给你一个\(n\)和一个\(p\),你要输出一个\(n\)位数,且是\(p\)的倍数

如果\(n\)小于\(p\)的位数则无解,否则直接输出\(p\),后面补上足够的\(0\)即可

E. Find the median

upsolved

你有一个初始为空的数列,\(n(1<=n<=4e5)\)次操作,每次给定一个区间\(L, R\),你将区间里所有数加入到数列中,然后求数列的中位数(偶数个取较小值)

\(R[i]\)全部自增1后离散化,会得到至多\(2n\)个点,实际上每个点代表一个区间,代表的是大于等于当前这个点且小于后一个点的区间,对这些点建线段树,维护区间里数的总数以及区间被更新了多少次(非叶子节点的这个值只用于延迟更新,只有叶子节点的参与最终计算),然后支持查询第\(k\)大的数就好了

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

const int N = 4e5 + 10;

int x[N], y[N], A[3], B[3], C[3], M[3], L[N], R[N];
int num[N << 1], tot, n;
long long pre[N << 1], now;

int lazy[N << 3], cnt[N << 3];
long long sum[N << 3];

void pushup(int rt) {
sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}

void pushdown(int rt, int l, int r) {
int mid = l + r >> 1;
if(lazy[rt]) {
lazy[rt << 1] += lazy[rt];
lazy[rt << 1 | 1] += lazy[rt];
cnt[rt << 1] += lazy[rt];
cnt[rt << 1 | 1] += lazy[rt];
sum[rt << 1] += 1LL * (pre[mid] - pre[l - 1]) * lazy[rt];
sum[rt << 1 | 1] += 1LL * (pre[r] - pre[mid]) * lazy[rt];
lazy[rt] = 0;
}
}

void update(int rt, int l, int r, int L, int R) {
if(L <= l && r <= R) {
lazy[rt]++; cnt[rt]++;
sum[rt] += (pre[r] - pre[l - 1]);
return;
}
pushdown(rt, l, r);
int mid = l + r >> 1;
if(L <= mid)
update(rt << 1, l, mid, L, R);
if(R > mid)
update(rt << 1 | 1, mid + 1, r, L, R);
pushup(rt);
}

int query(int rt, int l, int r, long long k) {
if(l == r) {
return num[l] + (k - 1) / cnt[rt];
}
pushdown(rt, l, r);
int mid = l + r >> 1;
if(sum[rt << 1] < k) return query(rt << 1 | 1, mid + 1, r, k - sum[rt << 1]);
return query(rt << 1, l, mid, k);
}

int main() {
cin >> n;
cin >> x[1] >> x[2] >> A[1] >> B[1] >> C[1] >> M[1];
cin >> y[1] >> y[2] >> A[2] >> B[2] >> C[2] >> M[2];
for(int i = 1; i <= n; ++i) {
if(i >= 3) {
x[i] = (1LL * A[1] * x[i - 1] + 1LL * B[1] * x[i - 2] + C[1]) % M[1];
y[i] = (1LL * A[2] * y[i - 1] + 1LL * B[2] * y[i - 2] + C[2]) % M[2];
}
L[i] = min(x[i], y[i]) + 1;
R[i] = max(x[i], y[i]) + 2;
num[i * 2 - 1] = L[i];
num[i * 2] = R[i];
}
sort(num + 1, num + 2 * n + 1);
tot = unique(num + 1, num + 2 * n + 1) - num - 1;
num[tot + 1] = num[tot];
for(int i = 1; i <= tot; ++i) {
pre[i] = pre[i - 1] + num[i + 1] - num[i];
}
for(int i = 1; i <= n; ++i) {
L[i] = lower_bound(num + 1, num + tot + 1, L[i]) - num;
R[i] = lower_bound(num + 1, num + tot + 1, R[i]) - num - 1;
now += pre[R[i]] - pre[L[i] - 1];
update(1, 1, tot, L[i], R[i]);
printf("%d\n", query(1, 1, tot, (now + 1) / 2));
}
return 0;
}

H. Pair

solved at 04:18(+1)

给你三个数\(A, B, C(1<=A,B,C<=1e9)\), 求满足\(1<=i<=A\ \&\&\ 1<=j<=B\ \&\& (i\ and\ j>C\ ||\ i\ xor\ j<C)\)\((i,j)\)对数量

正着不太好求,我们反过来求\(i\ and\ j<=C \ \&\&\ i\ xor\ j >=C\)的数量

考虑二进制数位\(dp\),从高位往低位考虑,一共有五维

第一维表示已经填了几位,第二维表示当前已经填的位的\(and\)是比\(C\)大还是一样大,第三位表示已经填的位的\(xor\)是比\(C\)小还是一样大,第四五维表示已经填的数和\(A,B\)的关系(是一样大还是已经更小了)后四维都是为\(0\)表示相等

转移的时候枚举当前位这两个数分别填\(0\)还是\(1\),不合法的情况就不转移

注意这样最后可能填的数是\(0\),而题目要求大于等于\(1\),考虑一下就好了

复杂度\(O(T2^6\log {1e9})\)

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

long long dp[31][2][2][2][2], ans;
int n, A, B, C, T, a[33], b[33], c[33];

int get_bit(int x, int aa[]) {
for(int i = 0; i <= 29; ++i)
aa[i] = (x >> i) & 1;
}

int main() {
cin >> T;
while(T--) {
cin >> A >> B >> C;
ans = 0;
memset(dp, 0, sizeof(dp));
get_bit(A, a);
get_bit(B, b);
get_bit(C, c);
dp[30][0][0][0][0] = 1;
for(int i = 30; i; --i) {
for(int _a = 0; _a < 2; ++_a) {
for(int _b = 0; _b < 2; ++_b) {
for(int AandB = 0; AandB < 2; ++AandB) {
for(int AxorB = 0; AxorB < 2; ++AxorB) {
for(int AtmpA = 0; AtmpA < 2; ++AtmpA) {
for(int BtmpB = 0; BtmpB < 2; ++BtmpB) {
if(AtmpA == 0 && _a > a[i - 1]) continue;
if(BtmpB == 0 && _b > b[i - 1]) continue;
if(AandB == 0 && (_a & _b) > c[i - 1]) continue;
if(AxorB == 0 && (_a ^ _b) < c[i - 1]) continue;
int newAandB = AandB | ((_a & _b) < c[i - 1]);
int newAxorB = AxorB | ((_a ^ _b) > c[i - 1]);
int newAtmpA = AtmpA | (_a < a[i - 1]);
int newBtmpB = BtmpB | (_b < b[i - 1]);
dp[i - 1][newAandB][newAxorB][newAtmpA][newBtmpB] += dp[i][AandB][AxorB][AtmpA][BtmpB];
}
}
}
}
}
}
}
ans = 1LL * A * B;
for(int a = 0; a < 2; ++a)
for(int b = 0; b < 2; ++b)
for(int c = 0; c < 2; ++c)
for(int d = 0; d < 2; ++d)
ans -= dp[0][a][b][c][d];
ans += max(0, A - C + 1);
ans += max(0, B - C + 1);
cout << ans << endl;
}
return 0;
}

J. A+B problem

solve at 00:07

定义\(f(x)\)为把\(x\)各位数倒过来的数,给你\(A, B\), 求\(f(f(A) + f(B))\)

牛客能交python好评