Codeforces Round #263 (Div. 2) 解题报告
Source: http://codeforces.com/contest/462
好像没什么好说的..
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 using namespace std; 5 6 int n; 7 int dx[] = {0, 0, 1, -1}, 8 dy[] = {1, -1, 0, 0}; 9 char map[110][110]; 10 int main() 11 { 12 scanf("%d", &n); getchar(); 13 for (int i = 0; i < n; i++) cin.getline(map[i], 110); 14 bool flag = true; 15 for (int i = 0; i < n && flag; i++) 16 for (int j = 0; j < n; j++){ 17 int x, y, cnt = 0; 18 for (int k = 0; k < 4; k++){ 19 x = i + dx[k], y = j + dy[k]; 20 if (x < 0 || y < 0 || x > n || y > n) continue; 21 if (map[x][y] == 'o') cnt++; 22 } 23 if (cnt % 2 != 0){ 24 flag = false; 25 break; 26 } 27 } 28 if (flag) printf("YES\n"); 29 else printf("NO\n"); 30 return 0; 31 }
理解题意后就很简单地贪心
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 7 typedef long long LL; 8 char str[101000]; 9 int n, k, ct[30]; 10 int main() 11 { 12 memset(ct, 0, sizeof(ct)); 13 scanf("%d %d", &n, &k); getchar(); 14 gets(str); 15 for (int i = 0; i < n; i++) ct[str[i]-'A'] ++; 16 sort(ct, ct+26); 17 LL ans = 0; 18 for (int i = 25; i >= 0; i--){ 19 if (k >= ct[i]){ 20 ans = ans + (LL)ct[i] * ct[i]; 21 k -= ct[i]; 22 } 23 else{ 24 ans = ans + (LL)k * k; 25 break; 26 } 27 } 28 cout << ans << endl; 29 return 0; 30 }
题意:给你一个数的集合,重复两个操作:每次加上集合所有元素的和,如果元素个数大于一,划分为两个集合。问最大分数。
分析:可以倒着考虑,就变成了n个点作为叶子节点,形成一棵二叉树,非叶子节点是两个儿子的和。一种树对应一种答案分数。比较显然的贪心思路就是让最小的元素单独分为一个集合,其余一个集合,这样能让大的数尽量地被多取。但证明不会。。或是可以按题解的思路理解,所有元素乘以-1,所求答案为最小值的相反数,也就是求哈夫曼树。原本哈夫曼树的每次取两个最小元素合并,由于元素为负,所以现在合并完会变为最小元素,再和当前第二小元素合并,也就是同上面一样的做法。算一下每个元素会被取几次就可以了。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<iostream> 5 using namespace std; 6 7 typedef long long LL; 8 int n; 9 int a[300100]; 10 int main() 11 { 12 scanf("%d", &n); 13 for (int i = 0; i < n; i++) scanf("%d", a+i); 14 sort(a, a+n); 15 LL ans = 0; 16 if (n == 1) ans = a[0]; 17 else if (n == 2) ans = (LL)(a[0] + a[1]) * 2ll; 18 else{ 19 ans = (LL)(a[n-1] + a[n-2]) * (LL)n; 20 for (int i = n-3, j = n-1; i >= 0; i--, j--){ 21 ans = ans + (LL)a[i] * j; 22 } 23 } 24 cout << ans << endl; 25 return 0; 26 }
461B Appleman and Tree
题意:n个点的树,每个点有黑或白色。对于一颗树,删掉k条边就会形成k+1个子树。现在要删掉一些边,形成许多子树组成的森林,使得每个子树有且只有一个黑点,问有多少种这样的方案。
分析:树形dp。dp[v][0]表示对v为根的子树进行删边,使得新的以v为根的子树,没有黑点,而原本以v为父亲的节点作为根形成的新子树,都是恰有一个黑点的子树,的方案数。dp[v][1]类似前面,不同在于表示v为根恰有一个黑点的方案数。最后答案即dp[root][1]。转移大概是这样:首先dp[v][1] = isblack[v], dp[v][0] = 1 - dp[v][1]。接着对于v的儿子u,u如果不跟着v,dp[v][1] = dp[v][1] * dp[u][1], dp[v][0] = dp[v][0] * dp[u][1], u若跟着v,有dp[v][1] = dp[v][1] * dp[u][0] + dp[v][0] * dp[u][1], dp[v][0] = dp[v][0] * dp[u][0]。
顺带一提,看了下status里面的代码,发现一种不用双向存边方式,因为这道题给的是pi,表示i和pi之间有边,那么我们可以认为pi是i的父亲,就可以这样存边,给每个pi做个儿子的链表,next[i] = son[pi], son[pi] = i,然后就可以通过v=son[u]进入,v=next[v]遍历访问u的所有儿子v。
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 using namespace std; 5 6 const long long mo = 1000000007; 7 const int maxn = 100000; 8 struct edge{ 9 int n, v; 10 } e[200100]; 11 int n, esize = 0; 12 int en[maxn+10]; 13 int b[maxn+10]; 14 long long dp[maxn+10][2]; 15 void addedge(int u, int v){ 16 e[esize].v = v; 17 e[esize].n = en[u]; 18 en[u] = esize++; 19 } 20 void dfs(int x, int fa) 21 { 22 dp[x][0] = 1 - b[x]; 23 dp[x][1] = b[x]; 24 long long dpx0, dpx1; 25 for (int t = en[x]; t != -1; t = e[t].n){ 26 int v = e[t].v; 27 if (v == fa) continue; 28 dpx0 = dp[x][0], dpx1 = dp[x][1]; 29 dp[x][0] = dp[x][1] = 0; 30 dfs(v, x); 31 dp[x][1] = (dp[x][1] + dp[v][0] * dpx1) % mo; 32 dp[x][1] = (dp[x][1] + dp[v][1] * dpx0) % mo; 33 dp[x][1] = (dp[x][1] + dp[v][1] * dpx1) % mo; 34 dp[x][0] = (dp[x][0] + dp[v][1] * dpx0) % mo; 35 dp[x][0] = (dp[x][0] + dp[v][0] * dpx0) % mo; 36 } 37 } 38 int main() 39 { 40 scanf("%d", &n); 41 int x; 42 memset(en, -1, sizeof(en)); 43 for (int i = 1; i < n; i++){ 44 scanf("%d", &x); 45 addedge(i, x); 46 addedge(x, i); 47 } 48 for (int i = 0; i < n; i++) scanf("%d", b+i); 49 dfs(0, -1); 50 cout << dp[0][1] << endl; 51 return 0; 52 }
461C Appleman and a Sheet of Paper
题意:1*10^5的纸,10^5次操作,一种是把当前左侧开始的p个单元的位置翻折到右边,一种是查询当前左侧开始数,l到r的范围内有多少单元长度的纸。
分析:比较机智的题目。。实际上就是对于一个动区间做操作,翻折是把对称点p的一端的一段长度的值加给另一端,查询就是求区间和。给的下标是当前纸的下标,但我们可以记录当前纸的左右端的位置,从而可以维护静态区间(处理了超过右端的问题后,静态区间长度为n)。可以注意到区间长度是不断减小的,一个单位长度只会加给其他地方一次,也就是暴力修改总共也就o(n)(用上树的数据结构是o(nlogn))。为了回答区间和的询问,使用BIT或者SegmentTree。会遇上一个问题就是如果左折得太多,超过了右端,端点就超过n了,这里我们可以转化为翻折右端,然后整个纸面是翻转的(画个图就明白了),搞明白这些就可以写了,麻烦的地方就是下标的细节。。我是没太好的方法,把自己搞晕了,不过还好A了=。=
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 6 const int maxn = (int)1e5+10; 7 int n, q, x, y, command, L, R; 8 int sum[maxn+100]; 9 int lowbit(int x){ 10 return x & -x; 11 } 12 void update(int x, int val){ 13 for (int i = x; i < maxn; i += lowbit(i)) 14 sum[i] += val; 15 } 16 int query(int x){ 17 int ret = 0; 18 for (int i = x; i > 0; i -= lowbit(i)) 19 ret += sum[i]; 20 return ret; 21 } 22 int main() 23 { 24 scanf("%d %d", &n, &q); 25 L = 1, R = n; 26 memset(sum, 0, sizeof(sum)); 27 for (int i = 1; i <= n; i++) update(i, 1); 28 for (int i = 0; i < q; i++){ 29 scanf("%d", &command); 30 if (command == 1){ 31 scanf("%d", &x); 32 int NewL, NewR; 33 if (L < R){ 34 if (x * 2 > R - L + 1){ 35 NewL = L + x - 1, NewR = L; 36 for (int i = NewL+1, j = NewL; i <= R; i++, j--) 37 update(j, query(i)-query(i-1)); 38 L = NewL, R = NewR; 39 } 40 else{ 41 NewL = L + x; 42 for (int i = NewL-1, j = NewL; i >= L; i--, j++) 43 update(j, query(i)-query(i-1)); 44 L = NewL; 45 } 46 } 47 else{ 48 if (x * 2 > L - R + 1){ 49 NewL = L - x + 1, NewR = L; 50 for (int i = NewL-1, j = NewL; i >= R; i--, j++) 51 update(j, query(i)-query(i-1)); 52 L = NewL, R = NewR; 53 } 54 else{ 55 NewL = L - x; 56 for (int i = NewL+1, j = NewL; i <= L; i++, j--) 57 update(j, query(i)-query(i-1)); 58 L = NewL; 59 } 60 } 61 } 62 else{ 63 scanf("%d %d", &x, &y); 64 if (L < R){ 65 x = L + x - 1, y = L + y - 1; 66 printf("%d\n", query(y) - query(x)); 67 } 68 else{ 69 x = L - x, y = L - y; 70 printf("%d\n", query(x) - query(y)); 71 } 72 } 73 } 74 return 0; 75 }