AtCoder Beginner Contest 245
**C - Choose Elements **(dp)
原题
题意
给定两个长度为N的整数序列A和B,整数K。询问是否能找出来满足下列两个条件的长度为N的整数序列X:
- Xi是Ai或Bi中的其中一个
- |Xi - Xi+1|<=K
思路
dp.第i个位置状态与第i-1个位置的状态有关,因此可以用dp来解决。
d[i] [j] 表示下标为i的j点是否可达,最后查看d[n-1] [j]是否可达即可。
代码实现
//当前位置的状态与前一个位置的状态有关
int q[N][3];
bool d[N][3];//dij表示Ai/Bi是否可达
void solve() {
int n = 0, k = 0;
cin >> n >> k;
for (int i = 0; i < n; i++) {
cin >> q[i][1];
}
for (int i = 0; i < n; i++) {
cin >> q[i][2];
}
d[0][1] = true;
d[0][2] = true;
for (int i = 1; i < n; i++) {
if (d[i - 1][1]) {
if (abs(q[i][1] - q[i - 1][1]) <= k) d[i][1] = true;
if (abs(q[i][2] - q[i - 1][1]) <= k) d[i][2] = true;
}
if (d[i - 1][2]) {
if (abs(q[i][1] - q[i - 1][2]) <= k) d[i][1] = true;
if (abs(q[i][2] - q[i - 1][2]) <= k) d[i][2] = true;
}
}
if (d[n - 1][1] || d[n - 1][2])printf("Yes");
else printf("No");
return;
}
收获
复习了一下dp,对dp的思想有了更进一步的体会(当前状态与前一状态有关)
D - Polynomial division
原题
题意
对于长度为N的序列A和长度为M的序列B,定义
同时定义
给定A(x)与C(x),要求得到B(x).
思路
多项式问题。
一般情况下可以正序得到所有Bi,也可以倒序得到所有Bi,但本题只限制An≠0,就只能倒序做了
因为Ci由多项组成,因此只要按顺序减掉其余项,最后剩余的一项就是要求的B*A
不妨令M>=N ( 反之逻辑相同 )
代码实现
void solve() {
int n = 0, m = 0;
cin >> n >> m;
for (int i = n; i >= 0;i--) cin >> a[i];
for (int i = n + m; i >= 0;i--) cin >> c[i];
for (int i = 0; i <= m; i++) {
b[i] = c[i] / a[0];
for (int j = 1; j <= n; j++) {
c[i + j] -= a[j] * b[i];
}
}
for (int i = m; i>=0;i--)
cout << b[i] << ' ';
return;
}//如果感觉输入输出不自然,正序输入后reverse一下也可以
收获
- 多项式问题的一种做法
- 多项式问题在纸上写一写可能更容易出思路
- 注意读题(一开始以为A0≠0,直接正序做了)
E - Wrapping Chocolate
原题
题意
有N片巧克力,宽Ai
,长Bi
(i=1,2,······,N),M个箱子,宽Ci
,长Di
.
巧克力的宽和长必须均小于等于箱子的宽和长时才能将巧克力放入箱子中。询问是否能将全部的巧克力放入箱子中。
思路
贪心,双变量。
在满足一个变量条件的所有情况中,选择对第二个变量条件的最优解。
本题:对于某块巧克力而言,在所有满足宽大于等于该巧克力宽的箱子中,选择长度大于等于该块巧克力的长且最小的箱子。
将巧克力和箱子降序排列后,将所有满足宽条件的箱子的长放入集合中,然后选择满足大于等于巧克力长的最小长度所对应的箱子。
代码实现
void solve() {
int n = 0, m = 0;
cin >> n >> m;
vector<array<int, 2>> cho(n), box(m);//创建变量大小的vector
for (int i = 0; i < n; i++) cin >> cho[i][0];
for (int i = 0; i < n; i++) cin >> cho[i][1];
for (int i = 0; i < m; i++) cin >> box[i][0];
for (int i = 0; i < m; i++) cin >> box[i][1];
sort(cho.begin(), cho.end(), greater());//倒序排vector
sort(box.begin(), box.end(), greater());
multiset<int> s;//自动排序,允许存重复元素//主要是用set的lower_bound函数
for (int i = 0,j = 0; i < n; i++) {
while (j < m && box[j][0] >= cho[i][0])
s.insert(box[j][1]),j++;//把所有宽度比当前巧克力宽度大的箱子存入set
auto m = s.lower_bound(cho[i][1]);//满足条件的长度最小的箱子,找不到则返回end()
if (m == s.end()) {
cout << "No";
return;
}
s.erase(m);//把这个箱子删掉
}
cout << "Yes";
return;
}
收获
-
贪心问题由单变量扩展到了双变量
-
如何倒序排列
vector
-
array+vector—独特的元素访问:
vector<array<int,3>>
中v[i][j](j=1,2,3)
-
set
中的两个独特函数lower_bound(x)
——找到第一个大于等于x的数,并返回迭代器,不存在则返回end()
upper_bound(x)
——找到第一个大于x的数,并返回迭代器,不存在则返回end()
-
multiset
和set
的区别:multiset和set都能对元素实现自动排序,但multiset允许存相同元素,而set则不可以lower_bound(x)
——找到第一个大于等于x的数,并返回迭代器,不存在则返回end()
upper_bound(x)
——找到第一个大于x的数,并返回迭代器,不存在则返回end()
-
multiset
和set
的区别:multiset和set都能对元素实现自动排序,但multiset允许存相同元素,而set则不可以
F - Endless Walk
题意
给定一个N个点(1—N),M条边的有向图,找出从1号点开始能够无限走的路径。
思路
图论,拓扑排序。
实际上就是找从1号点开始到环(包括环)的路径上所包含的所有点。我们知道在找拓扑排序的时候是找当前入度为0的点,由此可知在找拓扑排序的过程中,环是永远不会被遍历到的(环的起始点的入度不可能为0),同时不会被遍历到的还有不在从起点到环路径上的除环以外且与环相连的其他点。因此我们要存一个原图的反图,从反图的起点开始找拓扑序列(其实已经不是严格意义上的拓扑序列了,只是便于理解这样称呼),凡是能够遍历到的点,一定不在从原图1号点到环的路径上,而没有遍历到的点,则一定在从原图1号点到环的路径上,也就是我们所要求的答案。
思考:正向图和反向图都找不到的点,应该就是环中的所有点(不过不一定在一个环中)
代码实现
void solve() {
int n = 0, m = 0;
cin >> n >> m;
memset(h, -1, sizeof h);
int a = 0, b = 0;
while (m--) {
cin >> a >> b;
add(b,a);//存反图s
s.insert(a);
s.insert(b);//存入所有点
d[a]++;
}
int hh = 0, tt = -1;
for (int i = 1; i <= n; i++) {
if (!d[i]) {
s.erase(i);
q[++tt] = i;
}
}
//错误做法(无法处理入度出度均为0的点)
for (auto i : s) {
if (!d[i]) {
s.erase(i);
q[++tt] = i;
}
}//
while (hh <= tt) {//用数组模拟队列
int t = q[hh++];
for (int i = h[t]; i != -1; i = ne[i]) {
int j = e[i];
d[j]--;
if (!d[j]) {
q[++tt] = j;//入度为0则入队
s.erase(j);
}
}
}
auto res = s.size();
cout << res;
}
收获
- 又复习了一遍拓扑排序,加深理解
- 学到了一个图论中的新操作:反图。反图可以找到从起点到环的路径上的所有点,正反图结合可以找到所有在环中的点。不知道是不是拓扑排序的专有操作?