A - Yet Another Dividing into Teams

题意:n个不同数,分尽可能少的组,要求组内没有两个人的差恰为1。

题解:奇偶分组。

int a[200005];

void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
sort(a + 1, a + 1 + n);
int ans = 1;
for(int i = 2; i <= n; ++i) {
if(a[i] == a[i - 1] + 1)
ans = 2;
}
printf("%d\n", ans);
}

B1 - Books Exchange (hard version)

见下

B2 - Books Exchange (hard version)

题意:有一个排列,求每个位置的环的大小。

题解:并查集或者直接dfs。

int p[200005];
int ans[200005]; int cnt;
void dfs(int u, int t) {
if(u == t)
return;
++cnt;
dfs(p[u], t);
ans[u] = cnt;
} void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &p[i]);
ans[i] = 0;
}
for(int i = 1; i <= n; ++i) {
if(!ans[i]) {
cnt = 1;
dfs(p[i], i);
ans[i] = cnt;
}
printf("%d%c", ans[i], " \n"[i == n]);
}
}

C1 - Good Numbers (easy version)

见下

C2 - Good Numbers (hard version)

题意:一个good number是有一系列互异的3的幂次的和构成的,求超过n的最小的good number。

题解:这么说每种幂次都至多出现1次,先让大家都出现,然后优先去掉最大的幂次。

ll p3[105];

void test_case() {
p3[0] = 1;
int top = 0;
while(p3[top] < 1e18)
p3[++top] = p3[top - 1] * 3ll;
ll sum = 0;
for(int i = 0; i <= top; ++i) {
//cout<<p3[i]<<endl;
sum += p3[i];
}
int q;
scanf("%d", &q);
while(q--) {
ll n;
scanf("%lld", &n);
ll tsum = sum;
for(int i = top; i >= 0; --i) {
if(tsum - p3[i] >= n)
tsum -= p3[i];
}
printf("%lld\n", tsum);
} }

D1 - Too Many Segments (easy version)

见下

D2 - Too Many Segments (hard version)

题意:给一个数轴,上面有n个线段,移除最少的线段使得没有任何一个点被覆盖超过k次。(包括端点)

题解:看起来可以口胡一个贪心,先用线段树update出每个点的被覆盖次数,然后在每个点记录覆盖当前点的最右的线段是哪个(不过这样好像会记录太多线段),从左往右扫描,当一个点超过k时就移除最右的那条线段。这个做法的问题在于不能维护最右的线段是哪个。不过我们从左往右扫的时候遇到左端点就把线段的右端点插进set里面,然后遇到线段的右端点就可以把它移除(其实不移除也可以)。仔细想想甚至不需要线段树,也不需要set。

int n, k;

int l[200005], r[200005];
vector<int> L[200005], R[200005]; const int N = 200000; bool del[200005];
priority_queue<pii> S; vector<int> ans; void test_case() {
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++i) {
scanf("%d%d", &l[i], &r[i]);
L[l[i]].push_back(i);
R[r[i]].push_back(i);
}
int cnt = 0;
for(int i = 1; i <= N; ++i) {
for(auto &t : L[i]) {
++cnt;
S.push({r[t], t});
}
while(cnt > k) {
--cnt;
int t = S.top().second;
S.pop();
del[t] = 1;
ans.push_back(t);
}
for(auto &t : R[i]) {
if(!del[t])
--cnt;
}
}
printf("%d\n", (int)ans.size());
for(auto &t : ans)
printf("%d ", t);
printf("\n");
}

E - By Elevator or Stairs?

题意:有一个n层建筑,求从第1层去第i层的最短时间。你可以选择走楼梯,或者支付一个等待时间之后走电梯。

题解:那只需要记录前一层最后在楼梯和在电梯的最小花费直接转移就行了。

大意了,没看范围就交了,还好不会溢出。

int a[200005];
int b[200005]; void test_case() {
int n, c;
scanf("%d%d", &n, &c);
for(int i = 1; i <= n - 1; ++i)
scanf("%d", &a[i]);
for(int i = 1; i <= n - 1; ++i)
scanf("%d", &b[i]);
int dp0 = 0, dp1 = c;
for(int i = 1; i <= n; ++i) {
if(i > 1) {
int ndp0 = min(dp0, dp1) + a[i - 1];
int ndp1 = min(dp0 + c + b[i - 1], dp1 + b[i - 1]);
dp0 = ndp0;
dp1 = ndp1;
}
printf("%d%c", min(dp0, dp1), " \n"[i == n]);
}
}

F - Maximum Weight Subset

题意:在一棵树上面找一个点集,使得点集的权重和最大且点集中的点两两之间的距离严格大于k。

题解:感觉是可以树形dp的,设dp[i][j]为以结点i为根的子树,最近的被选择点距离为j的最大的权值。当i被选择时j就是0;否则当i的儿子被选择时j就是1。

但是子树之间的选择是比较复杂的。先把第一棵子树之间合并到根上:dp[u][j+1]=dp[i][j],且dp[u][0]=dp[i][k]。

再遍历u的其他子树i,距离为j的子树可以向根的距离t>=k-j配对,这时尝试更新dp[u][min(j+1,t)]。

看起来是个n^3的dp。

但是这个实际上有一些细节,比方说我这棵子树只有很底部的选了,而根节点不选,这个时候距离是>=k+1的状态,这些应该统一存起来。

int n, k;
int w[205];
int dp[205][205]; vector<int>G[205]; void dfs(int u, int p) {
if(G[u].size() == 1 && p != -1) {
dp[u][0] = w[u];
return;
}
bool first = 1;
for(int i = 0; i < G[u].size(); ++i) {
int v = G[u][i];
if(v == p)
continue;
dfs(v, u);
if(first) {
for(int j = 0; j + 1 <= k; ++j)
dp[u][j + 1] = dp[v][j];
dp[u][0] = w[u] + dp[v][k];
first = 0;
} else {
for(int j = 0; j <= k; ++j) {
for(int t = k - j; t <= k; ++t)
dp[u][min(j + 1, t)] = max(dp[u][min(j + 1, t)], dp[u][t] + dp[v][j]);
}
}
}
/*for(int j = 0; j <= k; ++j)
printf("dp[%d][%d]=%d\n", u, j, dp[u][j]);*/
} void test_case() {
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++i)
scanf("%d", &w[i]);
if(n == 1) {
printf("%d\n", w[1]);
return;
}
for(int i = 1; i <= n - 1; ++i) {
int u, v;
scanf("%d%d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1, -1);
int ans = 0;
for(int j = 0; j <= k; ++j)
ans = max(ans, dp[1][j]);
printf("%d\n", ans);
}

上面错在合并子树的时候可能同一棵子树重复更新了根节点的某个距离,导致状态与实际不符,解决的办法是往临时数组中更新,然后再把临时数组写回去(类似滚动数组)。而且状态设为dp[i][j]为以i为根的子树最近一个被选择点的距离超过j的最大值:

int n, k;
int w[205];
int dp[205][205];
int tmp[205]; vector<int>G[205]; void dfs(int u, int p) {
if(G[u].size() == 1 && p != -1) {
dp[u][0] = w[u];
return;
}
bool first = 1;
for(int i = 0; i < G[u].size(); ++i) {
int v = G[u][i];
if(v == p)
continue;
dfs(v, u);
if(first) {
dp[u][0] = w[u] + dp[v][k];
for(int j = 0; j <= k; ++j)
dp[u][j + 1] = dp[v][j];
for(int j = k; j >= 0; --j)
dp[u][j] = max(dp[u][j + 1], dp[u][j]);
first = 0;
} else {
tmp[0] = dp[u][0] + dp[v][k];
for(int j = 0; j <= k; ++j)
tmp[j + 1] = dp[v][j];
for(int j = 0; j <= k; ++j) {
for(int t = k - j; t <= k + 1; ++t)
tmp[min(j + 1, t)] = max(tmp[min(j + 1, t)], dp[u][t] + dp[v][j]);
}
for(int j = k; j >= 0; --j)
tmp[j] = max(tmp[j + 1], tmp[j]);
for(int j = 0; j <= k + 1; ++j)
dp[u][j] = max(dp[u][j], tmp[j]);
}
}
/*for(int j = 0; j <= k; ++j)
printf("dp[%d][%d]=%d\n", u, j, dp[u][j]);
puts("");*/
} void test_case() {
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++i)
scanf("%d", &w[i]);
if(n == 1) {
printf("%d\n", w[1]);
return;
}
for(int i = 1; i <= n - 1; ++i) {
int u, v;
scanf("%d%d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1, -1);
int ans = 0;
for(int j = 0; j <= k + 1; ++j)
ans = max(ans, dp[1][j]);
printf("%d\n", ans);
}

假如设为恰好等于的话是这样的,也是要用k+1表示大于等于k的所有值,在这里截断。和南京区域赛的那个银牌题差不多。

int n, k;
int w[205];
int dp[205][205];
int tmp[205]; vector<int>G[205]; void dfs(int u, int p) {
if(G[u].size() == 1 && p != -1) {
dp[u][0] = w[u];
return;
}
bool first = 1;
for(int i = 0; i < G[u].size(); ++i) {
int v = G[u][i];
if(v == p)
continue;
dfs(v, u);
if(first) {
dp[u][0] = w[u] + max(dp[v][k], dp[v][k + 1]);
for(int j = 0; j <= k - 1; ++j)
dp[u][j + 1] = dp[v][j];
dp[u][k + 1] = max(dp[v][k], dp[v][k + 1]);
first = 0;
} else {
tmp[0] = dp[u][0] + max(dp[v][k], dp[v][k + 1]);
for(int j = 0; j <= k - 1; ++j)
tmp[j + 1] = dp[v][j];
tmp[k + 1] = max(dp[v][k], dp[v][k + 1]);
for(int j = 0; j <= k + 1; ++j) {
for(int t = max(0, k - j); t <= k + 1; ++t)
tmp[min(j + 1, t)] = max(tmp[min(j + 1, t)], dp[u][t] + dp[v][j]);
}
for(int j = 0; j <= k + 1; ++j)
dp[u][j] = max(dp[u][j], tmp[j]);
}
}
/*for(int j = 0; j <= k; ++j)
printf("dp[%d][%d]=%d\n", u, j, dp[u][j]);
puts("");*/
} void test_case() {
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++i)
scanf("%d", &w[i]);
if(n == 1) {
printf("%d\n", w[1]);
return;
}
for(int i = 1; i <= n - 1; ++i) {
int u, v;
scanf("%d%d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1, -1);
int ans = 0;
for(int j = 0; j <= k + 1; ++j)
ans = max(ans, dp[1][j]);
printf("%d\n", ans);
}

不要漏掉了j=k+1状态的转移。也就是一开始设状态的时候就要考虑到有个k+1表示距离>=k+1的点。

总结:一题很好玩的树形dp。注意:1、树上度为1的虽然都是叶子,但是假如是根的话也要继续往下走。2、不要在原地更新,要另外开一个tmp数组保存。3、即使设状态为dp[i][j]为以i为根的子树最近一个被选择点的距离超过j的最大值,在合并子树的时候也要注意虽然选择t的时候是选择高的位置更新会得到更大的答案,但事实上j可以更新到一片连续的t。

Codeforces Round #595 (Div. 3)的更多相关文章

  1. Codeforces Round #595 (Div. 3)D1D2 贪心 STL

    一道用STL的贪心,正好可以用来学习使用STL库 题目大意:给出n条可以内含,相交,分离的线段,如果重叠条数超过k次则为坏点,n,k<2e5 所以我们贪心的想我们从左往右遍历,如果重合部分条数超 ...

  2. Codeforces Round #595 (Div. 3)B2 简单的dfs

    原题 https://codeforces.com/contest/1249/problem/B2 这道题一开始给的数组相当于地图的路标,我们只需对每个没走过的点进行dfs即可 #include &l ...

  3. Codeforces Round #595 (Div. 3) D2Too Many Segments,线段树

    题意:给n个线段,每个线段会覆盖一些点,求删最少的线段,使得每个点覆盖的线段不超过k条. 思路:按右端点排序,之后依次加入每个线段,查询线段覆盖区间内的每个点,覆盖的最大线段数量,如果不超过k,那就可 ...

  4. Codeforces Round #595 (Div. 3) A,B,C,D

    A题:n个学生,分成任意组,要求每组中任意两名学生的技能值相差不等于1,问最小分组. #include<bits/stdc++.h> using namespace std; #defin ...

  5. Codeforces Round #595 (Div. 3) 题解

    前言 大家都在洛谷上去找原题吧,洛谷还是不错的qwq A 因为没有重复的数,我们只要将数据排序,比较两两之间有没有\(a_j - a_i == 1 (j > i)\) 的,有则输出 \(2\) ...

  6. Codeforces Round #366 (Div. 2) ABC

    Codeforces Round #366 (Div. 2) A I hate that I love that I hate it水题 #I hate that I love that I hate ...

  7. Codeforces Round #354 (Div. 2) ABCD

    Codeforces Round #354 (Div. 2) Problems     # Name     A Nicholas and Permutation standard input/out ...

  8. Codeforces Round #368 (Div. 2)

    直达–>Codeforces Round #368 (Div. 2) A Brain’s Photos 给你一个NxM的矩阵,一个字母代表一种颜色,如果有”C”,”M”,”Y”三种中任意一种就输 ...

  9. cf之路,1,Codeforces Round #345 (Div. 2)

     cf之路,1,Codeforces Round #345 (Div. 2) ps:昨天第一次参加cf比赛,比赛之前为了熟悉下cf比赛题目的难度.所以做了round#345连试试水的深浅.....   ...

随机推荐

  1. Python进阶(十一)----包,logging模块

    Python进阶(十一)----包,logging模块 一丶包的使用 什么是包: ​ 包是通过使用 .模块名的方式组织python模块名称空间的方式. 通俗来说,含有一个__init__.py文件的文 ...

  2. MPSoc之Hello World学习笔记

    XILINX 新一代 SOC,Zynq UltraScale+ MPSOC 系列性能强悍无比,号称相比ZYNQ 7000系列每瓦性能提升5倍,一直想体验.近期因项目需要,入手了一套米尔的MPSoc开发 ...

  3. Java基础之枚举类型Enum的使用

    Java基础之枚举类型Enum的使用 定义 public enum AccruedCleanEnum { SPREAD("1","发票"),OTHER(&quo ...

  4. 彻底弄懂ES6中的Map和Set

    Map Map对象保存键值对.任何值(对象或者原始值) 都可以作为一个键或一个值.构造函数Map可以接受一个数组作为参数. Map和Object的区别 一个Object 的键只能是字符串或者 Symb ...

  5. ABAP和Java里的单例模式攻击

    面向对象编程世界里的单例模式(Singleton)可能是设计模式里最简单的一种,大多数开发人员都觉得可以很容易掌握它的用法.单例模式保证一个类仅有一个实例,并提供一个访问它的全局访问点. 然而在某些场 ...

  6. Cannot assign to read only property 'exports' of object '#<Object>' ,文件名大小写问题!!!

    有些坑不知道怎么就掉进去,可能一辈子都爬不起来!!! 一.错误描述 昨天还好好的,今天早上来从git获取了一下别人提交的代码就出错了!而提交代码的人 运行一点错误都没有!!! cya@KQ-101 M ...

  7. Gitlab 重置 root 密码

    要重置root密码,请先使用root权限登录服务器.使用以下命令启动Ruby on Rails控制台: gitlab-rails console production 等到控制台加载完毕,您可以通过搜 ...

  8. ant安装(Windows)

    ant安装(Windows) ant 下载之前参考一下官网的ant与java版本依赖 1. 下载地址 2. 解压与配置 1. 下载地址 ant官网 所有版本 2. 解压与配置 java版本:1.8.0 ...

  9. JMeter压测时报“内存不足”故障的9个简单解决方案

    Test failed! java.lang.OutOfMemoryError: Java heap space 测试失败了!java.lang.OutOfMemoryError:Java堆空间 在不 ...

  10. failed to recover intents

    failed to recover intents 无法恢复意图