【LGR-096】洛谷 11 月月赛 I Div.2 题解
前言
想了想写四篇好麻烦(其实是看不下去水博客太猛了)。
然后就整合起来了。
T1:Addition
题目大意
给你一个序列,你每次可以选相邻的两个数,将其合并乘一个。
合并的形式是前面的加或者减后面那个。
然后要你最大化最后剩下的数。
思路
发现第一个数一定是正的贡献,后面的你随便正负,都可以有一种何方方式满足。
然后就直接第一个加上后面的绝对值就是答案。
代码
#include<cstdio>
#define ll long long
using namespace std;
int n, x;
ll ans;
int Abs(int x) {
return x < 0 ? -x : x;
}
int main() {
scanf("%d", &n);
scanf("%d", &x); ans = x;
for (int i = 2; i <= n; i++) {
scanf("%d", &x);
ans += 1ll * Abs(x);
}
printf("%lld", ans);
return 0;
}
T2:Lines
题目大意
给你一些直线,保证不会有重合的直线或者有三条直线有同一个交点。
然后问你至少要选多少条直线,才能使得所有的线交点都在你选的直线上。
思路
考虑平行的边是不会有交点的。
然后你想想那因为不会有是哪个直线有同一个交点,那我们可以把不同斜率的相交点当做是这两个斜率的直线至少要选一个。
那转化一下就是你顶多可以不选一种斜率的直线。(因为任意两个斜率不同的直线都会有交点)
那你可以统计出每个斜率的直线个数,然后选个数最多的那个不要选即可。
代码
#include<map>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int n, ans, maxn;
int a[100001], b[100001], c[100001];
map <pair<int, int>, int> q;
int gcd(int x, int y) {
if (!y) return x;
return gcd(y, x % y);
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d %d %d", &a[i], &b[i], &c[i]);
if (!a[i]) b[i] = 1;
if (!b[i]) a[i] = 1;
if (a[i] && b[i]) {
int g = gcd(a[i], b[i]);
a[i] /= g; b[i] /= g;
}
q[make_pair(a[i], b[i])]++;
}
for (map <pair<int, int>, int> :: iterator it = q.begin(); it != q.end(); it++) {
ans += (*it).second;
maxn = max(maxn, (*it).second);
}
printf("%d", ans - maxn);
return 0;
}
T3:ABC
题目大意
给你一个字符串,只由 ABC 三个字符组成。
然后你每次操作可以选择一个区间,把区间的 ABC 替换成你指定的字符。
然后问你最少要多少次操作使得每个相邻的位置字符相同。
思路
你考虑到对于一个区间里面,它相邻的原来的状态和现在的是不会变的。
会变的就是区间的两段。
不难想到每次可以两端的不合法,然后如果只有一个不合法的地方,就一个端点是它,一个端点是最右就可以了。
然后至于怎么换其实就随便换,只要肯定每个都跟之前不一样就可以了,这样你就保证了两边会从一样变成不一样。
然后就好了。
代码
#include<cstdio>
using namespace std;
struct node {
int l, r;
char c[4];
}q[5001];
int n, m, l, r;
char s[5001];
bool gets() {
l = -1; r = -1;
for (int i = 2; i <= n; i++) {
if (s[i] == s[i - 1]) {
if (l == -1) l = i;
else {
r = i - 1;
break;
}
}
}
if (l == -1) return 0;
if (r == -1) r = n;
return 1;
}
int main() {
scanf("%d", &n);
scanf("%s", s + 1);
while (1) {
if (!gets()) break;
m++;
q[m].l = l;
q[m].r = r;
q[m].c[0] = 'B';
q[m].c[1] = 'C';
q[m].c[2] = 'A';
for (int i = l; i <= r; i++) {
s[i] = q[m].c[s[i] - 'A'];
}
}
printf("%d\n", m);
for (int i = 1; i <= m; i++) {
printf("%d %d ", q[i].l, q[i].r);
for (int j = 0; j <= 2; j++) putchar(q[i].c[j]);
printf("\n");
}
return 0;
}
T4:Permutation
题目大意
给你一棵树,然后要你找一个字典序最大的数组,使得它按下面要求生成出的数是给出数的同构树。
每次找前面中最后一个大于它的数,然后两个下标对于的点连边,如果没有就不连边。
思路
首先我们考虑贪心,假设已经确定(枚举)了根,要怎么搞。
那就每次找那种最小的树走递归下去。
但其实这样是有问题的,因为相同大小的数形态不同优劣也不一样。
那我们考虑怎么解决,发现可以通过记录它儿子的排名,来进行排序。
那具体来讲,我们要从最下面的一个点开始,一点一点合并上来。
具体就是枚举每一种子树,然后先按大小排,然后每次把相同大小的排序,然后编号(注意相同形态编号相同),然后只有的排序就会用到这次的编号。
然后不难看出一开始的两个肯定是 \(1\ n\),然后接着就会从大小为 \(n-1\) 中编号最小的子树开始搞。
然后就递归下去,按儿子的优先顺序搞即可。
然后注意特判一下大小是 \(1\) 的就好了。
代码
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
struct node {
int fr, to, nxt;
}e[10005];
int n, x, y, le[10005], KK;
int sz[10005], fa[10005], pm;
int f[10005], pl[10005];
struct nde {
int id, sz;
vector <int> p;
}a[10005];
void add(int x, int y) {
e[++KK] = (node){x, y, le[x]}; le[x] = KK;
e[++KK] = (node){y, x, le[y]}; le[y] = KK;
}
void dfs(int now, int father) {
sz[now] = 1; fa[now] = father;
for (int i = le[now]; i; i = e[i].nxt)
if (e[i].to != father) {
dfs(e[i].to, now);
sz[now] += sz[e[i].to];
}
}
bool cmp0(nde x, nde y) {
return x.sz < y.sz;
}
bool cmp1(int x, int y) {
return f[x] < f[y];
}
bool cmp2(nde x, nde y) {
int d = min(x.p.size(), y.p.size());
for (int i = 0; i < d; i++)
if (f[x.p[i]] != f[y.p[i]]) return f[x.p[i]] < f[y.p[i]];
return 0;
}
void write(int now, int k) {
for (int i = 0; i < a[now].p.size(); i++) {
printf(" %d", k - a[pl[a[now].p[i]]].sz + 1);
write(pl[a[now].p[i]], k);
k -= a[pl[a[now].p[i]]].sz;
}
}
int main() {
scanf("%d", &n);
if (n == 1) {//特判
printf("1"); return 0;
}
for (int i = 1; i < n; i++) {
scanf("%d %d", &x, &y);
add(x, y);
}
dfs(1, 0);
for (int i = 1; i <= KK; i++) {
int fr = e[i].fr, to = e[i].to;
if (fa[fr] == to) a[i].sz = n - sz[fr];
else a[i].sz = sz[to];
a[i].id = i;
for (int j = le[to]; j; j = e[j].nxt)
if (e[j].to != fr) {
a[i].p.push_back(j);
}
}
sort(a + 1, a + KK + 1, cmp0);//先按大小排
int l = 1, r = 0;
for (int S = 2; S < n; S++) {
while (l <= KK && a[l].sz < S) l++;//找到相同大小的
while (r < KK && a[r + 1].sz <= S) r++;
for (int i = l; i <= r; i++) sort(a[i].p.begin(), a[i].p.end(), cmp1);//先把儿子排好
sort(a + l, a + r + 1, cmp2);//按儿子的排名排
f[a[l].id] = ++pm;
if (S != n - 1) {
for (int i = l + 1; i <= r; i++) {
pm += cmp2(a[i - 1], a[i]);//注意完全相同的是不用加的
f[a[i].id] = pm;
}
}
}
for (int i = 1; i <= KK; i++)
pl[a[i].id] = i;
printf("1 %d", n);
write(l, n - 1);
return 0;
}