A.Total Eclipse
题目描述
给
n
n
n个点,
m
m
m条边的无向图,每一个点上有一个权值。接下来你每次要选择最大
k
k
k个联通的点,使他们的权值
−
1
-1
−1,问最少需要几次能让所有点变为
0
0
0
思路
对于每一次减小肯定是从小的值减小到大的。其实将整个过程反过来看,先把大的值减小到和他相连的值一样大,此时的结果肯定是大的减去小的差值,这个过程可以就可以看成是把这两个点并成一个点,可以用并查集来维护。而那些最小值就一定是起点,加上这些本身就是自己集合的点的值。
具体操作就是记录每一条边的两个端点的值,然后从根据端点较小的值从小到大排序一遍,从后往前进行操作,合并集合即可。最后跑一遍循环,将本身就是自己的集合的点的值全部加上。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
struct node {
int u, v, val1, val2;
friend bool operator < (node a, node b) {
if(a.val1 == b.val1) {
return a.val2 < b.val2;
}
return a.val1 < b.val1;
}
}e[N << 1];
int fa[N], val[N];
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void solve() {
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) {
scanf("%d", &val[i]);
fa[i] = i;
}
for(int i = 1; i <= m; i++) {
int u, v;
scanf("%d%d", &u, &v);
if(val[u] > val[v]) {
swap(u, v);
}
e[i] = {u, v, val[u], val[v]};
}
sort(e + 1, e + 1 + m);
LL res = 0;
for(int i = m; i; i--) {
int x = e[i].u, y = e[i].v;
int fx = find(x), fy = find(y);
if(val[fx] > val[fy]) {
res += (LL)val[fx] - val[fy];
fa[fx] = fy;
}
else {
res += (LL)val[fy] - val[fx];
fa[fy] = fx;
}
}
for(int i = 1; i <= n; i++) {
if(fa[i] == i) res += (LL)val[i];
}
printf("%lld\n", res);
}
int main() {
// freopen("in.txt", "r", stdin);
int t; cin >> t; while(t--)
solve();
return 0;
}
F.The Oculus
题目描述
给定长度为lena,lenb,lenc的
0
,
1
0,1
0,1数组,每一位表示对应的斐波那契数
F
1
=
1
,
F
2
=
2
,
F
3
=
3
,
F
4
=
5...
F_1=1,F_2=2,F_3=3,F_4=5...
F1=1,F2=2,F3=3,F4=5...,1表示加上所对应的这位数,0不加上。第一行总和代表数字
A
A
A,第二行总和代表数字
B
B
B,第三行总和代表数字
C
C
C。满足
A
∗
B
=
C
A*B=C
A∗B=C。但C中有一位
1
1
1错写成了
0
0
0,需要找出是哪一位的
0
0
0错写成
1
1
1。也就是求第
k
k
k位斐波那契数列满足
A
∗
B
=
C
∗
F
k
A*B=C*F_k
A∗B=C∗Fk。
思路
A
∗
B
m
o
d
P
A*B mod P
A∗BmodP =
C
∗
F
k
m
o
d
P
C*F_kmodP
C∗FkmodP 等价于
F
k
m
o
d
P
=
(
A
∗
B
−
C
)
m
o
d
P
F_kmodP=(A*B-C)modP
FkmodP=(A∗B−C)modP
数据范围并不大,所以按照暴力跑完全是可以模拟跑完的,唯一就是需要对每一位斐波那契数取模,保证所有数对应的模数都是唯一的,根据官方题解把模数当成
2
64
2^{64}
264完全就可以了,注意一下取反即可。看见有的队伍取双模数也是过了。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e6 + 10;
LL f[N];
void init() {
f[1] = 1, f[2] = 2;
for(int i = 3; i < N; i++) {
f[i] = f[i - 1] + f[i - 2];
}
}
void solve() {
int lena, lenb, lenc;
LL a = 0, b = 0, c = 0;
scanf("%d", &lena);
for(int i = 1; i <= lena; i++) {
int x; scanf("%d", &x);
if(x == 1) a += f[i];
}
scanf("%d", &lenb);
for(int i = 1; i <= lenb; i++) {
int x; scanf("%d", &x);
if(x == 1) b += f[i];
}
scanf("%d", &lenc);
for(int i = 1; i <= lenc; i++) {
int x; scanf("%d", &x);
if(x == 1) c += f[i];
}
LL num = a * b - c;
for(int i = 1; i <= lena + lenb + 1; i++) {
if(f[i] == num || f[i] == ~num) {
printf("%d\n", i);
return;
}
}
}
int main() {
init();
// freopen("in.txt", "r", stdin);
int t; cin >> t; while(t--)
solve();
return 0;
}
G.In Search of Gold
题目描述
给一棵树,求每条边有两种权值
a
,
b
a,b
a,b,有
k
k
k条边权值为
a
a
a,
n
−
k
−
1
n-k-1
n−k−1条边权值为
b
b
b,求树的直径的最小值。
思路
学了这位巨巨的题解
谈一下自己的理解。
求一个值最大值的最小值,二分答案判断是否可行。正常求树的直径以
f
[
i
]
f[i]
f[i]表示以
i
i
i为根节点的其子树中的最长链,这里设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示以
i
i
i为根节点,其子树中有
j
j
j个值选择
a
a
a的最大值。在每次更新的过程中,需要判断当前的直径是否超过给定的
m
i
d
mid
mid,若超过了就没有必要更新。
代码
代码变量解释:
在每个结点的层,开一个临时数组
d
i
s
[
25
]
dis[25]
dis[25]表示在结点
u
u
u时,选择
i
i
i条边为
a
a
a的最大值,最后对该层的每一种选择进行更新。
s
i
z
[
i
]
siz[i]
siz[i]表示根结点为
i
i
i的点最多能选择
a
a
a的边数。
n
o
w
now
now表示根结点
u
u
u最多能选择的
a
a
a的数量,所以在每一层关于
n
o
w
now
now值的更新就是
now = min(siz[u] + siz[v] + 1, K);
最后把该层的 n o w now now值赋给对应层数的 s i z [ u ] siz[u] siz[u]。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e4 + 10, M = 25;
struct edge {
int to, next;
LL a, b;
}e[N << 1];
int head[N], idx;
LL f[N][M];
int siz[N];
int n, K;
void add(int u, int v, LL a, LL b) {
e[++idx] = {v, head[u], a, b};
head[u] = idx;
}
void init() {
for(int i = 1; i <= n; i++) {
head[i] = 0;
}
idx = 0;
}
void dfs(int u, int fa, LL x) {
siz[u] = 0;
for(int i = 0; i <= K; i++) f[u][i] = 0;
for(int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if(v == fa) continue;
dfs(v, u, x);
int now = min(K, siz[u] + siz[v] + 1);
LL dis[M];
for(int j = 0; j <= now; j++) dis[j] = x + 10;
for(int j = 0; j <= siz[u]; j++) {
for(int k = 0; k <= siz[v] && k + j <= K; k++) {
if(f[u][j] + f[v][k] + e[i].a <= x) {
dis[j + k + 1] = min(dis[j + k + 1], max(f[u][j], f[v][k] + e[i].a));
}
if(f[u][j] + f[v][k] + e[i].b <= x) {
dis[j + k] = min(dis[j + k], max(f[u][j], f[v][k] + e[i].b));
}
}
}
for(int j = 0; j <= now; j++) f[u][j] = dis[j];
siz[u] = now;
}
}
void solve() {
scanf("%d%d", &n, &K);
init();
LL l = 1, r = 0;
for(int i = 1; i < n; i++) {
int u, v;
LL a, b;
scanf("%d%d%lld%lld", &u, &v, &a, &b);
add(u, v, a, b);
add(v, u, a, b);
r += max(a, b);
}
LL res = r;
while(l <= r) {
LL mid = l + r >> 1;
dfs(1, 0, mid);
if(f[1][K] <= mid) {
res = mid;
r = mid - 1;
} else l = mid + 1;
}
printf("%lld\n", res);
}
int main() {
// freopen("in.txt", "r", stdin);
int t; cin >> t; while(t--)
solve();
return 0;
}
J.Lead of Wisdom
题目描述
有
n
n
n件物品,
k
k
k种类型,有的类型的物品可能没有。每件物品四个值
a
,
b
,
c
,
d
a,b,c,d
a,b,c,d,求选择
k
k
k类每类物品只能选择一个,求
D
M
G
=
(
100
+
∑
i
∈
S
a
i
)
(
100
+
∑
i
∈
S
b
i
)
(
100
+
∑
i
∈
S
c
i
)
(
100
+
∑
i
∈
S
d
i
)
DMG=(100+∑_{i∈S}ai)(100+∑_{i∈S}bi)(100+∑_{i∈S}ci)(100+∑_{i∈S}di)
DMG=(100+∑i∈Sai)(100+∑i∈Sbi)(100+∑i∈Sci)(100+∑i∈Sdi)的最大值
思路
爆搜,把中间为0的点扔掉。个人认为是这样的,对于有的空结点,假如说有
50
50
50件物品,共有
50
50
50种类型,但是只有前
15
15
15类型的物品才存在,假设
1
−
5
1-5
1−5类物品有
4
4
4件,
6
−
15
6-15
6−15类物品有3件,那么最后的35类物品就相当于0,如果全部跑一遍的时间复杂度就变成了
O
(
4
5
∗
3
10
∗
35
)
O(4^5*3^{10}*35)
O(45∗310∗35)就存在TLE的情况。有的倒着搜跑过去了感觉是数据没出全吧emmm
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 55;
int n, k;
struct node {
LL a, b, c, d;
};
vector<int> num;
vector<node> cnt[N];
LL res;
void dfs(int x, LL a, LL b, LL c, LL d) {
if(x == num.size()) {
res = max(res, a * b * c * d);
return;
}
int y = num[x];
for(int i = 0; i < cnt[y].size(); i++) {
dfs(x + 1, a + cnt[y][i].a, b + cnt[y][i].b, c + cnt[y][i].c, d + cnt[y][i].d);
}
}
void solve() {
scanf("%d%d", &n, &k);
num.clear();
res = 0;
for(int i = 1; i <= k; i++) {
cnt[i].clear();
}
for(int i = 1; i <= n; i++) {
int op, a, b, c, d;
scanf("%d%d%d%d%d", &op, &a, &b, &c, &d);
cnt[op].push_back({a, b, c, d});
}
for(int i = 1; i <= k; i++) {
if(cnt[i].size() != 0) num.push_back(i);
}
dfs(0, 100, 100, 100, 100);
printf("%lld\n", res);
}
int main() {
// freopen("out.txt", "w", stdout);
// freopen("in.txt", "r", stdin);
int t; cin >> t; while(t--)
solve();
return 0;
}
L.String Distance
题目描述
给定两个字符串
S
1
,
S
2
S_1,S_2
S1,S2,
q
q
q次询问,每次给一个
l
,
r
l,r
l,r表示取
S
1
S_1
S1串的第
l
l
l位到第
r
r
r位。对于字符串
S
S
S可以删除一个字符或插入一个字符,问最少操作几次可以使选择后的串
S
1
S_1
S1与
S
2
S_2
S2相等。
思路
删除一个字符或者插入一个字符其实等价,可以看成删除
S
1
S_1
S1和
S
2
S_2
S2的最少字符使两个字符串相等。其实就是求两个字符串的最长公共子序列,答案就是
l
e
n
S
1
len_{S_1}
lenS1+
l
e
n
S
2
−
2
∗
L
C
S
len_{S_2}-2*LCS
lenS2−2∗LCS。但是对于每次询问再算一遍直接T。所以需要DP预处理。
d
p
[
i
]
[
j
]
[
k
]
dp[i][j][k]
dp[i][j][k]表示字符串
S
1
S_1
S1匹配到第
i
i
i位,
S
2
S_2
S2字符串匹配到第
j
j
j位,此时最长公共子序列长度为
k
k
k时,对于这个匹配在
S
1
S_1
S1中出现的最晚位置。
如果
S
1
[
i
]
!
=
S
2
[
j
]
S_1[i]!=S_2[j]
S1[i]!=S2[j],
d
p
[
i
]
[
j
]
[
k
]
=
m
a
x
(
d
p
[
i
−
1
]
[
j
]
[
k
]
,
d
p
[
i
]
[
j
−
1
]
[
k
]
)
dp[i][j][k]=max(dp[i-1][j][k],dp[i][j-1][k])
dp[i][j][k]=max(dp[i−1][j][k],dp[i][j−1][k])。
如果
S
1
[
i
]
=
=
S
2
[
j
]
S_1[i]==S_2[j]
S1[i]==S2[j],当
k
=
=
1
k==1
k==1时
d
p
[
i
]
[
j
]
[
k
]
=
i
dp[i][j][k]=i
dp[i][j][k]=i,
k
!
=
1
k!=1
k!=1时,
d
p
[
i
]
[
j
]
[
k
]
=
m
a
x
(
d
p
[
i
]
[
j
]
[
k
]
,
d
p
[
i
−
1
]
[
j
−
1
]
[
k
−
1
]
)
;
dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-1][k-1]);
dp[i][j][k]=max(dp[i][j][k],dp[i−1][j−1][k−1]);
最后对于每个询问,判断是否满足
d
p
[
r
]
[
j
]
[
k
]
>
=
l
dp[r][j][k]>=l
dp[r][j][k]>=l的条件即可。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
char s1[N], s2[N];
int dp[N][22][22];
void solve() {
scanf("%s%s", s1 + 1, s2 + 1);
int n = strlen(s1 + 1), m = strlen(s2 + 1);
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
for(int k = 1; k <= j; k++) {
dp[i][j][k] = max(dp[i - 1][j][k], dp[i][j - 1][k]);
if(s1[i] == s2[j]) {
if(k == 1) dp[i][j][k] = i;
else dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j - 1][k - 1]);
}
}
}
}
int q; scanf("%d", &q);
while(q--) {
int l, r;
scanf("%d%d", &l, &r);
int num = 0;
for(int j = 1; j <= m; j++) {
for(int k = 1; k <= j; k++) {
if(dp[r][j][k] >= l) {
num = max(num, k);
}
}
}
printf("%d\n", r - l + 1 + m - 2 * num);
}
}
int main() {
// freopen("in.txt", "r", stdin);
int t; cin >> t; while(t--)
solve();
return 0;
}