ABC369 C-G题解 (AtCoder Beginner Contest 369)

 

C

对于一个等差数列,它里面包含的等差数列(取这个数列的第i位~第j位),必定也是等差数列。

寻找等差数列的时候,如果一个等差数列,向最左/最右加1个数后,仍是等差数列,则把它们加上。从而寻找所有场上的等差数列,必定是不重叠的,等差数列彼此独立。

从而可以从1遍历到n,O(n)复杂度。

对于每一段等差数列,只统计里面包含的长度大于等于2的等差数列。最后再加上长度等于1的等差数列,即为单独的每个数,为n。这样,思路会清晰一点

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 #define LL long long
 4 #define ULL unsigned long long
 5 
 6 const LL mod_1=1e9+7;
 7 const LL mod_2=998244353;
 8 
 9 const double eps_1=1e-5;
10 const double eps_2=1e-10;
11 
12 const int maxn=2e5+10;
13 
14 LL a[maxn];
15 
16 int main()
17 {
18     LL n,i,j,d;
19     LL sum=0;
20     cin>>n;
21     for (i=1;i<=n;i++)
22         cin>>a[i];
23     j=1;
24     for (i=1;i<n;)
25     {
26         d=a[i+1]-a[i];
27         j=i;
28         i++;
29         while (i<n && a[i+1]-a[i]==d)
30             i++;
31         sum+=1LL*(i-j)*(i-j+1)/2;
32     }
33     cout<<sum+n;
34 
35     return 0;
36 }

 

 

D

DP

写成数组形式,好理解一点,写少一点代码

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 #define LL long long
 4 #define ULL unsigned long long
 5 
 6 const LL mod_1=1e9+7;
 7 const LL mod_2=998244353;
 8 
 9 const double eps_1=1e-5;
10 const double eps_2=1e-10;
11 
12 const int maxn=2e5+10;
13 
14 LL f[maxn][2];
15 
16 
17 int main()
18 {
19     LL n,i,a;
20     f[0][0] = 0;
21     f[0][1] = -1e18;
22     cin>>n;
23     for (i=1;i<=n;i++)
24     {
25         cin>>a;
26         f[i][0] = max(f[i-1][0], f[i-1][1] + a*2);
27         f[i][1] = max(f[i-1][1], f[i-1][0] + a);
28     }
29     
30     cout<<max(f[n][0],f[n][1]);
31     return 0;
32 }

 

E

floyd 求两两每个点之间的距离 + dfs获得几个桥所有可能的顺序

看到别人有用next_permutation,而且是挺多人用这个,挺不错。

但是后面,直接一行写出情况,对于大部分人来说是不是容易写错?而且关键是挺多人都这样写,我感觉比较神奇emmm。我个人,感觉用dfs相对不容易写错。

dfs获得几个桥所有可能的顺序,其实就很像旅行商问题,所有城市都访问一遍。然后这里是桥可以选择两个方向,也不好用状态压缩。

g个点,2^g个方向,桥的顺序,g!排列组合,每种桥方向类型,g个权值和相加,O(2^g*g!*g) 19,200 再乘个Q,没问题

 

 

我在比赛的时候,把floyd的顺序搞反了,然后几天找不到问题,真的绷不住了!!!floyd算法,三重循环的顺序问题,不要写错了 - congmingyige - 博客园 (cnblogs.com)

 

 

 1 /*
 2 n,m的大小,对应创建数组的大小,不要弄混,特别是这类题,n<=400,m<=2e5,提交前要检查一遍。当RE的时候,就要第一时间意识到数组大小设置错误的问题
 3 
 4 怎么也调不出问题,包括检查代码,造样例,看题面,就立刻先切换到下一题。没必要花那么多时间耗在上面,否则检查错误,采用错误的方法,做很多无用功。而且影响做题时间和效率。这些时间,今早做下一题,有可能就做出来其它题了
 5 做选择,要么全攻下一题(意识到难发现错误在哪,立刻切换,检查时间不要太长),要么剩下这些时间,全力检查这一题,不要既这又那
 6 用assert等方式找一下问题所在,还有造一下大样例
 7 */
 8 #include <bits/stdc++.h>
 9 using namespace std;
10 #define LL long long
11 #define ULL unsigned long long
12 
13 const LL mod_1=1e9+7;
14 const LL mod_2=998244353;
15 
16 const double eps_1=1e-5;
17 const double eps_2=1e-10;
18 
19 const int maxn=4e2+10;
20 const int maxm=2e5+10;
21 
22 LL u[maxm],v[maxm],t[maxm],dist[maxn][maxn],b[10],g,n,result;
23 bool vis[10];
24 
25 void dfs(LL d, LL ans, LL road)
26 {
27     LL i,j;
28     if (ans==g)
29     {
30         result = min(result, road+dist[d][n]);
31         return;
32     }
33     for (i=1;i<=g;i++)
34         if (vis[i]==0)
35         {
36             j=b[i];
37 
38             vis[i]=1;
39             dfs(v[j], ans+1, road+dist[d][u[j]] + t[j]);
40             vis[i]=0;
41 
42             vis[i]=1;
43             dfs(u[j], ans+1, road+dist[d][v[j]] + t[j]);
44             vis[i]=0;
45         }
46 }
47 
48 int main()
49 {
50     LL m,q,i,j,k;
51     cin>>n>>m;
52     for (i=1;i<=n;i++)
53         for (j=1;j<=n;j++)
54             dist[i][j] = 1e15;
55     for (i=1;i<=n;i++)
56         dist[i][i]=0;
57     for (i=1;i<=m;i++)
58     {
59         cin>>u[i]>>v[i]>>t[i];
60         dist[ u[i] ][ v[i] ] = dist[ v[i] ][ u[i] ] = min(dist[ u[i] ][ v[i] ], t[i]);
61     }
62     
63     for (k=1;k<=n;k++)
64         for (i=1;i<=n;i++)
65             for (j=1;j<=n;j++)
66                  dist[i][j] = min(dist[i][j], dist[i][k]+dist[k][j]);
67     /*
68     for (i=1;i<=n;i++)
69         for (j=1;j<=n;j++)
70             assert(dist[i][j]<1e15);
71     */
72 
73     cin>>q;
74     while (q--)
75     {
76         cin>>g;
77         for (i=1;i<=g;i++)
78             cin>>b[i];
79         memset(vis,0,sizeof(vis));
80         result=1e18;
81         dfs(1,0,0);
82         //assert(result<1e18);
83         cout<<result<<endl;
84     }
85 
86     return 0;
87 }
88 /*
89 g个点
90 2^g个方向,桥的顺序,g!排列组合,每种桥方向类型,g个权值和相加,O(2^g*g!*g) 19,200 再乘个Q,没问题
91 */

 

F

应该看一下题面,在剩余1个小时的时候,这样才有时间做和取舍(题D、E)
这种属于题面很容易理解,也很容易想到解法的。就是写的过程中容易写错,debug也是,需要时间长一点
感觉这是高手和某类菜鸟的区别。某类菜鸟也可以很快找到解法,就是写代码和调试代码的时候,对于这类比较琐碎的代码,需要写挺久

 

能想到的基础题目是用DP做,二维方格,dp[i][j]=max(dp[i-1][j], dp[i][j-1])。

现在是n、m相乘比较大,然后硬币数比较少。

因为只能向下或者向右,所以对于一个点,只有列的数值小于等于它的点,才能访问到它。这容易想到树状数组。同样,对于行,也是只有行的数值小于等于它的点,才能访问到它。

f[i]= max(f[j]) + 1。 i表示这个点的列数值为i,j是所有小于i的数,用树状数组获取f数组里1~i的最大值。

按照硬币行数值优先,然后列数值优先的顺序排序,然后依次访问硬币。可以确保,对于该硬币,前面的硬币的行都是小于等于它,后面的硬币的行都是大于它,或者行等于它但是列大于它,无法从后面的硬币到该硬币。

 

  1 /*
  2 应该看一下题面,在剩余1个小时的时候,这样才有时间做和取舍(题D、E)
  3 这种属于题面很容易理解,也很容易想到解法的。就是写的过程中容易写错,debug也是,需要时间长一点
  4 感觉这是高手和某类菜鸟的区别。某类菜鸟也可以很快找到解法,就是写代码和调试代码的时候,对于这类比较琐碎的代码,需要写挺久
  5 
  6 用xpre,ypre,x,y,这样变量不容易搞错。还有,找问题的,多检查一下变量,这种变量最容易写错
  7 */
  8 #include <bits/stdc++.h>
  9 using namespace std;
 10 #define LL long long
 11 #define ULL unsigned long long
 12 
 13 const LL mod_1=1e9+7;
 14 const LL mod_2=998244353;
 15 
 16 const double eps_1=1e-5;
 17 const double eps_2=1e-10;
 18 
 19 const int maxn=2e5+10;
 20 
 21 pair<int,int> par[maxn];
 22 
 23 map<pair<int,int>, pair<int,int> > mp;
 24 
 25 pair<int, int> pre[maxn];
 26 int f[maxn];
 27 vector<string> vec;
 28 
 29 void add_path(pair<int,int> dpre, pair<int,int> d)
 30 {
 31     int i,j;
 32     string str="";
 33     for (i=dpre.first;i<d.first;i++)
 34         str+='D';
 35     for (j=dpre.second;j<d.second;j++)
 36         str+='R';
 37     vec.push_back(str);
 38 }
 39 
 40 void dfs(pair<int,int> d)
 41 {
 42     pair<int,int> dpre;
 43 
 44     dpre = mp[d];
 45 
 46     if (dpre!=make_pair(-1,-1))
 47     {
 48         add_path(dpre,d);
 49         dfs(dpre);
 50     }
 51     else
 52         add_path(make_pair(1,1),d);
 53 }
 54 
 55 int main()
 56 {
 57     int h,w,n,x,y,v,i,j,y_max=2e5;
 58     pair<int,int> dpre,d;
 59     cin>>h>>w>>n;
 60     memset(f,0,sizeof(f));
 61     for (i=0;i<n;i++)
 62     {
 63         cin>>x>>y;
 64         par[i] = make_pair(x,y);
 65     }
 66     sort(par, par+n);
 67 
 68     for (j=0;j<n;j++)
 69     {
 70         x = par[j].first;
 71         y = par[j].second;
 72         dpre = make_pair(-1,-1);
 73 
 74         v = 0;
 75         for (i=y;i>=1;i-=i&-i)
 76             if (f[i]>v)
 77             {
 78                 v=f[i];
 79                 dpre = pre[i];
 80             }
 81 
 82         v++;
 83 
 84         if (v>f[y])
 85         {
 86             d = make_pair(x,y);
 87             mp[d] = dpre;
 88 
 89             for (i=y;i<=y_max;i+=i&-i)
 90                 if (v>f[i])
 91                 {
 92                     f[i]=v;
 93                     pre[i]=d;
 94                 }
 95         }
 96     }
 97     
 98     v=0;
 99     for (i=y_max;i>=1;i-=i&-i)
100         if (f[i]>v)
101         {
102             v=f[i];
103             dpre=pre[i];
104         }
105     cout<<v<<endl;
106     add_path(dpre,make_pair(h,w));
107     dfs(dpre);
108     reverse(vec.begin(), vec.end());
109     for (auto d:vec)
110         cout<<d;
111 
112     return 0;
113 }

 

学习官方代码,

 

vector 初始化,第二个数,就是vector里初始化的数值

 

LIS,二分的写法,4行就搞定了??!!!

对于pre数组,记录点id,而不是记录二维坐标,这样方便一点,写少一点代码

用二分的写法,而不是树状数组的写法,只用修改一次pre,这个点的pre就是数组里上一个点,用树状数组,每次+lowbit后,都要判断修改一次,总感觉代码量相对比较大,不利索

pre[i] = (it ? id[it - 1] : -1); 这个写法不错
 

官方也是用while、vector记录从终点出发,不断pre的点序列,然后用reverse,的确,这个好用。我感觉用dfs,写得挺不舒服的

1     int now = id[m];
2     while (now != -1) {
3         path.push_back(coins[now]);
4         now = pre[now];
5     }
6     path.emplace_back(1, 1);
7     reverse(path.begin(), path.end());

 

 

 1 #include <bits/stdc++.h>
 2 
 3 using namespace std;
 4 
 5 int main() {
 6     int h, w, n;
 7     cin >> h >> w >> n;
 8     vector<pair<int, int>> coins;
 9     for (int i = 0; i < n; i++) {
10         int r, c;
11         cin >> r >> c;
12         coins.emplace_back(r, c);
13     }
14     sort(coins.begin(), coins.end());
15     vector<int> dp(n, 1e9), id(n, -1), pre(n);
16     for (int i = 0; i < n; i++) {
17         int it = upper_bound(dp.begin(), dp.end(), coins[i].second) - dp.begin();
18         dp[it] = coins[i].second;
19         id[it] = i;
20         pre[i] = (it ? id[it - 1] : -1);
21     }
22     int m = n - 1;
23     while (id[m] == -1) --m;
24     vector<pair<int, int>> path = {{h, w}};
25     int now = id[m];
26     while (now != -1) {
27         path.push_back(coins[now]);
28         now = pre[now];
29     }
30     path.emplace_back(1, 1);
31     reverse(path.begin(), path.end());
32     string s;
33     for (int i = 0; i < (int) path.size() - 1; i++) {
34         int d = path[i + 1].first - path[i].first;
35         int r = path[i + 1].second - path[i].second;
36         while (d--) s.push_back('D');
37         while (r--) s.push_back('R');
38     }
39     cout << m + 1 << '\n' << s << '\n';
40 }

 

G

我们只需要选择叶子节点。如果选择了其它点,那么它的孙子节点必定比它更适合。

记录点1到某个点的长度,从大到小排序依次取。

取了一个点后,点1到这个点经过的边,所有的边值都要修改为0,即点1到其它点经过的边中,再用到这些边,也无法增加权值。

暴力做的话,即每个取一个点后,修改受影响的其它点(有与这个点共用边的),不行。比如这个图 ,每次拿最左边的点,然后靠右的点都要受到重新计算权值,要修改的点的数目太多了。

 

树的长链剖分

树链剖分 - OI Wiki (oi-wiki.org)

 

 

区别在于,定义 重子节点 表示其子节点中子树经过边权值之和最大的子结点

无论选择的顺序如何(权值大小的排序),我们必定是按照长链剖分的方式选择叶子节点/边。

 

我用的方法是,对于一个点的所有子节点,不计算重点(只会有一个或者没有),剩余其它点,都统计长度(从这个点开始到某个叶节点的路径),同时,不要遗漏根节点。就比如上图里,记录所有白色点为起始的路径。

Atcoder Beginner Contest 369 - maniubi - 博客园 (cnblogs.com) 这个的代码,统计轻子节点,放在了dfs函数之外。

 

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 #define LL long long
 4 #define ULL unsigned long long
 5 
 6 const LL mod_1=1e9+7;
 7 const LL mod_2=998244353;
 8 
 9 const double eps_1=1e-5;
10 const double eps_2=1e-10;
11 
12 const int maxn=2e5+10;
13 
14 vector<pair<LL, LL> > adj[maxn];
15 bool vis[maxn];
16 LL len[maxn],fa[maxn],ans[maxn];
17 vector<LL> result;
18 
19 void dfs(LL d)
20 {
21     LL child,value,cur_len,hson;
22     vis[d]=1;
23     for (auto single:adj[d])
24     {
25         child = single.first;
26         value = single.second;
27 
28         if (!vis[child])
29         {
30             dfs(child);
31 
32             cur_len = len[child] + value;
33 
34             if (cur_len>len[d])
35             {
36                 len[d] = cur_len;
37                 hson = child;
38             }
39         }
40         else
41             fa[d] = child;
42     }
43 
44     for (auto single:adj[d])
45     {
46         child = single.first;
47         value = single.second;
48         if (child!=hson && child!=fa[d])
49             result.push_back(len[child]+value);
50     }
51 }
52 
53 int main()
54 {
55     LL n,i,u,v,w;
56     cin>>n;
57     for (i=1;i<n;i++)
58     {
59         cin>>u>>v>>w;
60         adj[u].push_back(make_pair(v,w));
61         adj[v].push_back(make_pair(u,w));
62     }
63 
64     memset(vis,0,sizeof(vis));
65     memset(len,0,sizeof(len));
66     fa[1]=0;
67     dfs(1);
68 
69     result.push_back(len[1]);
70     sort(result.begin(),result.end(),greater<LL>());
71     
72     i=0;
73     ans[0]=0;
74     for (auto d:result)
75     {
76         i++;
77         ans[i]=ans[i-1]+d;
78     }
79     i++;
80     while (i<=n)
81     {
82         ans[i]=ans[i-1];
83         i++;
84     }
85     for (i=1;i<=n;i++)
86         cout<<ans[i]*2<<endl;
87 
88 
89     return 0;
90 }

 

学习它这种写法:

    int ans = 0;
    for (int i = 0; i < n; i ++) {
        if (i < d.size()) ans += d[i];
        cout << ans * 2ll << "\n";
    }

 

posted @ 2024-09-05 17:35  congmingyige  阅读(110)  评论(0)    收藏  举报