AtCoder Beginner Contest 281
《D - Max Multiple》
dp
这道题意思即为:在序列A中任选K个数的和组成的序列S,求S中为D的倍数的最大的数,如果没有则输出-1
当时我考虑到了dp,但是完全觉得状态转移是不可能的,但是这道题仔细想一下他的框架:
对于一个下标为i的数,考虑选不选他,选则对a[i],进行操作
不选则不对a[i],进行操作
从更简单的dfs来考虑这道题,如果用dfs写大概是什么框架?
1 // i是现在看到的下标,j是现在选择的多少个数
2 ll ans;
3 void dfs(int i, int j, ll num)
4 {
5 if (i > n || j > k)
6 return;
7 if (j == k && num % d == 0)
8 {
9 ans = max(ans, num);
10 return;
11 }
12 // 表示选a[i];
13 dfs(i + 1, j + 1, num + a[i]);
14 // 表示不选a[i];
15 dfs(i + 1, j, num);
16 }
17 // 选
18 dp[i][j][num] = max(dp[i][j][num], dp[i - 1][j - 1][num - a[i]] + a[i]);
19 // 不选
20 dp[i][j][num] = max(dp[i][j][num], dp[i - 1][j][num]);
然后会发现dp中num太大,但是题目中给的D很小只有100,想一下(a1+a2+....+ak)%d==0,其实等价于
(a1%d+a2%d......+ak%d)%d=0,
即dp[i][j][(num+a[i])%d]= max(dp[i][j][num], dp[i - 1][j - 1][num] + a[i]);
改成这个形式是为了防止在模d的情况下如果用减,变成负的会对状态转移造成麻烦;
dp[i][j][num]:在看到第i个数,已经选了j个数的情况下,总和在模d的情况下为num时,能够选择出的总和最大的数
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
const int N = 102;
ll dp[N][N][N];
int a[N];
int main()
{
int n, k, d;
cin >> n >> k >> d;
for (int i = 1; i <= n; i++)
cin >> a[i];
memset(dp, -1, sizeof(dp));
dp[0][0][0] = 0;
for (int i = 1; i <= n; i++)
for (int j = 0; j <= k; j++)
for (int u = 0; u < d; u++)
{
if (dp[i - 1][j][u] != -1)
dp[i][j][u] = max(dp[i][j][u], dp[i - 1][j][u]);
if (j >= 1 && dp[i - 1][j - 1][u] != -1)
dp[i][j][(u + a[i]) % d] = max(dp[i - 1][j - 1][u] + a[i], dp[i][j][(u + a[i]) % d]);
}
cout << dp[n][k][0];
return 0;
}
注意这个初始状态的转移,只有当dp[0][0][0]转移过来才是合法的,其余不合法
《E - Least Elements》
平衡二叉数(multiset),思维
题目意思很简单:给定一个序列A长度为N,给出数M,K
对于每一个i(1<=i<=N-M+1):定义一个新序列:ai,ai+1,.......,ai+M-1,并且将其从小到大排好序
求其前K个数的和;
最开始我的想法是:对于最初始的新序列,将其排好序,求出其前K个数的和
然后后面将每次要退出的数判断其是否在原来前K个数之前,对于新加进来的数判断其是否能够到前K个数,并将这个新来的数加入序列
但是判断我想用二分,但是这就要求整个序列有序,但是如果一个新加进来的数要插入,会消耗O(n)的时间复杂度
于是我不会了:
这个因为我不知道在STL库中有平衡二叉数的实现:multiset
平衡二叉数类似于排列二次树,查找,删除,增加节点基本上都是O(logn)(会比这个大,因为调整平衡的原因等),同时
可以使整个序列保持有序
在multiset默认中是从小到大,
multiset<int> st;
如果用迭代器如:multiset<int>:: iterator pos
for (pos= st.begin();pos!=st.end();pos++)
cout<<*pos<<" ";
那么打印出来的数是从小到大排好序的
即默认下*st.begin()是序列最小数,*(--st.end())是序列最大数
multiset的常用方法:
st.find(element),返回在st中元素为element的迭代器,找不到返回st.end();
st.erase(pos),删除迭代器pos所指的元素
st.insert(element),向st中添加元素element
题解:
既然涉及到前K个元素与后面的其他元素,我们可以设置两个multiset<int>pre,after
分别维护前K个元素的有序和后面元素的有序,提前算出了最初始的ans;
当i++时,会涉及到前面元素的消失,和后面元素的添加
如最开始新序列是 3 1 4 1,然后是 1 4 1 5
元素3消失,元素5添加;
这个时候如果对原来序列排好序了其实变的只有数 3 ,5
看一下消失的数是否在原来的前k个数中即pre中,如果在 ans-=消失的数,然后将消失的数从pre中删除,
否则只将其从after中删除
然后看一下新加入的数应加入那个中,是pre,还是after。
如果新加入的数<after中最小的数,那么就将其加入pre中
如果pre.size()>k,则将pre中最大的数加入after
如果pre.size()<k,则将after中最小的数加入pre
这是为了维护pre只有K个数的性质
1 #include <iostream>
2 #include <algorithm>
3 #include <cstring>
4 #include <set>
5 using namespace std;
6 typedef long long ll;
7 const int N = 2 * 1e5 + 2;
8 multiset<int> pre, after;
9 int arr[N], n, m, k;
10 /* void print(multiset<int> t)
11 {
12 multiset<int>::iterator pos;
13 for (pos = t.begin(); pos != t.end(); pos++)
14 cout << *pos << " ";
15 cout << endl;
16 }
17 void debug()
18 {
19 printf("pre: ");
20 print(pre);
21 printf("after: ");
22 print(after);
23 } */
24 int main()
25 {
26 cin >> n >> m >> k;
27 int t[N];
28 for (int i = 1; i <= n; i++)
29 {
30 cin >> arr[i];
31 if (i <= m)
32 t[i] = arr[i];
33 }
34 sort(t + 1, t + m + 1);
35 ll ans = 0;
36 for (int i = 1; i <= m; i++)
37 {
38 if (i <= k)
39 {
40 pre.insert(t[i]);
41 ans += t[i];
42 }
43 else
44 after.insert(t[i]);
45 }
46 cout << ans << " ";
47 /* debug(); */
48 for (int i = 2; i <= n - m + 1; i++)
49 {
50 int delnum = arr[i - 1], addnum = arr[i + m - 1];
51 // 这说明delnum不在前k个数中
52 if (pre.find(delnum) == pre.end())
53 after.erase(after.find(delnum));
54 else
55 {
56 ans -= delnum;
57 pre.erase(pre.find(delnum));
58 }
59 /* debug(); */
60 // 这里不管delnum是不是pre,都可以将addnum加入after中;
61 if (addnum < *after.begin())
62 {
63 pre.insert(addnum);
64 ans += addnum;
65 }
66 else
67 after.insert(addnum);
68
69 // 因为pre个数恒为k,整个序列的数量恒为m,则可以通过下面的方法去调整
70 while (pre.size() > k)
71 {
72 // 说明要将pre中最大的数移动到after中
73 multiset<int>::iterator maxnum = --pre.end();
74 pre.erase(maxnum);
75 after.insert(*maxnum);
76 ans -= *maxnum;
77 }
78 while (pre.size() < k)
79 {
80 // 说明要将after中最小的数移动到pre中
81 multiset<int>::iterator minnum = after.begin();
82 after.erase(minnum);
83 pre.insert(*minnum);
84 ans += *minnum;
85 }
86 /* debug(); */
87 cout << ans << " ";
88 }
89 return 0;
90 }