2013暑假集训 第三场个人赛总结
7月20日,第三场个人赛。这场个人赛应该是最低谷了吧,整场比赛2y了一题以后就再也没出题了。
ID | Origin | Title | ||
---|---|---|---|---|
0 / 11 | Problem A | SPOJ SEGMENTS | A | |
1 / 94 | Problem B | SPOJ STRDIST | B | |
26 / 124 | Problem C | SGU 296 | C | |
2 / 17 | Problem D | CodeForces 3D | D | |
Problem E | SPOJ MUSIC | E | ||
1 / 35 | Problem F | CodeForces 185C | F |
这场比赛没什么好描述的。C题开始的时候贪心的思想错了,以为直接删除最小的数字就行了,结果搞了很久才搞到一组(98919 1)这样的数据,然后才知道是不停地删掉上升序列。然后我就整场比赛卡在A题那里了。原来A就是我这几天做的差分约束加上二分。做的是我还以为是DLX(Dancing Links)结果敲模板敲了好久,改模板也改了好久都改不出结果,然后就知道自己的模板坑了自己了。比赛中其实知道有几题比这题还要简单的,不过状态不是很好,于是就一直卡在A题,D的贪心完全没有想到。
好了,这场比赛我能做的题其实是ABCDE,赛后过了这几题。
A是差分约束加二分,二分结果,然后不停的更改构图的边权。做的时候将(i)->(i-1)=0的边写成i+1,debug了好久才找到这个问题,居然提交的时候还能过了不少数据。囧!
代码如下,时间780ms:
1 #include <cstdio> 2 #include <iostream> 3 #include <algorithm> 4 #include <cstring> 5 #include <map> 6 7 using namespace std; 8 9 const int N = 444; 10 const int INF = 0x11111111; 11 typedef long long LL; 12 struct Edge { 13 int s, t, c; 14 Edge() {} 15 Edge(int s, int t, int c) : s(s), t(t), c(c) {} 16 } edge[N << 2]; 17 int ec, rn, dis[N << 1]; 18 LL x[N], y[N], rx[N << 1]; 19 map<LL, int> xid; 20 21 bool bellman(int n, int e) { 22 for (int i = 0; i <= n; i++) dis[i] = INF; 23 dis[0] = 0; 24 bool ok; 25 for (int i = 0; i < n; i++) { 26 ok = true; 27 for (int j = 0; j < e; j++) { 28 if (dis[edge[j].t] > dis[edge[j].s] + edge[j].c) { 29 dis[edge[j].t] = dis[edge[j].s] + edge[j].c; 30 ok = false; 31 } 32 } 33 if (ok) return true; 34 } 35 return false; 36 } 37 38 int main() { 39 int n, v; 40 while (cin >> n) { 41 rn = 0; 42 xid.clear(); 43 for (int i = 0; i < n; i++) { 44 cin >> x[i] >> y[i] >> v; 45 x[i] <<= 1; 46 y[i] <<= 1; 47 y[i]--; 48 rx[rn++] = x[i]; 49 rx[rn++] = y[i]; 50 } 51 sort(rx, rx + rn); 52 rn = (int) (unique(rx, rx + rn) - rx); 53 ec = n; 54 for (int i = 0; i < rn; i++) { 55 xid[rx[i]] = i; 56 if (i) edge[ec++] = Edge(i, i - 1, 0); 57 } 58 for (int i = 0; i < n; i++) edge[ec++] = Edge(xid[y[i]], xid[x[i]], -1); 59 int h = 0, t = n + 1, ans = t; 60 while (h <= t) { 61 int m = h + t >> 1; 62 for (int i = 0; i < n; i++) edge[i] = Edge(xid[x[i]], xid[y[i]], m); 63 if (bellman(rn, ec)) ans = m, t = m - 1; 64 else h = m + 1; 65 } 66 cout << ans << endl; 67 } 68 return 0; 69 }
B是一个dp的优化,题目其实敲代码并不难,就是要比较细心才能看到这样的一个细节,最终答案不超过100。这个意味着每一层dp的范围不超过前后100个元素。我们可以从下面的sample得出来的dp矩阵可以看出来。
实现的时候要用滚动数组,这样可以防止MLE。
实现的代码如下,注释部分是用map来做的,不过超时了。用数组直接hash存放,9s左右通过:
1 #include <cstdio> 2 #include <iostream> 3 #include <algorithm> 4 #include <cstring> 5 #include <map> 6 7 using namespace std; 8 9 const int N = 111111; 10 const int INF = 0x11111111; 11 const int DIF = 111; 12 const int SZ = 1 << 8; 13 char A[N], B[N]; 14 //map<int, int> dp[4]; 15 int dp[1 << 2][SZ]; 16 17 int main() { 18 // freopen("in", "r", stdin); 19 int n, m; 20 while (cin >> n >> m) { 21 // for (int i = 0; i < 4; i++) dp[i].clear(); 22 memset(dp, 0, sizeof(dp)); 23 cin >> A + 1 >> B + 1; 24 dp[0][0] = 0; 25 for (int i = 0; i <= n; i++) { 26 int ti = i & 3; 27 // dp[ti].clear(); 28 for (int s = max(0, i - DIF), t = min(m, i + DIF), j = s; j <= t; j++) { 29 if (i || j) dp[ti][j & SZ - 1] = INF; 30 if (i > 0) dp[ti][j & SZ - 1] = min(dp[ti][j & SZ - 1], dp[(i - 1) & 3][j & SZ - 1] + 1); 31 if (j > s) dp[ti][j & SZ - 1] = min(dp[ti][j & SZ - 1], dp[ti][j - 1 & SZ - 1] + 1); 32 if (i > 0 && j > 0) { 33 if (A[i] == B[j]) dp[ti][j & SZ - 1] = min(dp[ti][j & SZ - 1], dp[(i - 1) & 3][j - 1 & SZ - 1]); 34 else dp[ti][j & SZ - 1] = min(dp[ti][j & SZ - 1], dp[(i - 1) & 3][j - 1 & SZ - 1] + 1); 35 } 36 if (i > 1 && j > 1 && A[i] == B[j - 1] && A[i - 1] == B[j]) dp[ti][j & SZ - 1] = min(dp[ti][j & SZ - 1], dp[(i - 2) & 3][j - 2 & SZ - 1] + 1); 37 } 38 } 39 cout << dp[n & 3][m & SZ - 1] << endl; 40 } 41 return 0; 42 }
开始的时候没有想到上面这样的一个方法,于是就搞了如下的滚动数组:
1 #include <cstdio> 2 #include <iostream> 3 #include <algorithm> 4 #include <cstring> 5 6 using namespace std; 7 8 const int N = 111111; 9 const int INF = 0x11111111; 10 const int MID = 222; 11 char A[N], B[N]; 12 int dp[1 << 3][MID + 5 << 1]; 13 14 int main() { 15 int n, m; 16 while (cin >> n >> m) { 17 memset(dp, 0, sizeof(dp)); 18 cin >> A + 1 >> B + 1; 19 dp[0][MID] = 0; 20 for (int i = 0; i <= n; i++) { 21 for (int s = max(0, i - MID), t = min(m, i + MID), j = s; j <= t; j++) { 22 int ti = i & 7, tj = j - i + MID + 5; 23 if (i || j) dp[ti][tj] = INF; 24 if (i > 0) dp[ti][tj] = min(dp[ti][tj], dp[(i - 1) & 7][tj + 1] + 1); 25 if (j > 0) dp[ti][tj] = min(dp[ti][tj], dp[ti][tj - 1] + 1); 26 if (i > 0 && j > 0) { 27 if (A[i] == B[j]) dp[ti][tj] = min(dp[ti][tj], dp[(i - 1) & 7][tj]); 28 else dp[ti][tj] = min(dp[ti][tj], dp[(i - 1) & 7][tj] + 1); 29 } 30 if (i > 1 && j > 1 && A[i] == B[j - 1] && A[i - 1] == B[j]) dp[ti][tj] = min(dp[ti][tj], dp[(i - 2) & 7][tj] + 1); 31 } 32 } 33 cout << dp[n & 7][MID + 5 - n + m] << endl; 34 } 35 return 0; 36 }
C题就是那题数字的贪心,我的做法跟找最大上升序列差不多:
1 #include <cstdio> 2 #include <iostream> 3 #include <algorithm> 4 #include <cstring> 5 6 using namespace std; 7 8 const int N = 1111; 9 char buf[N]; 10 bool vis[N]; 11 int main() { 12 int k; 13 while (cin >> buf >> k) { 14 int n = strlen(buf), mk = 0, t; 15 k = n - k; 16 memset(vis, 0, sizeof(vis)); 17 while (k > 0) { 18 t = mk; 19 for (int i = mk; i <= n - k; i++) { 20 if (buf[t] < buf[i]) t = i; 21 } 22 vis[t] = true; 23 mk = t + 1; 24 k--; 25 } 26 for (int i = 0; i < mk; i++) if (vis[i]) putchar(buf[i]); 27 puts(""); 28 } 29 return 0; 30 }
D题是括号匹配的贪心,做法是用一个优先队列存放A和B的差值,我们每次扫到问号的时候就直接讲它变成')'右括号,因为对于一个右括号过多的序列,可以任意挑选一个A和B的差最小的右括号来换成左括号,之前合法的状态依然继续合法。不合法的,例如前面存在固定的右括号过多,就可以直接调出循环了。
代码如下:
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 #include <queue> 6 7 using namespace std; 8 9 typedef long long LL; 10 typedef pair<int, int> PII; 11 const int N = 55555; 12 char str[N]; 13 priority_queue<PII> Q; 14 15 int main() { 16 int a, b; 17 while (~scanf("%s", str)) { 18 while (!Q.empty()) Q.pop(); 19 char *p = str; 20 int lf = 0; 21 LL sum = 0; 22 bool noans = false; 23 while (*p) { 24 if (*p == '(') lf++; 25 if (*p == ')') lf--; 26 if (*p == '?') { 27 scanf("%d%d", &a, &b); 28 if (noans) { p++; continue;} 29 *p = ')'; 30 lf--; 31 sum += b; 32 Q.push(PII(b - a, p - str)); 33 } 34 while (!Q.empty() && lf < 0) { 35 PII t = Q.top(); 36 sum -= t.first; 37 *(str + t.second) = '('; 38 lf += 2; 39 Q.pop(); 40 } 41 if (lf < 0) noans = true; 42 p++; 43 } 44 if (noans || lf) puts("-1"); 45 else { 46 cout << sum << endl; 47 cout << str << endl; 48 } 49 } 50 return 0; 51 }
然后就是E了,E的做法,首先是要看得出每次的修改都是将一段的和S变成了-S,这时候就可以想到求前缀和的操作了。求出前缀和以后,就是一个最小交换次数的题了,计算最少要多少次交换才能对数组排好序。因为每次修改的操作都是将两个前缀和交换而已,所以就能直接就散最小交换次数。
代码如下:
1 #include <cstdio> 2 #include <iostream> 3 #include <algorithm> 4 #include <cstring> 5 6 using namespace std; 7 8 const int N = 111111; 9 int rec[N], sum[N], p[N]; 10 bool vis[N]; 11 12 bool cmp(int a, int b) { return sum[a] < sum[b];} 13 14 int main() { 15 //freopen("in", "r", stdin); 16 int n; 17 while (~scanf("%d", &n)) { 18 sum[0] = 0; 19 bool ok = true; 20 for (int i = 1; i <= n; i++) { 21 scanf("%d", rec + i); 22 sum[i] = sum[i - 1] + rec[i]; 23 p[i] = i; 24 if (sum[i] <= 0) ok = false; 25 } 26 sort(p + 1, p + n + 1, cmp); 27 memset(vis, 0, sizeof(vis)); 28 int ans = n; 29 for (int i = 1; i <= n; i++) { 30 //cout << p[i] << endl; 31 if (sum[p[i - 1]] == sum[p[i]]) ok = false; 32 if (!vis[i]) { 33 int t = i; 34 while (!vis[t]) vis[t] = true, t = p[t]; 35 ans--; 36 } 37 //for (int i = 1; i <= n; i++) cout << vis[i] << ' '; cout << endl; 38 } 39 if (ok) printf("%d\n", ans); 40 else puts("-1"); 41 } 42 return 0; 43 }
最后还有一题F,这个真不会了。以后研究懂了dp的时候再说吧。
——written by Lyon