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 }
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 }
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 }
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 }
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 }
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 }
上面的代码写了很多重复的段落,而且有些地方写的复杂了,没有用到黑点孤立的性质。
不知道根据黑点孤立,可不可以推出一个封闭的式子计算答案。