Yandex Algorithm 2018 Warmup

Posted 大四开始ACM

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Yandex Algorithm 2018 Warmup相关的知识,希望对你有一定的参考价值。

A. Time Throught The Glass

题意:对于一个时钟,分针和秒针一定会在整数位置。下面告诉你这个时钟关于中垂线对称后的读数,让你输出它的真实读数。

观察:如果镜像读数是h,m的话,真实读数就是(12-h)%12, (60-m)%60。

code:

技术分享图片
 1 /*
 2  by skydog
 3  */
 4 #include <iostream>
 5 #include <cstdio>
 6 #include <vector>
 7 #include <utility>
 8 #include <algorithm>
 9 #include <cmath>
10 #include <cstring>
11 #include <map>
12 #include <set>
13 #include <stack>
14 #include <queue>
15 #include <deque>
16 #include <cassert>
17 #include <list>
18 using namespace std;
19 typedef long long ll;
20 typedef pair<int, int> ii;
21 typedef pair<ll, ll> l4;
22 typedef unsigned long long ull;
23 #define mp make_pair
24 #define pb push_back
25 
26 
27 int main()
28 {
29     int a, b;
30     scanf("%d %d", &a, &b);
31     printf("%d %d\n", (12-a)%12, (60-b)%60);
32 
33 }
View Code

 

B. Palindromic Feature

题意:给你一个长度不超过2e5的小写字母串,让你找出一个长度不小于2的最短回文字串。如果最短的由多个,取字典序最小的。无解输出-1。

观察:如果有解的话,答案一定是长度为2或者长度为3的串。暴力检查就好

code:

技术分享图片
 1 /*
 2  by skydog
 3  */
 4 #include <iostream>
 5 #include <cstdio>
 6 #include <vector>
 7 #include <utility>
 8 #include <algorithm>
 9 #include <cmath>
10 #include <cstring>
11 #include <map>
12 #include <set>
13 #include <stack>
14 #include <queue>
15 #include <deque>
16 #include <cassert>
17 #include <list>
18 using namespace std;
19 typedef long long ll;
20 typedef pair<int, int> ii;
21 typedef pair<ll, ll> l4;
22 typedef unsigned long long ull;
23 #define mp make_pair
24 #define pb push_back
25 
26 bool work(const string &str)
27 {
28     for (int i = 0; i < str.length(); ++i)
29         if (str[i] != str[str.length()-i-1])
30             return false;
31     return true;
32 }
33 string check(const string &str, int len)
34 {
35     string ret = "";
36     for (int i = 0; i+len <= str.length(); ++i)
37     {
38         auto tmp = str.substr(i, len);
39         if (!work(tmp))
40             continue;
41         if (ret.empty() || ret > tmp)
42             ret = tmp;
43     }
44     return ret;
45 }
46 int main()
47 {
48     string str;
49     cin >> str;
50     string ans = check(str, 2);
51     if (ans.empty())
52         ans = check(str, 3);
53     if (ans.empty())
54         cout << "-1\n";
55     else
56         cout << ans << \n;
57 }
View Code

WA:一开始没有看清楚题意,还以为是要求字典序最小的回文子串,就开始找后缀家族和回文家族的模版。但是看到这么多人过了,重新读了下题目,才意识到是水题。

 

C. Divide Them All

题意:给你不超过1e5个图形,圆或者长方形。圆心或者长方形顶点,都是整点。问你是否存在一条直线,可以将每一个图形的面积平分。

观察:其实就是问所有图形的重心是否在一条直线上。圆的重心就是圆心,长方形重心是对角线的中点。这里不知道double会不会有精度问题,所以我就把坐标都乘了2。

code:

技术分享图片
 1 /*
 2  by skydog
 3  */
 4 #include <iostream>
 5 #include <cstdio>
 6 #include <vector>
 7 #include <utility>
 8 #include <algorithm>
 9 #include <cmath>
10 #include <cstring>
11 #include <map>
12 #include <set>
13 #include <stack>
14 #include <queue>
15 #include <deque>
16 #include <cassert>
17 #include <list>
18 using namespace std;
19 typedef long long ll;
20 typedef pair<int, int> ii;
21 typedef pair<ll, ll> l4;
22 typedef unsigned long long ull;
23 #define mp make_pair
24 #define pb push_back
25 
26 
27 vector<ii> v;
28 ii get()
29 {
30     int t;
31     scanf("%d", &t);
32     if (t == 0)
33     {
34         ii ret;
35         
36         scanf("%*d %d %d", &ret.first, &ret.second);
37         ret.first *= 2;
38         ret.second *= 2;
39         return ret;
40     }
41     else
42     {
43         int x[4], y[4];
44         for (int i = 0; i < 4; ++i)
45             scanf("%d %d", x+i, y+i);
46         return mp(x[0]+x[2], y[0]+y[2]);
47     }
48 }
49 bool solve()
50 {
51     sort(v.begin(), v.end());
52     v.resize(unique(v.begin(), v.end())-v.begin());
53     if (v.size() <= 2)
54         return true;
55     for (int i = 2; i < v.size(); ++i)
56     {
57         int x1 = v[1].first-v[0].first, y1 = v[1].second-v[0].second;
58         int x2 = v[i].first-v[0].first, y2 = v[i].second-v[0].second;
59         if (x1*y2 != x2*y1)
60             return false;
61     }
62     return true;
63 }
64 int main()
65 {
66     int n;
67     scanf("%d", &n);
68     for (int i = 1; i <= n; ++i)
69     {
70         v.pb(get());
71     }
72     puts(solve()?"Yes":"No");
73 }
View Code

WA:忘记去重了,导致判断出错。看来blind提交还是有一定风险的。

 

D. Interview Task

题意:给了你一个递归式。f[1] = {1, 1}。f[i] = 在f[i-1]的基础上,将f[i-1]中任意两个相邻的元素的和插入两个相邻元素中间。f[2] = {1, 1+1, 1} = {1, 2, 1};f[3] = {1, 1+2, 2, 2+1, 1} = {1, 3, 2, 3, 2, 1}。给你一个正整数n (n <= 1e13),问你在f[n]中有几个n。

观察:推了一会没有推出来,决定打表找规律。看了半天,先发现对于一个prime p, ans[p] = p-1。好像有点意思,然后发现除了n=1的情况,ans[n] = phi(n)。

code:

技术分享图片
 1 /*
 2  by skydog
 3  */
 4 #include <iostream>
 5 #include <cstdio>
 6 #include <vector>
 7 #include <utility>
 8 #include <algorithm>
 9 #include <cmath>
10 #include <cstring>
11 #include <map>
12 #include <set>
13 #include <stack>
14 #include <queue>
15 #include <deque>
16 #include <cassert>
17 #include <list>
18 using namespace std;
19 typedef long long ll;
20 typedef pair<int, int> ii;
21 typedef pair<ll, ll> l4;
22 typedef unsigned long long ull;
23 #define mp make_pair
24 #define pb push_back
25 
26 ll solve(ll n)
27 {
28     if (n == 1)
29         return 2;
30     ll ret = n;
31     for (ll i = 2; i*i <= n; ++i)
32         if (n%i==0)
33         {
34             ret -= ret/i;
35             while (n%i == 0)
36                 n/=i;
37         }
38     if (n != 1)
39         ret -= ret/n;
40     return ret;
41 }
42 
43 int main()
44 {
45     ll n;
46     scanf("%lld", &n);
47     printf("%lld\n", solve(n));
48 }
View Code

WA:现场的时候应该比较有勇气的直接打表找规律,尤其是看到这么多人过的情况。事后诸葛亮的话,输入的n不超过1e13,可能暗示了要用sqrt(n)的方法来解。

回顾:看到CF上有人提示,数列其实是Stern-Brocot Tree的分母。比赛的时候有想到这个东西,但是对它不是特别了解,连名字都不记得(一年半以前usaco的某一道题用过)。如果了解这个知识点的话,答案就比较明了了。f[n]就是Stern-Brocot Tree第n层的分母序列,f[n]中n的个数就是[0, 1]内,以n为分母的最简分数的个数。n=1时有两个最简分数0/1 and 1/1。其他情况,以n为底的最简分数的个数就等于分子小于n且与n互质的个数,也就是phi(n)。

 

E. Backup

题意:给你一颗大小不超过1e5的有根树,还有最大负载量k(k >= 2)。起初每个点都有1个单位的负载。每一次操作你可以删一个点,如果这个点的parent还没被删掉,那么删除该点会使parent的负载加1。当某个点的负载到达k时,下一秒必须删除这个点。当根节点被删除时,停止操作。问你最多可以进行几次操作,根节点被删除的操作不算。

观察:观察过样例之后才想清楚可能的情况,对于一个节点cur,有三种处理方式:(0) 直接先把cur删掉,之后再删cur的孩子们。(1)最后删cur,在cur的孩子们中尽量删,删完k-2个后,最后再删一个孩子,刚好使得cur的负载达到k。注意,根节点必须是这种操作。(2)保证cur不被删除。

   下面我们考虑一下每种方案的最大操作数该怎么求,先令第i种方案的最大操作数dp[cur][i]。

  (0)可以想到,在这种方案下的最大操作数就是cur所在的子树大小,具体操作就是按照从cur开始的bfs序删点,即删掉cur,再删掉cur的孩子们,再删掉cur的孩子的孩子们,以此类推。dp[cur][0] = 1 + sum(dp[child of cur][0])。

  (1)一开始肯定是删k-2个孩子,然后再删一个孩子,剩下的孩子都不删。dp[cur][1] = 1 + max{sum(d[前k-2个孩子][0]) + d[最后删的孩子][1] + sum(d[没被删的孩子][2])}。

  (2)删掉k-2个孩子,剩下的孩子都不删。dp[cur][2] = max{sum(d[前k-2个孩子][0]) + sum(d[没被删的孩子][2])}

   最后的答案就是d[根节点][1] - 1。这里减1是因为题目要求根节点不算在答案中。

   比较容以看出, dp[cur][0]就是子树大小,比较好求。但对于其他两类方案,dp的值和选孩子的方案有关。我们先考虑dp[cur][2]的求法。我们要取k-2个孩子的dp[nxt][0]和剩下孩子的dp[nxt][2],是一个经典的排序贪心问题。即给n个元素,每个元素有a[i], b[i]两个值,问你取x个元素的a[i]和剩下n-x个元素的b[i],最大的和是多少。解决方法是将元素按照a[i]-b[i]的顺序从大到小排序,取前x元素的a[i],后n-x个元素的b[i]就好了。所以为了求dp[cur][2],我们只需要将cur所有孩子按照dp[nxt][0]-dp[nxt][1]排一下降序,取前k-2个孩子的dp[nxt][0]和剩下孩子的dp[nxt][2]就好。

   dp[cur][1]的求法也是类似,先将cur的孩子按照上述方法排序之后,枚举最后被删的孩子是哪一个,更新答案。这里可以先预处理出前缀和和后缀和,枚举被删除的孩子的时候可以快速更新答案。

code:

技术分享图片
 1 /*
 2  by skydog
 3  */
 4 #include <iostream>
 5 #include <cstdio>
 6 #include <vector>
 7 #include <utility>
 8 #include <algorithm>
 9 #include <cmath>
10 #include <cstring>
11 #include <map>
12 #include <set>
13 #include <stack>
14 #include <queue>
15 #include <deque>
16 #include <cassert>
17 #include <list>
18 using namespace std;
19 typedef long long ll;
20 typedef pair<int, int> ii;
21 typedef pair<ll, ll> l4;
22 typedef unsigned long long ull;
23 #define mp make_pair
24 #define pb push_back
25 
26 const int maxn = 1e5+1;
27 vector<int> g[maxn];
28 int n, k, p[maxn];
29 
30 int d[maxn][3];
31 /*
32  0 for first pick
33  1 for last pick
34  2 for not pick
35  */
36 
37 bool cmp(int a, int b)
38 {
39     return d[a][0]-d[a][2] > d[b][0]-d[b][2];
40 }
41 void dfs(int cur)
42 {
43     d[cur][0] = 1;
44     for (auto nxt : g[cur])
45     {
46         dfs(nxt);
47         d[cur][0] += d[nxt][0];
48     }
49     if (g[cur].size() < k-1)
50     {
51         d[cur][1] = d[cur][0];
52         d[cur][2] = d[cur][0]-1;
53     }
54     else
55     {
56         //2 for not pick
57         //1 for last pick
58         sort(g[cur].begin(), g[cur].end(), cmp);
59         int front = 0, back = 0;
60         for (int i = 0; i < k-1; ++i)
61             front += d[g[cur][i]][0];
62         for (int i = k-1; i < g[cur].size(); ++i)
63             back += d[g[cur][i]][2];
64         int ans = 0;
65         for (int i = 0; i < k-1; ++i)
66         {
67             int tmp = front+back-d[g[cur][i]][0]+d[g[cur][i]][1];
68             if (tmp > ans)
69                 ans = tmp;
70         }
71         front -= d[g[cur][k-2]][0];
72         back += d[g[cur][k-2]][2];
73         for (int i = k-1; i < g[cur].size(); ++i)
74         {
75             int tmp = front+back-d[g[cur][i]][2]+d[g[cur][i]][1];
76             if (tmp > ans)
77                 ans = tmp;
78         }
79         d[cur][1] = 1+ans;
80         d[cur][2] = front+back;
81     }
82 }
83 int main()
84 {
85     int T;
86     scanf("%d", &T);
87     for (int kase = 1; kase <= T; ++kase)
88     {
89         scanf("%d %d", &n, &k);
90         for (int i = 1; i <= n; ++i) g[i].clear();
91         for (int i = 1; i <= n-1; ++i)
92             scanf("%d", p+i), g[p[i]].pb(i);
93         dfs(n);
94         printf("%d\n", d[n][1]-1);
95         
96     }
97 }
View Code

 

F. Lying Processors

题意:在一个n*m的主板上,每个位置都有一个处理器,n<=7, m <= 100。处理器分两种,一定说实话的和一定说谎话的。对每一个处理其都问了一个相同的问题:你相邻的处理器中是不是既有说谎的也有说实话的?每一个处理器都回答了是。问你最少可能有多少个说谎的处理器。

猜测:看数据范围和问题模式,感觉会是个轮廓线dp。而且询问的种类不是很多,看似可以打表提交。

观察:本来想状态可能有很多种,但想清楚了之后,只用0,1代表说实话的和说谎话的就好了。

  首先有个独立于dp之外的观察,就是,所有人都说谎一定可行。而且对于一个说谎的人,他要么周围都是说实话的,即这个说谎者孤立;要么他周围都是说谎话的,以此类推可以推广到所有人都是说谎话的。全都是说谎话的人,答案是n*m,所以这就是答案的上限,之后我们只需要考虑说谎者孤立的情况。但是由于一开始想法不是很清晰,而且数据范围比较小,又存在本地打表的可能,接下来的写法并没有用到这一点。

  首先预处理m=1的情况:暴力枚举2^n种可能,检查状态是否合法,如果合法,更新答案。

  对于m >= 2的情况,暴力枚举前两行的所有可能,(2^n) * (2^n),之后维护过去的两行,进行状态转移。最后,再暴力枚举最后两行所有的可能,用合法的状态更新答案。

code:

技术分享图片
  1 /*
  2  by skydog
  3  */
  4 #include <iostream>
  5 #include <cstdio>
  6 #include <vector>
  7 #include <utility>
  8 #include <algorithm>
  9 #include <cmath>
 10 #include <cstring>
 11 #include <map>
 12 #include <set>
 13 #include <stack>
 14 #include <queue>
 15 #include <deque>
 16 #include <cassert>
 17 #include <list>
 18 using namespace std;
 19 typedef long long ll;
 20 typedef pair<int, int> ii;
 21 typedef pair<ll, ll> l4;
 22 typedef unsigned long long ull;
 23 #define mp make_pair
 24 #define pb push_back
 25 
 26 void print(int bit, int n)
 27 {
 28     for (int i = 0; i < n; ++i)
 29         putchar(0+((bit>>i)&1));
 30 }
 31 
 32 const int N = 7;
 33 const int maxs = 1<<N;
 34 int bit[maxs];
 35 
 36 const int maxm = 100;
 37 int d[maxm][N][maxs][maxs];
 38 int pre[maxm][N][maxs][maxs];
 39 int n, m;
 40 //0 for honest, 1 for liar
 41 bool check_single(int mask, int n)
 42 {
 43     for (int i = 0; i < n; ++i)
 44         if ((mask>>i)&1)
 45         {
 46             // for liar
 47             if (i-1 >= 0 && i+1 < n && ((mask>>i-1)&1)^((mask>>i+1)&1))
 48                 return false;
 49         }
 50         else
 51         {
 52             if (i == 0 || i == n-1 || ((mask>>i-1)&1)==((mask>>i+1)&1))
 53                 return false;
 54         }
 55     //print(mask, n);
 56     //cerr << " " << bit[mask] << "  work\n";
 57     return true;
 58 }
 59 int solve_m(int n)
 60 {
 61     int ans = n*2;
 62     for (int i = 0; i < (1<<n); ++i)
 63     {
 64         if (check_single(i, n) && bit[i] < ans)
 65             ans = bit[i];
 66     }
 67     assert(ans <= n);
 68     return ans;
 69 }
 70 bool check_first_two(int a, int b, int n)
 71 {
 72     for (int i = 0; i < n; ++i)
 73         if ((a>>i)&1)
 74         {
 75             //liar
 76             bool cnt[2]={0, 0};
 77             if (i-1>=0)
 78                 cnt[(a>>i-1)&1] = true;
 79             if (i+1<n)
 80                 cnt[(a>>i+1)&1] = true;
 81             cnt[(b>>i)&1] = true;
 82             if (cnt[1]&&cnt[0])
 83                 return false;
 84         }
 85         else
 86         {
 87             //liar
 88             bool cnt[2]={0, 0};
 89             if (i-1>=0)
 90                 cnt[(a>>i-1)&1] = true;
 91             if (i+1<n)
 92                 cnt[(a>>i+1)&1] = true;
 93             cnt[(b>>i)&1] = true;
 94             if (!cnt[1] || !cnt[0])
 95                 return false;
 96         }
 97     swap(a, b);
 98     for (int i = 0; i < n; ++i)
 99         if ((a>>i)&1)
100         {
101             //liar
102             bool cnt[2]={0, 0};
103             if (i-1>=0)
104                 cnt[(a>>i-1)&1] = true;
105             if (i+1<n)
106                 cnt[(a>>i+1)&1] = true;
107             cnt[(b>>i)&1] = true;
108             if (cnt[1]&&cnt[0])
109                 return false;
110         }
111     return true;
112 }
113 bool check_last_two(int a, int b, int n)
114 {
115     return check_first_two(b, a, n);
116 }
117 
118 void init_bit(int n)
119 {
120     for (int i = 1; i < (1<<n); ++i)
121         bit[i] = bit[i>>1] + (i&1);
122 }
123 void dfs(int tar, int dep, int n)
124 {
125     int old = tar;
126     if (dep > 1)
127     {
128         tar = pre[dep][0][tar%(1<<n)][tar>>n];
129         for (int i = n-1; i >= 1; --i)
130             tar = pre[dep-1][i][tar%(1<<n)][tar>>n];
131         dfs(tar, dep-1, n);
132     }
133     if (dep == 1)
134         print(tar%(1<<n), n), puts("");
135     print(old>>n, n);
136 
137     puts("");
138 }
139 int solve(int n, int m)
140 {
141     init_bit(n);
142     if (m == 1)
143         return solve_m(n);
144     //brute first two row
145     memset(d, 7, sizeof(d));
146     const int inf = n*m*2;
147     for (int i = 0; i < (1<<n); ++i)
148         for (int j = 0; j < (1<<n); ++j)
149             if (check_first_two(i, j, n))
150                 d[1][0][i][j] = bit[i]+bit[j];
151     //check middle
152     for (int r = 1; r < m-1; ++r)
153         for (int w = 0; w < n; ++w)
154         {
155             int nr = r, nw = w+1;
156             if (nw == n)
157                 nw = 0, ++nr;
158             for (int i = 0; i < (1<<n); ++i)
159                 for (int j = 0; j < (1<<n); ++j)
160                     if (d[r][w][i][j] < inf)
161                     {
162                         //consider first bit of j
163                         if (j&1)
164                         {
165                             //liar
166                             int pick = i&1;
167                             if (pick && w != 0 && ((j>>n-1)&1) != 1)
168                                 continue;
169                             int &nxt = d[nr][nw][(i>>1)|(1<<n-1)][(j>>1)|((i&1)<<n-1)];
170                             int &prev = pre[nr][nw][(i>>1)|(1<<n-1)][(j>>1)|((i&1)<<n-1)];
171                             if (nxt > d[r][w][i][j]+(i&1))
172                                 nxt = d[r][w][i][j]+(i&1), prev = (j<<n)|i;
173                         }
174                         else
175                         {
176                             bool cnt[2] = {0, 0};
177                             cnt[i&1] = true;
178                             if (w != n-1)
179                                 cnt[(j>>1)&1] = true;
180                             if (w != 0)
181                                 cnt[(i>>n-1)&1] = true;
182                             if (cnt[0])
183                             {
184                                 if (w != 0 && ((j>>n-1)&1) != 0)
185                                     goto haha;
186                                     
187                                 int &nxt = d[nr][nw][(i>>1)][(j>>1)|(1<<n-1)];
188                                 int &prev = pre[nr][nw][(i>>1)][(j>>1)|(1<<n-1)];
189                                 if (nxt > d[r][w][i][j]+1)
190                                     nxt = d[r][w][i][j]+1, prev = (j<<n)|i;
191                             }
192                             
193                         haha: if (cnt[1])
194                             {
195                                 int &nxt = d[nr][nw][(i>>1)][(j>>1)|(0<<n-1)];
196                                 int &prev = pre[nr][nw][(i>>1)][(j>>1)|(0<<n-1)];
197                                 if (nxt > d[r][w][i][j])
198                                     nxt = d[r][w][i][j], prev = (j<<n)|i;
199                             }
200                                 
201                         }
202                     }
203         }
204     //check last two row
205     int ans = n*m;
206     int tar = 0;
207     for (int i = 0; i < (1<<n); ++i)
208         for (int j = 0; j < (1<<n); ++j)
209             if (check_last_two(i, j, n) && d[m-1][0][i][j] < ans)
210             {
211                 ans = d[m-1][0][i][j];
212                 tar = (j<<n)|i;
213             }
214     //dfs(tar, m-1, n);
215     return ans;
216                 
217 }
218 void check()
219 {
220     for (int i = 1; i <= 7; ++i)
221         for (int j = 1; j <= 7; ++j)
222             assert(solve(i, j) == solve(j, i));
223 }
224 int main()
225 {
226    // check();
227     
228     while (~scanf("%d %d", &n, &m))
229         printf("%d\n", solve(n, m));
230 }
View Code

上面的代码写了很多重复的段落,而且有些地方写的复杂了,没有用到黑点孤立的性质。

不知道根据黑点孤立,可不可以推出一个封闭的式子计算答案。

以上是关于Yandex Algorithm 2018 Warmup的主要内容,如果未能解决你的问题,请参考以下文章

Yandex Algorithm 2017 Qualication Round (数组练习 + 拓扑排序练习)

Yandex.Algorithm 2011 A. Double Cola

yandex 邮件不使用 smtp 发送

如何在 android 中添加 Yandex 地图?

Yandex 地图套件 Android

JavaMail Yandex 发件人