link 期末结束了,开始训练。不知道是太久没做题了还是太弱了,感觉连ABC都写了挺久的,所以就都写一份题解吧。
A
题意
给出两个字符串以及一个初始为空的答案串,每次可以从两个字符串中任选一个字符移动到答案串的末尾,直到某一串为空,问答案串的最小字典序。其中,同一个串不能连续取超过
k
k
k 次。(保证两个串没有相同字母)
思路
因为和顺序无关,所以首先将2个串内部分别按字典序从小到大排序,然后贪心地取小的即可,并维护取到哪个串以及连续取了该串的多少个,等于
k
k
k 时就强制换串。因为保证了没有相同字母,所以不需要考虑相同的情况。
代码
int n, m, k;
void solve() {
cin >> n >> m >> k;
string s1, s2, s3;
cin >> s1 >> s2;
sort(s1.begin(), s1.end());
sort(s2.begin(), s2.end());
s3.clear();
int cnt = 0;
int last =-1;
while(s1.size() && s2.size()) {
if(cnt < k) {
cnt++;
if(*s1.begin() < *s2.begin()) {
s3.pb(*s1.begin());
s1.erase(s1.begin());
if(last != 1) {
last = 1;
cnt = 1;
}
}
else {
s3.pb(*s2.begin());
s2.erase(s2.begin());
if(last != 2) {
last = 2;
cnt = 1;
}
}
}
else {
if(last == 1) {
last = 2;
cnt = 1;
s3.pb(*s2.begin());
s2.erase(s2.begin());
}
else {
last = cnt = 1;
s3.pb(*s1.begin());
s1.erase(s1.begin());
}
}
}
cout << s3 << endl;
}
B
题意
给出一个全排列,求在满足错排条件下的最小字典序排列。
思路
可以直接贪心,用一个
s
e
t
set
set 维护当前还未被取的数,能取最小的就取最小的,不能就取集合中第二小的,若最后一个数不满足错排只需要交换最后两个数即可。
代码
int a[maxn], n;
void solve() {
cin >> n;
set<int> s;
for(int i = 1; i <= n; i++) {
cin >> a[i];
s.insert(i);
}
if(n == 1) {
cout << -1 << endl;
return ;
}
vector<int> ans;
ans.pb(0);
for(int i = 1;i <= n; i++) {
if(a[i] != *s.begin() || i == n) {
ans.pb(*s.begin());
s.erase(s.begin());
}
else {
ans.pb(*s.begin()+1);
s.erase(*(s.begin())+1);
}
}
if(ans[n] == a[n]) {
swap(ans[n], ans[n-1]);
}
for(int i = 1; i <= n; i++) {
cout << ans[i] <<' ';
}
cout << endl;
}
C
题意
给出一棵根为1的二叉树,有一个病毒从根开始传播,每轮会传播到其中一个儿子,同时你每轮可以选择一个节点砍掉,如果这样做则以该结点为根的子树都不会被感染。问最多能有多少个节点既没有被感染也没有被砍掉?
思路
这道题用两个标记来标记有几个儿子还算挺妙的,学到了 树形dp,用 cnt[] 和 dp[] 数组分别维护以
i
i
i 为根的子数的节点数量以及最多有多少个节点满足条件。 因为是二叉树,所以如果一个节点只有一个儿子,那么我们可以直接把它的儿子砍掉,dp[i] = cnt[son] - 1 。两个儿子的话 dp[i] = max(dp[son1] + cnt[son2], dp[son2] + cnt[son1]) - 1 。
D
1900
题意
给你一个由黑白两种颜色构成的
n
×
m
(
n
,
m
≤
1000
)
n\times m(n,m\leq 1000)
n×m(n,m≤1000) 的矩阵,请你找到某个坐标
(
i
,
j
)
(i,j)
(i,j) ,使得离这个坐标曼哈顿距离最大的黑格子距离最小。
思路
想了一会才发现,无论图上有多少个黑格子,与任意一个点曼哈顿距离最大的一定是“左上,左下,右上,右下”(最多)四个顶点之一。另一方面,如果我们知道了这四个点坐标,那么对任意点都可以
O
(
1
)
O(1)
O(1) 得出答案。所以考虑如何求这四个点。
- 右下和左上:显然分别是
max
?
(
i
+
j
)
和
min
?
(
i
+
j
)
\max(i+j) 和\min(i+j)
max(i+j)和min(i+j)。
- 右上和左下:画个图发现右上是
max
?
(
j
?
i
)
\max(j-i)
max(j?i) ,左下是
max
?
(
i
?
j
)
\max(i-j)
max(i?j)。
当然最后求出来的“右上”不一定对每个点都处在右上方,但并不影响答案,因为此时“右上”一定不会构成最大值。
代码
int n, m;
char a[1010][1010];
pii ru, rd, lu, ld;
void judge(int i, int j) {
if(i + j < lu.x + lu.y) {
lu.x = i, lu.y = j;
}
if(i + j > rd.x + rd.y) {
rd.x = i, rd.y = j;
}
if(j - i > ru.y - ru.x) {
ru.x = i, ru.y = j;
}
if(i - j > ld.x - ld.y) {
ld.x = i, ld.y = j;
}
}
int cal(int i, int j) {
int ans = abs(rd.x-i)+abs(rd.y-j);
chmax(ans, abs(lu.x-i)+abs(lu.y-j));
chmax(ans, abs(ru.x-i)+abs(ru.y-j));
chmax(ans, abs(ld.x-i)+abs(ld.y-j));
return ans;
}
void solve() {
cin >> n >> m;
rd = mp(0, 0);
lu = mp(INF, INF);
ru = mp(INF, 0);
ld = mp(0, INF);
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
cin >> a[i][j];
if(a[i][j] == 'B') {
judge(i, j);
}
}
}
int ans = INF;
int xx = 0, yy = 0;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
if(cal(i, j) < ans) {
ans = cal(i, j);
xx = i, yy = j;
}
}
}
cout << xx << ' ' << yy << endl;
}
E
2500
题意
给出一个序列
n
(
n
≤
2000
)
n(n\leq 2000)
n(n≤2000),我们定义
i
i
i 和
j
j
j 之间存在一条边当且仅当
a
i
&
a
j
≠
0
a_i \&a_j \neq 0
ai?&aj??=0, 可以进行以下两种操作任意次:
- 选一个
i
i
i ,使
a
i
+
1
a_i+1
ai?+1 。
- 选一个
i
i
i ,使
a
i
?
1
a_i-1
ai??1 ,其中
a
i
>
0
a_i > 0
ai?>0。
问至少多少操作次使得序列构成的图是联通的?
思路
首先如果这个序列中有0,我们必须把它们先变成1。 此时的序列有如下结论:至多操作2次满足联通。 方法: 设
l
b
(
x
)
lb(x)
lb(x) 为
x
x
x 的 lowbit 值,
m
l
b
mlb
mlb 为
max
?
(
l
b
(
x
)
)
,
x
∈
序
列
\max(lb(x)),x\in 序列
max(lb(x)),x∈序列 ,这里假设
x
x
x 的 lowbit 是最大的,那么我们只需要将
x
x
x 减1,则所有 lowbit 小于
l
b
(
x
)
lb(x)
lb(x) 的数都满足和
x
x
x 连通,剩下的可能不与
x
x
x 连通的数 lowbit 也一定等于
l
b
(
x
)
lb(x)
lb(x), 然后我们只需要找其中一个 +1 就可以了。
上面这两种都是先考虑减,但还有一种特殊情况,比如
1010
,
0110
,
0001
1010,0110,0001
1010,0110,0001,就需要选一个数加1,最少一次就可以得到。
总之具体做的时候枚举每个数++,- -,++,时间复杂度
O
(
30
n
2
)
O(30n^2)
O(30n2)。
代码
int n;
int a[maxn];
int fa[maxn], sz[maxn];
int find(int x) {
if(fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
void merge(int x, int y) {
int fx = find(x);
int fy = find(y);
int tmp = sz[fx] + sz[fy];
if(fx != fy) {
fa[fy] = fx;
sz[fx] = tmp;
}
}
int lb(int x) {
return x & (-x);
}
void init() {
for(int i = 0; i <= 30; i++) fa[i] = i, sz[i] = 1;
}
bool check() {
init();
int k = 0;
for(int i = 1; i <= n; i++) {
k |= a[i];
}
for(int i = 1; i <= n; i++) {
int la = -1;
for(int j = 0; j <= 30; j++) {
if((a[i] >> j) & 1) {
if(la != -1) merge(la, j);
la = j;
}
}
}
set<int> s;
for(int i = 0; i <= 30; i++) {
if((k >> i) & 1) s.insert(find(i));
}
return s.size() == 1;
}
int ans = 0;
void print() {
cout << ans << endl;
for(int i = 1; i <= n; i++) {
cout << a[i] << ' ';
}
cout << endl;
return;
}
void solve() {
cin >> n;
ans = 0;
for(int i = 1; i <= n;i++) {
cin >> a[i];
if(!a[i]) a[i]++, ans++;
}
if(check()) {
print();
return;
}
for(int i = 1; i <= n; i++) {
a[i]++;
ans++;
if(check()) {
print();
return;
}
ans--;
a[i]--;
}
int id = 0, mlb = 0;
for(int i = 1; i <= n; i++) {
if(lb(a[i]) > mlb) {
id = i;
mlb = lb(a[i]);
}
}
vector<int> v;
for(int i = 1; i <= n; i++) {
if(lb(a[i]) == mlb) {
a[i]--;
ans++;
if(check()) {
print();
return;
}
ans--;
a[i]++;
}
}
a[id]--;
ans++;
if(check()) {
print();
return;
}
for(int i = 1; i <= n; i++) {
if(i == id) continue;
a[i]++;
ans++;
if(check()) {
print();
return;
}
ans--;
a[i]--;
}
}
|