#qbxt国庆水题记#
#day3#
//30 + 5 + 30 = 65
//暴力 + 暴力+ 暴力
##tree##
1.1 题目
从前有一棵树,编号 1 到 n,确定一个根节点,最大化所有点深度之和
1.2 输入Ṭ
第一行 n 接下来 n - 1 行表示树的每条边
1.3 输出Ṭ
一个整数,表示根节点编号。如果有多个节点满足条件,输出编号最小的
1.4 Sample Input
8
1 4
5 6
4 5
6 7
6 8
2 4
3 4
1.5 Sample Output
7
1.6 数据范围及约定
对于 30% 的数据满足:1 ≤n ≤1000
对于另外 20% 的数据满足:树是一条链
对于 100% 的数据满足,1 ≤n ≤106
读入量过大,最好加上读入优化
树形DP
用dis[]表示每个节点的子树和
用size[]表示每个节点有多少个子节点
爆搜出dis和size
**对于每个节点深度和为 vis[节点] = vis[父亲] + (n - size[节点])- size[节点]; **
代码(爆栈了不知到对不对~~不想写bfs)
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn = 1000000 + 100;
int n;
struct edge {
int u,v;
int next;
}e[maxn];
int head[maxn], tot = 0;
void add(int u, int v) {
e[++tot] = (edge){u,v,head[u]};
head[u] = tot;
}
int read() {
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9') {
if(ch == '-') f = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9') {
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
int size[maxn],dis[maxn];
void dfs1(int x, int fa) {
for(int i = head[x]; i; i = e[i].next) {
int v = e[i].v;
if(v != fa){
dfs1(v,x);
dis[x] += dis[v] + size[v];
size[x] += size[v];
}
}
size[x]++;
}
int vis[maxn];
void dfs2(int x, int fa) {
if(x == 1) vis[x] = dis[x];
else vis[x] = vis[fa] + n - 2 * size[x];
for(int i = head[x]; i; i = e[i].next) {
int v = e[i].v;
if(v != fa) {
dfs2(v,x);
}
}
}
int main() {
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
n = read();
for(int i = 1; i < n; i++) {
int u = read(), v = read();
add(u,v);
add(v,u);
}
dfs1(1,0);
dfs2(1,0);
int ans = 0,l = 0;
for(int i = 1; i <= n; i++) {
if(ans < vis[i]) {
l = i;
ans = vis[i];
}
}
cout<<l<<endl;
return 0;
}
代码
std用的拓扑(表示不好理解)
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
#define rep(i,l,r) for (int i=l; i<=r; i++)
#define drep(i,r,l) for (int i=r; i>=l; i--)
#define ll long long
#define pb push_bake
const int N = 1000008;
int n, tot, head[N], h[N], fa[N], sum[N];
bool vis[N];
ll Down[N], Up[N];
int read()
{
char c; int w = 1, num = 0;
for (c = getchar(); !isdigit(c) && c != '+' && c != '-'; c = getchar());
if (c == '-') c = getchar(), w = -1;
if (c == '+') c = getchar();
for (; isdigit(c); c = getchar()) num = num * 10 + c - '0';
return num * w;
}
struct Node
{
int next, node;
}e[2 * N];
inline void add(int x, int y)
{
e[++tot].next = head[x], head[x] = tot, e[tot].node = y;
e[++tot].next = head[y], head[y] = tot, e[tot].node = x;
}
void topsort()
{
int l = 1, r = 1;
h[1] = 1; vis[1] = 1;
while (l <= r)
{
int u = h[l++];
for (int i = head[u], v; v = e[i].node, i; i = e[i].next)
if (!vis[v])
{
vis[v] = 1;
fa[v] = u;
h[++r] = v;
}
}
}
int main()
{
freopen("tree.in", "r", stdin);
freopen("tree.out", "w", stdout);
n = read();
rep(i, 1, n - 1)
{
int u, v;
u = read(); v = read();
add(u, v);
}
topsort();
drep(k, n, 1)
{
int u = h[k];
sum[u] = 1;
for (int i = head[u], v; v = e[i].node, i; i = e[i].next)
if (v != fa[u])
{
sum[u] += sum[v];
Down[u] += Down[v] + sum[v];
}
}
rep(k, 2, n)
{
int u = h[k];
Up[u] += Up[fa[u]] + sum[1] - sum[fa[u]] + 1;
Up[u] += Down[fa[u]] - Down[u] - sum[u] + sum[fa[u]] - sum[u] - 1;
}
ll Max = 0; int ans;
rep(i, 1, n) if (Up[i] + Down[i] > Max) Max = Up[i] + Down[i], ans = i;
printf("%d\n", ans);
fclose(stdin); fclose(stdout);
return 0;
}
##safe##
2.1 题目
“秋名山上行人稀, 常有车神较高低。如今车道依旧在, 不见当年老司机”
从前有一位车神,要行驶秋名山上的一条公路。沿着公路一次站着 n 个观众,第 i 个观众
对他的喜欢程度为 a[i]。如果 a[i]<0,说明这个观众讨厌他,有可能在他经过时搞一些危险的
事情,危及他的安全。
于是,车神想选出两段路,这两段路对应的两段观众的喜欢值之和最大。这样的话,车神
在这两段路行驶就最为安全,可以适当降低警惕。
你的任务就是计算选出的这两段观众喜欢值之和的最大值。
2.2 输入Ṭ
第一行:N,表示有 N 个人站成一排观看比赛
第二行:有 N 个数字,表示每个人的喜欢值
2.3 输出Ṭ
输出选出的这两段观众喜欢值之和的最大值。注意一定要选出两段观众,每段观众至少一
人。
2.4 样ֻ输入
7
4 -5 3 -1 11 -2 -1
2.5 样ֻ输出
17
2.6 样例解释
第一段是第一个人,第二段是第三到第五个人
2.7 数据范围及㓖定
30% 的数据,保证 n<=100;
100% 的数据,保证 n<=65535,DI 值的绝对值小于 127;
答案在 C++ 的 long long 范围内```
//dp呵呵~~~~~~~~~~~~~~~~~~~~~~~不会
最长子段和的变形
我们用数组 f[i] 表示以 i 结尾的最大子段和,g[i] 表示以 i 开头的最大子段和。
我们可以O(n)dp 预处理出这两个数组。
std中用的是ST表(表示数据结构渣不会)
但有方法O(n)解决
f[i] 表示 i 以前的最大子段和,g[i] 表示 i 以后的最大子段和。
f[i] = max(f[i],f[i-1]),g[i] = max(g[i],g[i+1])
枚举i即可
代码
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define ll long long
const int maxn = 1000000 + 100;
ll n,a[maxn];
ll g[maxn],f[maxn];
ll read() {
ll x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9') {
if(ch == '-') f = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9') {
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
int main() {
freopen("safe.in","r",stdin);
freopen("safe.out","w",stdout);
n = read();
for(int i = 1; i <= n; i++) a[i] = read();
f[1] = a[1],g[n] = a[n];
for(int i = 2; i <= n; i++) f[i] = max(a[i],f[i-1] + a[i]);
for(int i = n - 1; i >= 1; i--) g[i] = max(a[i],g[i+1] + a[i]);
for(int i = 2; i <= n; i++) f[i] = max(f[i],f[i-1]);
for(int i = n - 1; i >= 1; i--) g[i] = max(g[i],g[i+1]);
ll ans = -99999999999L;
for(int i = 2; i <= n; i++) {
ans = max(ans, f[i-1] + g[i]);
}
cout<<ans<<endl;
return 0;
}
还有奇葩的做法
ans = r2 - l2 + r1 - l1
枚举i 求出最大的 -l2 再求 r2 - l2 …… 到 r2 - l2 + r1 - l1
代码
#include <algorithm>
#include <cstdio>
typedef long long LL;
const LL INF = 1000000000000000LL;
const int N = 70000;
using std::max;
int main() {
freopen("safe.in", "r", stdin);
freopen("safe.out", "w", stdout);
int n, x;
LL a = 0, b = -INF, c = -INF, d = -INF, S = 0;
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d", &x);
S += x;
d = max(d, c + S);
b = max(b, a + S);
c = max(c, b - S);
a = max(a, -S);
}
printf("%I64d\n", d);
return 0;
}
##shoot##
3.1 题目
从前有 n 个人,愉快地生活在一起。直到有一天他们学会了互黑。
每个人都有且仅有一个黑的目标 (这个目标有可能是他自己,这叫自黑)。对于那些没人黑
的人 (其他所有人黑的目标都不是他),他们想刷存在感,于是可以选择放弃黑自己的目标而选
择自黑 (也就是二选一)。
注意只有一开始没有作为任何人的目标的人才可以放弃目标选择自黑。
规定轮到某个没有被黑的人时,必须且仅能黑一次。所有被黑过的人都不能再黑别人了。
当然一个人可以被黑很多次。
由你来决定一个顺序,大家按照顺序行动。求问最后被黑的人数的最小值和最大值。
3.2 输入Ṭ
第一行一个整数 n
第二行 n 个整数,第 i 个整数 ai 表示第 i 个人的目标,满足 1 ≤ai ≤n
3.3 输出Ṭ
两个整数,依次表示最小值和最大值
3.4 样ֻ输入
8
2 3 2 2 6 7 8 5
3.5 样ֻ输出
3 6
3.6 样ֻ例解释
最小值:按照 1,4,2,3,5,6,7,8 的顺序行动。轮到 2,6,8 时它们已经被黑所以他们不能再
黑人。最后被黑的人数是 3
最大值:1 和 4 自黑,然后剩下的人按照 2,3,7,6,5 的顺序行动。这样一来 3,6,7,8 也
会被黑。于是一共有 6 个人被黑
3.7 数据范围及约定
• 对于前 30% 的数据,满足 1 ≤n ≤8
• 对于接下来 30% 的数据,满足只有前若干个人自黑,后面的第 i 个人只会黑 1. . . i −1 中
的某一个人
• 对于所有数据,满足 1 ≤n ≤106
//不会
//太难QAQ
题解
如果 u 要黑 v,我们从 u 到 v 之间连一条边,很容易发现我们得到了一个基环内向树森
林。对于每个联通块我们单独考虑。
分 30 ࡽ 3.1
直接爆搜
3.2 接下来 40 分
这一部分的数据,实际上就是只有单环。单环的节点一定会被黑,因此可以直接从图中删
去。于是我们得到了一个森林。
接下来的做法就是从正解的做法中忽略了环的处理。其实很接近正解了
3.3 所有数据
对于每棵环套树单独考虑
3.3.1 最大值
- 如果只有一个单环,那么显然被黑
- 如果只有一个大于 1 的环,那么可以只剩下一个人
- 又有环,环上的点又连了一棵树,那么我们可以先让所有度数为 0 的点自黑。可以证明
这样一来答案不会变差。如果此时只剩下一个环,同情况 1 或者 2。否则答案就是剩下的
点中,入度为 0 的点的个数
3.3.2 最小值
- 如果只有一个单环,那么显然被黑
- 如果只有一个大于 1 的环,那么可以隔着黑。
- 又有环,环上的点又连了一棵树。我们可以贪心,按照拓扑序依次从底往上黑 (如果可以
黑的话)。如果黑掉了环上某个点,那就可以断环为链了,否则剩下一个环,同情况 1 或
者 2。
时间复杂度和空间复杂度为 O(n)
std
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <map>
#include <queue>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define drep(i, r, l) for (int i = r; i >= l; i--)
typedef long long ll;
const int N = 1e6 + 8, INF = 1e7;
int n, head[N], tot, L, R, h[N], que[N], cir[N], die[N], f[N], g[N];
int ansmax, ansmin, fa[N], ind[N], cnt, cir_cnt, len, b[N], w[N], q[N], _ind[N];
bool vis[N], inq[N], incir[N], col[N], _vis[N], _die[N];
struct Edge
{
int next, node;
}e[N << 1];
void add(int x, int y)
{
e[++tot].next = head[x], head[x] = tot, e[tot].node = y;
e[++tot].next = head[y], head[y] = tot, e[tot].node = x;
}
int getint()
{
char c; int num = 0, w = 1;
for (c = getchar(); !isdigit(c) && c != '-'; c = getchar());
if (c == '-') c = getchar(), w = -1;
for (;isdigit(c); c = getchar()) num = num * 10 + c - '0';
return num * w;
}
void calc_min()
{
int l = 1, r = R;
while (l <= r)
{
int u = que[l++];
if (_die[u] || _die[fa[u]]) continue;
_die[fa[u]] = 1;
if (incir[fa[u]]) que[++r] = fa[fa[u]];
}
if (r + cir_cnt == cnt) ansmin += (cir_cnt + 1) >> 1;
rep(i, 1, cnt) ansmin += _die[h[i]];
}
void calc_max()
{
if (cir_cnt + len == cnt)
{
if (cir_cnt == 1) ansmax += cnt;
else ansmax += cnt - 1; return;
}
int x = 0;
rep(i, 1, len)
if (!incir[fa[que[i]]]) _ind[fa[que[i]]]--;
rep(i, len + 1, R) if (!_ind[que[i]]) die[que[i]] = -1;
rep(i, 1, cnt) if (die[h[i]] == -1) x++;
ansmax += cnt - x;
}
void solve(int rt)
{
int l = 1, r = 1; h[l] = rt;
while (l <= r)
{
int u = h[l++]; vis[u] = 1;
for (int i = head[u], v; v = e[i].node, i; i = e[i].next)
if (!vis[v]) vis[v] = 1, h[++r] = v;
}
cnt = r, len = 0;
rep(i, 1, r) if (!ind[h[i]]) que[++len] = h[i], inq[h[i]] = 1;
if (!len)
{
if (r == 1) ansmax++, ansmin++;
else ansmax += r - 1, ansmin += (r + 1) / 2;
return;
}
L = 1, R = len;
while (L <= R)
{
int u = que[L++];
ind[fa[u]]--;
if (!ind[fa[u]]) que[++R] = fa[u], inq[fa[u]] = 1;
}
cir_cnt = 0;
rep(i, 1, r) if (!inq[h[i]])
{
int tmp = h[i], u = h[i];
do
{
cir[++cir_cnt] = u;
incir[u] = 1;
u = fa[u];
}while (u != tmp);
break;
}
calc_min();
calc_max();
}
int main()
{
freopen("shoot.in", "r", stdin);
freopen("shoot.out", "w", stdout);
scanf("%d", &n);
rep(i, 1, n) fa[i] = getint(), ind[fa[i]]++, _ind[fa[i]]++, add(fa[i], i);
rep(i, 1, n) if (!vis[i]) solve(i);
printf("%d %d\n", ansmin, ansmax);
fclose(stdin); fclose(stdout);
return 0;
}