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 }
posted @ 2022-12-14 17:36  次林梦叶  阅读(22)  评论(0编辑  收藏  举报