每日一练
1.16
Problem A Simple Molecules
题意:给一个三元分子各原子的共价键数,问能否构造出符合题意的分子。
简析:设三个原子共价键数分别为a,b,c,且原子1与2,2与3,3与1,之间相连的键数为x,y,z,
得到线性方程组
$$ left{
egin{aligned}
a = z + x \
b = x + y\
c = y + z
end{aligned}
ight.
$$
容易解得
$$ left{
egin{aligned}
x = frac{a + b - c}{2} \
y = frac{b + c - a}{2}\
z = frac{c + a - b}{2}
end{aligned}
ight.
$$
只要解得x,y,z都是非负整数即可,否则无解。

#include<iostream> #include<cstdio> using namespace std; int main() { int a, b, c; scanf("%d %d %d", &a, &b, &c); int x = a + b - c, y = b + c - a, z = c + a - b; if(x < 0 || y < 0|| z < 0 || x % 2 || y % 2 || z % 2) puts("Impossible"); else printf("%d %d %d ", x / 2 , y / 2 , z / 2 ); return 0; }
Problem B Rational Resistance
题意:给无限多个单位电阻,每次只能串联或者并联一个单位电阻,问最少要几个电阻才能构造出阻值为a/b的电阻。
简析:先反过来考虑,假设一个电阻的阻值为$frac{x}{y}$,串联一个单位电阻,其阻值变化为$frac{x + y}{y} > 1$.
如果并联一个单位电阻,其阻值变化为$frac{x}{x + y} < 1$.
现在回到原来的问题,我们只要判断a与b的大小就能知道这个电阻是串联了一个单位电阻还是并联了一个单位电阻。
如果$a > b$,那么是由$frac{a - b}{b}$与1串联得到,如果$a < b$,那么是由$frac{a}{b - a}$与1并联得到。
只要重复这个过程直到a与b有一个为0结束。
需要注意的是,纯粹的模拟这个过程是会TLE的,例如a = 1e18, b = 1, 显然不能重复 a -= b, ans++; 1e18次。
正确的做法是 ans += a / b, a %= b; 于是变成了辗转相除的过程,复杂度为$O(logN)$。

#include <iostream> using namespace std; typedef long long LL; LL gcd(LL a, LL b) {return ( a % b ? gcd(b, a % b) : 0 ) + a / b;} int main() { LL a, b; cin >> a >> b; cout << gcd(a, b) << endl; return 0; }
1.17
Problem A Bear and Elections
题意:给出 n 个数 a[i],求使得 a[1] 为最大的数时候,需要从别的数给多少给 a[1]
简析:如果a[1] 已结是当前最大值的时候,是 0
如果a[1]不是当前最大值的时候,维护一个当前的最大值,每次从最大值给 1 个给 a[1],直到 a[1] 为最大值

1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #include<queue> 6 using namespace std; 7 8 int n; 9 int a[1005]; 10 11 int main(){ 12 while(scanf("%d",&n) != EOF){ 13 priority_queue<int> q; 14 for(int i = 1;i <= n;i++) { 15 scanf("%d",&a[i]);; 16 if(i != 1) q.push(a[i]); 17 } 18 19 int ans = 0; 20 while(1){ 21 int x = q.top();q.pop(); 22 // printf("x = %d a[1] = %d ",x,a[1]); 23 if(a[1] > x ) break; 24 x--; 25 // printf("---x = %d ",x); 26 a[1]++; 27 q.push(x); 28 ans++; 29 } 30 printf("%d ",ans); 31 } 32 return 0; 33 }
Problem B Bear and Poker
题意:给出 n 个数 a[i] ,每次操作可以给每个数乘以 2 或者乘以 3,每个数可以操作任意次,问这 n 个数最后是否可能相等
简析:假设最后每个数都相等,可以表示为 b[i] = 2^x * 3^y * k
所以把每个数除2,除3,除完之后,如果都是 k ,就是yes

1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #include<vector> 6 using namespace std; 7 8 const int maxn = 100005; 9 int n,g; 10 int a[maxn]; 11 12 int gcd(int a, int b){ 13 return (!b) ? a : gcd(b, a % b); 14 } 15 16 int ok(int x){ 17 x = x/g; 18 while(x){ 19 if(x % 3 == 0) x = x/3; 20 if(x % 2 == 0) x = x/2; 21 if(x == 1) return 1; 22 if(x%2 != 0 && x%3 != 0) return 0; 23 } 24 return 1; 25 } 26 27 void solve(){ 28 g = gcd(a[1],a[2]); 29 30 for(int i = 3;i <= n;i++){ 31 g = gcd(a[i],g); 32 // printf("i = %d g = %d ",i,g); 33 } 34 35 for(int i = 1;i <= n;i++){ 36 if(!ok(a[i])) { 37 puts("No"); 38 return; 39 } 40 } 41 puts("Yes"); 42 } 43 44 int main(){ 45 while(scanf("%d",&n) != EOF){ 46 for(int i = 1;i <= n;i++) scanf("%d",&a[i]); 47 solve(); 48 } 49 return 0; 50 }
1.18
Problem A Modulo Sum
题意:给一个n元数列和一个数m,问是否存在子序列使得该子序列和模m余0。
简析:请先看下面一个命题的证明,来自《离散数学》教材。
命题:对于一个n元数列,则必存在一个区间和模n余0。
证明:记前缀和对n取模后为sum[1...n],分两类讨论。
①存在一个sum[i] = 0,结论显然成立。
②任意sum[i] > 0,由鸽巢原理(小学生抽屉原理),必存在i,j(不妨设i < j)使得sum[i] = sum[j]。
则由i + 1, i + 2, ... , j - 1, j 构成的区间和模n余0.
证毕。
所以本题的关键在于当n > m时,答案必然是YES。
对于n < m的情况,我们可以用一个数组对于已经出现的余数进行标记。
每读取一个新的数x,对于每一个已标记的y,给(x + y)% m也打上标记,同时勿忘 x % m也要标记。
最后检查0是否被标记,被标记则输出YES,否则为NO。
这样的复杂度是$O(m^{2})$的。
对于上面这个命题不了解的人,可能很难想出正解,如果不管n是否大于m,全部使用上述的标记法的话,复杂度是$O(nm)$的,会超时。
但是如果经常扶老奶奶过马路的话,并且在每次循环中检查0是否被标记并及时跳出,表面看起来是$O(nm)$,实际循环不会超过m次就会跳出,还是$O(m^{2})$的。
请注意,参考代码中的break注释掉就会超时。

#include <iostream> #include <cstdio> using namespace std; int dp[1111], cpy[1111]; int main(void) { int n, m, x, ok = 0; scanf("%d%d", &n, &m); for(int i = 0; i < n; i++) { scanf("%d", &x); for(int j = 0; j < m; j++) cpy[j] = dp[j]; dp[x%=m] = 1; for(int j = 0; j < m; j++) if(cpy[j]) dp[(j+x)%m] = 1; if(dp[0]) {ok = 1;break;}// 该处break删去会TLE } puts(ok? "YES" : "NO"); return 0; }
Problem B Vasya and Petya's Game
题意:要区分[1,n]中的每一个数,对于选定的数字,每次只能询问该数是否被x整除,最少需要几次询问才能保证可以确认出这个数。
简析:首先考虑对于一个素数p,如何区分它的幂,例如2,4,8... 显然只能询问p的幂才能区分,所以素数的幂是必须要询问的。
下面证明仅询问素数的幂即可,由代数基本定理,
任意正整数$N = {p_{1}}^{a_{1}}cdot{p_{2}}^{a_{2}}cdot{p_{3}}^{a_{3}} ... {p_{k}}^{a_{k}}$
通过对于每个素数$p_{i}$询问它的幂,确认指数$a_{i}$后,即可确认该数。
所以答案即为范围内所有素数的幂,由于n只有1000,所以对素数和幂的处理方式也很随意了。

#include <iostream> #include <cstdio> #include <vector> using namespace std; vector<int> ans; int main(void) { int n; scanf("%d", &n); for(int i = 2; i <= n; i++) { int prime = 1; for(int j = 2; j * j <= i; j++) if(i % j == 0) prime = 0; if(!prime) continue; int cur = i; while(cur <= n) ans.push_back(cur), cur *= i; } printf("%d ", ans.size()); for(int i = 0; i < ans.size(); i++) printf("%d ", ans[i]); puts(""); return 0; }
1.19
Problem A Finding Team Member
题意:2n个人,给出每2人配对的得分,求每个人的最优配对。
简析:对所有配对的得分排序,从大到小贪心的将没有配对的人配对即可。(注意数组开的大小?

#include <iostream> #include <cstdio> #include <algorithm> using namespace std; int ans[888]; struct node { int i, j, v; node(int I = 0, int J = 0, int V = 0){i = I, j = J, v = V;} friend bool operator < (node A, node B){return A.v > B.v;} }p[888888]; int main(void) { int n, x, cnt = 0; scanf("%d", &n); for(int i = 1; i <= 2 * n; i++) for(int j = 1; j < i; j++) scanf("%d", &x), p[cnt++] = node(i, j, x); sort(p, p + cnt); for(int k = 0; k < cnt; k++) { int i = p[k].i, j = p[k].j; if(!ans[i] && !ans[j]) ans[i] = j, ans[j] = i; } for(int i = 1; i <= 2 * n; i++) printf("%d ", ans[i]); puts(""); return 0; }
Problem B A Problem about Polyline
题意:求最小的x使得折线与点相交。
简析:为了防止叙述时字母混淆,将我们所要求的值改称为m,将点记为P(a,b)。
我们先将m增大到m = b。无非两种情况。
① P在直线x=y的左侧,即a < b,显然无论怎么改变m也无法相交,此时无解。
②P点处于折线的两个"山峰"之间所夹的区域,为了方便区分,我们将这些区域按1,2,3,...编号。
由平面几何关系,容易得到满足$k = frac{a + b}{2b}$的点P处于第$k$个区域。
同时知道$k$之后,我们可以推算出左边这条直线的方程为$ l : y = 2km - x$。
接下来我们只要继续增大m,左边的"山峰"向右移动,左边的直线l就会与点P相交了,将P反代入直线l即可得到答案$m = frac{a + b}{2k}$.

#include <iostream> #include <cstdio> using namespace std; int main(void) { int a, b; scanf("%d%d", &a, &b); if(a < b) puts("-1"); else printf("%.12lf ", 1.0 * (a + b) / ( (a + b) / (2 * b) * 2 )); return 0; }
1.20
Problem A Anton and currency you all know
题意:给一个奇数,要求交换两个数字使它成为偶数,并且值要尽可能大。
简析:设最右的数为d,从左到右找第一个比d小的偶数与之交换即可,若偶数都比d大则要选择最右边的那个偶数,除此之外无解

#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn = 1e5 + 10; char s[maxn]; int main(void) { scanf("%s", s); int l = strlen(s), p = -1, d = s[l-1] - '0'; for(int i = 0; s[i]; i++) { int x = s[i] - '0'; if(x % 2) continue; p = i; if(x < d) break; } if(p == -1) {puts("-1");return 0;} swap(s[p], s[l-1]); printf("%s ", s); return 0; }
Problem B Anya and Ghosts
题意:来了m个不知道什么鬼,要求每个鬼来的时候要点r根烛,每根烛只能点t秒,每秒只能点一根烛,
给出每个鬼来的时间,问最少要几根烛?
简析:因为数据很小,做法比较暴力了。首先无解当且仅当t < r。
t >= r 时,按照时间从早到晚,检查每个鬼来的时候烛有没有r根,没有的话补到r根即可。
补的时候贪心的从后往前补,因为点烛的时间越往后,蜡烛能照到后面的鬼就越多。

#include <iostream> #include <cstdio> using namespace std; int w[2333], l[2333]; int main(void) { int m, t, r, ans = 0; scanf("%d %d %d", &m, &t, &r); for(int i = 0; i < m; i++) scanf("%d", w + i); if(r > t) {puts("-1"); return 0;} for(int i = 0; i < m; i++) { int now = w[i] + 500, cnt = 0; for(int j = 0; j < t; j++) if(l[now-j]) cnt++; if(cnt >= r) continue; for(int j = 0; cnt - r; j++) if(!l[now-j]) l[now-j] = 1, cnt++, ans++; } printf("%d ", ans); return 0; }
1.21
Problem A Mr. Kitayuta's Colorful Graph
题意:n个点m条边的无向图,每条边有一个颜色,询问可以直接或者间接连接u,v两点的颜色总数。
简析:因为m,n都很小,可以每加一条边(假设该边连接a,b,且颜色为c)就枚举已经与a有c色边联通的点,假设为i,
再枚举与b有c色边联通的点,假设为j,如果i和j没有c色边联通,那么他们可以间接的由i-a-b-j构成c色边联通。
本题做法还是很多样的,如果觉得暴力不够优雅可以换种姿势,例如可以每次询问的时候去做一次dfs,或者用并查集维护联通的点等等。

#include <iostream> #include <cstdio> using namespace std; bool cl[111][111][111]; int ans[111][111]; int main(void) { int n, m; scanf("%d %d", &n, &m); for(int i = 0; i < m; i++) { int a, b, c; scanf("%d %d %d", &a, &b, &c); if(cl[c][a][b]) continue; cl[c][a][b] = cl[c][b][a] = 1, ans[a][b]++, ans[b][a]++; for(int j = 1; j <= n; j++) for(int k = 1; k <= n; k++) if( (a == j||cl[c][j][a]) && (b == k||cl[c][k][b]) && !cl[c][j][k]) cl[c][j][k] = cl[c][k][j] = 1, ans[j][k]++, ans[k][j]++; } int q; scanf("%d", &q); for(int i = 0; i < q; i++) { int u, v; scanf("%d %d", &u, &v); printf("%d ", ans[u][v]); } return 0; }
司老大的并查集。

#include <cstdio> #include <vector> #include <cstring> #include <algorithm> using namespace std; const int maxn = 100 + 10; int G[maxn][maxn]; int n, m; int p[maxn][maxn]; int findp(int color, int x) { return x == p[color][x] ? x : p[color][x] = findp(color, p[color][x]); } struct Edge { int u, v; Edge(int u=0, int v=0):u(u), v(v) {} }; vector<Edge> edges[maxn]; int main() { //freopen("in.txt", "r", stdin); int n, m, Mc = 0; scanf("%d%d", &n, &m); for(int i = 1; i <= m; ++i) for(int j = 1; j <= n; ++j) p[i][j] = j; for(int i = 0; i < m; ++i) { int a, b, c; scanf("%d%d%d", &a, &b, &c); Mc = max(Mc, c); int pa = findp(c, a); int pb = findp(c, b); if(pa != pb) { p[c][pa] = pb; } } int Q; scanf("%d", &Q); for(int i = 0; i < Q; ++i) { int u, v, cnt = 0; scanf("%d%d", &u, &v); for(int c = 1; c <= Mc; ++c) if(findp(c, u) == findp(c, v)) cnt++; printf("%d ", cnt); } return 0; } 参考代码
路人的dfs。

#include<iostream> #include<algorithm> #include<vector> #include<utility> #include<cstring> using namespace std; const int MAXN = 200; int n, m; vector<pair<int, int>> adj[MAXN]; bool vis[MAXN]; bool dfs(int v, int c, int des){ if (vis[v]) return false; if (v == des) return true; vis[v] = 1; for (pair<int, int> e:adj[v]) if (e.second == c) if (dfs(e.first, c, des)) return true; return false; } int main(){ cin >> n >> m; for (int i = 0; i < m; i++){ int a, b, c; cin >> a >> b >> c; a--, b--; adj[a].push_back({b, c}); adj[b].push_back({a, c}); } int q; cin >> q; while (q--){ int a, b; cin >> a >> b; a--, b--; int ans = 0; for (int i = 1; i <= m; i++){ memset(vis, 0, sizeof(vis)); if(dfs(a, i, b)) ans++; } cout << ans << endl; } return 0; }
Problem B Mr. Kitayuta, the Treasure Hunter
题意:30000个岛上有n个宝石,一个人从0开始向右跳,如果一次跳了l,下次只能跳l-1,l或者l+1,第一次跳d,问最多能拿几个宝石。
简析:基本上一看上去就是要往dp想的题目,由于每次跳的距离受上一次的影响,所以状态应该包含上一次跳的距离和现在所处的岛屿位置。
不妨用dp[i][j]表示上一次跳的距离是i,目前在第j个岛能拿到的宝石数目。
那么显然dp[i][j] = j岛的宝石数目 + max( dp[i-1][j+i-1], dp[i][j+i], dp[i+1][j+i+1])
dp的时候注意i不能减少到0,j不能超过30000.
但是这样显然是不行的,因为i的范围是0-30000,j的范围也是0-30000,dp数组的空间都不够。
本题的关键在于尽管i的范围是0-30000,但是对于每一个给定的d,i的变化范围不会超过[d-245,d+245].
简单证明一下:
考虑最糟糕的情况,第一次跳1,然后跳2,3,4,...,最多到245就超过了30000,
如果第一步跳的更远,离30000就更近了,i的变化范围一定比d+245更小,
反过来第一次跳245,然后跳244,243,...,最后一步不会小于d-245.
从而说明了i的区间范围差不多只要500就够了,空间和时间都可以满足。
事实上,245相当于是$frac{m(m+1)}{2}=30000$的近似解,所以总复杂度是$O({m}^{frac{3}{2}})$的。
既然如此,我们用dp[i-(d-245)][j]来表示原来的dp[i][j]就可以了。
备注:可能会想到用一个map来存dp值,然而CF的出题人丧心病狂的把所有复杂度多一个log的都卡掉了。

#include <iostream> #include <cstdio> #include <algorithm> using namespace std; int p[30005], dp[505][30005]; int main(void) { int n, d, x; scanf("%d %d", &n, &d); for(int i = 0; i < n; i++) scanf("%d", &x), p[x]++; int l = max(1, d - 250), r = d + 250; for(int i = 30000; i >= d; i--) { for(int j = l; j < r; j++) { if(j != 1 && i + j - 1 <= 30000) dp[j-l][i] = max(dp[j-l][i], dp[j-l-1][i+j-1]); if(i + j <= 30000) dp[j-l][i] = max(dp[j-l][i], dp[j-l][i+j]); if(i + j + 1 <= 30000) dp[j-l][i] = max(dp[j-l][i], dp[j-l+1][i+j+1]); dp[j-l][i] += p[i]; } } printf("%d ", dp[d-l][d]); return 0; }
1.22
Problem A Watching a movie
题意:看电影,可以一分钟一分钟看,也可以一次快进掉x分钟,要求不错过n个片段,最少要看多久。
简析:时间很小,可以直接按时间轴模拟看电影的过程,能快进则快进,否则一分钟一分钟看。

1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 int l[55], r[55]; 5 6 int main(void) 7 { 8 int n, x, cur = 1, ans = 0; 9 scanf("%d%d", &n, &x); 10 for(int i = 0; i < n; i++) scanf("%d %d", l + i, r + i); 11 for(int i = 0; i < n; i++) 12 { 13 while(cur + x <= l[i]) cur += x; 14 while(cur <= r[i]) cur++, ans++; 15 } 16 printf("%d ", ans); 17 return 0; 18 }
Problem B Lecture
题意:一个单词有两种表达,要求选用更短的输出。
简析:直接模拟即可,用string会方便一些。

1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 string a[3333], b[3333]; 5 6 int main(void) 7 { 8 int n, m; 9 scanf("%d %d", &n, &m); 10 for(int i = 0; i < m; i++) cin >> a[i] >> b[i]; 11 for(int i = 0; i < n; i++) 12 { 13 string s; 14 cin >> s; 15 for(int j = 0; j < m; j++) 16 if(a[j] == s) cout << (a[j].size() > b[j].size() ? b[j] : a[j]) << ' '; 17 } 18 return 0; 19 }
Problem C Crazy Town
题意:平面上有n条直线,给两个不在任意直线上的点,问从一点到另一点要跨越多少直线。
简析:如果能把题意理解成上述这个样子这个题就没难度了,判断点是否在直线两侧,将点代入直线,看得到的值是否异号。
备注:如果将两个值相乘判断是否小于0,会超过long long的范围。

1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 typedef long long LL; 5 6 int main(void) 7 { 8 LL x1, y1, x2, y2; 9 scanf("%I64d %I64d %I64d %I64d", &x1, &y1, &x2, &y2); 10 int n, ans = 0; 11 scanf("%d", &n); 12 for(int i = 0; i < n; i++) 13 { 14 LL a, b, c; 15 scanf("%I64d %I64d %I64d", &a, &b, &c); 16 if( (a * x1 + b * y1 + c > 0) != (a * x2 + b * y2 + c > 0) ) ans++; 17 } 18 printf("%d ", ans); 19 return 0; 20 }
专题一 STL
见紫书(STL是一个很有用的东西,所以大家务必要学会)
专题二 基础DP
保存当前系统数和每台的最低高度,每次扫一遍已有系统,如果有比它高的,就挑其中最低的用,没有则新增一台。

1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 const int INF = 233333; 5 int H[11111]; 6 7 int main(void) 8 { 9 int N, h; 10 while(~scanf("%d", &N)) 11 { 12 int cnt = 0; 13 for(int i = 0; i < N; i++) 14 { 15 int tmp = INF, pos; 16 scanf("%d", &h); 17 for(int j = 0; j < cnt; j++) 18 { 19 if(H[j] < h) continue; 20 if(tmp > H[j]) tmp = H[j], pos = j; 21 } 22 if(tmp == INF) H[cnt++] = h; 23 else H[pos] = h; 24 } 25 printf("%d ", cnt); 26 } 27 return 0; 28 }
用dp[i]表示以i结尾的连续和最大值,如果dp[i-1]是负数,dp[i] = a[i],否则dp[i] = dp[i-1] + a[i].
dp的同时顺便记录一下左端点即可。最后找出最大值即为答案。

1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 const int maxn = 1e5 + 10; 5 int M[maxn], l[maxn]; 6 7 int main(void) 8 { 9 int T; 10 scanf("%d", &T); 11 for(int kase = 1; kase <= T; kase++) 12 { 13 int N, x, tmp = -1111, pos; 14 scanf("%d", &N); 15 for(int i = 1; i <= N; i++) 16 { 17 scanf("%d", &x); 18 if(i == 1 || M[i-1] < 0) M[i] = x, l[i] = i; 19 else M[i] = M[i-1] + x, l[i] = l[i-1]; 20 if(M[i] > tmp) tmp = M[i], pos = i; 21 } 22 if(kase != 1) puts(""); 23 printf("Case %d: %d %d %d ", kase, tmp, l[pos], pos); 24 } 25 return 0; 26 }
Problem C HDU 1024 Max Sum Plus Plus
用dp[i][j]表示前j个元素取i段且包含第j个元素的最大值。
用一个辅助数组M[j]来保存dp[i-1][1]~dp[i-1][j]的最大值。
考虑到每次可以将a[j]连到前一段后面,或者中间隔开增加一段。
dp[i][j] = max( dp[i][j-1] + a[j], M[j-1] + a[j])

1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 using namespace std; 6 const int maxn = 1e5 + 10; 7 typedef long long LL; 8 LL s[maxn], dp[maxn], M[maxn]; 9 10 int main(void) 11 { 12 int m, n; 13 while(~scanf("%d %d", &m, &n)) 14 { 15 for(int i = 1; i <= n; i++) scanf("%I64d", s + i); 16 memset(dp, 0, sizeof(dp)); 17 memset(M, 0, sizeof(M)); 18 for(int k = 1; k <= m; k++) 19 { 20 for(int i = k; i <= n; i++) 21 { 22 if(i == k) dp[i] = dp[i-1] + s[i]; 23 else dp[i] = max(dp[i-1] + s[i], M[i-1] + s[i]); 24 } 25 for(int i = k; i <= n; i++) 26 { 27 if(i == k) M[i] = dp[i]; 28 else M[i] = max(M[i-1], dp[i]); 29 } 30 } 31 printf("%I64d ", M[n]); 32 } 33 return 0; 34 }
Problem D HDU 1069 Monkey and Banana
每个block三条边分别作为高当成三个block,按底边排序后dp[i]表示第i个block为底层的最高高度。
每次遍历底边比i小的block取最高的放在上面。

1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 using namespace std; 5 6 struct block 7 { 8 int x, y, h; 9 }b[111]; 10 11 bool cmp (block A, block B) 12 { 13 if(A.x != B.x) return A.x < B.x; 14 return A.y < B.y; 15 } 16 17 int main(void) 18 { 19 int n, kase = 0; 20 while(~scanf("%d", &n) && n) 21 { 22 int cnt = 0; 23 for(int i = 0; i < n; i++) 24 { 25 int x, y, z; 26 scanf("%d%d%d", &x, &y, &z); 27 b[cnt].h = x, b[cnt].x = min(y, z), b[cnt++].y = max(y, z); 28 b[cnt].h = y, b[cnt].x = min(x, z), b[cnt++].y = max(x, z); 29 b[cnt].h = z, b[cnt].x = min(x, y), b[cnt++].y = max(x, y); 30 } 31 sort(b, b + cnt, cmp); 32 int ans = 0; 33 for(int i = 0; i < cnt; i++) 34 { 35 int H = b[i].h; 36 for(int j = 0; j < i; j++) 37 { 38 if(b[j].x == b[i].x || b[j].y >= b[i].y) continue; 39 b[i].h = max(b[i].h, b[j].h + H); 40 } 41 ans = max(ans, b[i].h); 42 } 43 printf("Case %d: maximum height = %d ", ++kase, ans); 44 } 45 return 0; 46 }
Problem E HDU 1078 FatMouse and Cheese
题意略坑。老鼠只能跑直线,不能跑L形路线。记搜即可。

1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 using namespace std; 6 int G[111][111]; 7 int n, k, dp[111][111]; 8 9 int DP(int i, int j) 10 { 11 if(dp[i][j]) return dp[i][j]; 12 int tmp = 0; 13 for(int x = -k; x <= k; x++) 14 { 15 if(x == 0 || i + x < 0 || i + x >= n) continue; 16 if(G[i+x][j] <= G[i][j]) continue; 17 tmp = max(tmp, DP(i+x, j)); 18 } 19 for(int y = -k; y <= k; y++) 20 { 21 if(y == 0 || j + y < 0 || j + y >= n) continue; 22 if(G[i][j+y] <= G[i][j]) continue; 23 tmp = max(tmp, DP(i, j+y)); 24 } 25 return dp[i][j] = tmp + G[i][j]; 26 } 27 28 int main(void) 29 { 30 while(~scanf("%d %d", &n, &k) && n != -1) 31 { 32 memset(dp, 0, sizeof(dp)); 33 for(int i = 0; i < n; i++) 34 for(int j = 0; j < n; j++) 35 scanf("%d", &G[i][j]); 36 printf("%d ", DP(0, 0)); 37 } 38 return 0; 39 }
Problem F HDU 1160 FatMouse's Speed
排序求个LIS即可。n2就好。

1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 using namespace std; 5 6 struct node 7 { 8 int id, w, s; 9 int pre, x; 10 }m[1111]; 11 12 bool cmp(node A, node B) 13 { 14 return A.w < B.w; 15 } 16 17 void ans_print(int pos) 18 { 19 if(m[pos].pre != pos) ans_print(m[pos].pre); 20 printf("%d ", m[pos].id); 21 } 22 23 int main(void) 24 { 25 int cnt = 0, M = 0, pos; 26 while(~scanf("%d%d", &m[cnt].w, &m[cnt].s)) cnt++; 27 for(int i = 0; i < cnt; i++) m[i].id = i + 1; 28 sort(m, m + cnt, cmp); 29 for(int i = 0; i < cnt; i++) 30 { 31 m[i].x = 1, m[i].pre = i; 32 for(int j = 0; j < i; j++) 33 { 34 if(m[i].w == m[j].w || m[i].s >= m[j].s) continue; 35 if(m[j].x >= m[i].x) m[i].x = m[j].x + 1, m[i].pre = j; 36 } 37 if(m[i].x > M) M = m[i].x, pos = i; 38 } 39 printf("%d ", m[pos].x); 40 ans_print(pos); 41 return 0; 42 }
dp[i]表示前i个顾客买完票的时间,dp[i] = min(dp[i-1] + s[i], dp[i-2] + d[i])

1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 using namespace std; 5 int s[2222], d[2222], dp[2222]; 6 7 int main(void) 8 { 9 int N; 10 scanf("%d", &N); 11 while(N--) 12 { 13 int k; 14 scanf("%d", &k); 15 for(int i = 0; i < k; i++) scanf("%d", s + i); 16 for(int i = 1; i < k; i++) scanf("%d", d + i); 17 dp[0] = s[0], dp[1] = min(d[1], dp[0] + s[1]); 18 for(int i = 2; i < k; i++) dp[i] = min(dp[i-1] + s[i], dp[i-2] + d[i]); 19 int h = 8, m = 0, sec = dp[k-1]; 20 m += sec / 60, sec %= 60; 21 h += m / 60, m %= 60; 22 printf("%02d:%02d:%02d ", h % 12, m, sec); 23 puts( h > 11 ? "pm" : "am"); 24 } 25 return 0; 26 }
首先要知道一定是重量相邻的两件配对,否则必有更优。
所以先sort一下,然后定义dp[i][j]为前i件凑j对的最优解。
dp[i][j] = min(dp[i-1][j], dp[i-2][j-1] + (a[j] - a[j-1])^2)

1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 using namespace std; 5 typedef long long LL; 6 LL a[2222], dp[2222][1111]; 7 8 int main(void) 9 { 10 int n, k; 11 while(~scanf("%d%d", &n, &k)) 12 { 13 for(int i = 1; i <= n; i++) scanf("%d", a + i); 14 sort(a + 1, a + n + 1); 15 for(int i = 1; i <= k; i++) 16 { 17 dp[2*i][i] = dp[2*(i-1)][i-1] + (a[2*i-1] - a[2*i]) * (a[2*i-1] - a[2*i]); 18 for(int j = 2 * i + 1; j <= n; j++) 19 dp[j][i] = min(dp[j-1][i], dp[j-2][i-1] + (a[j] - a[j-1]) * (a[j] - a[j-1])); 20 } 21 printf("%I64d ", dp[n][k]); 22 } 23 return 0; 24 }
O(n^3)O(n^3)的做法。用l表示一个位置往上和往右延伸,字符相同的最大长度,
dp[i][j]表示以(i, j)为左下角的对称矩阵最大边长,dp[i][j] = min(dp[i-1][j+1] + 1, l)

1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 using namespace std; 5 char G[1111][1111]; 6 int dp[1111][1111]; 7 8 int main(void) 9 { 10 int n; 11 while(~scanf("%d", &n) && n) 12 { 13 for(int i = 1; i <= n; i++) scanf("%s", G[i] + 1); 14 int ans = 1; 15 for(int i = 1; i <= n; i++) 16 { 17 for(int j = 1; j <= n; j++) 18 { 19 int l = 1; 20 while( i - l > 0 && j + l <= n && G[i-l][j] == G[i][j+l] ) l++; 21 dp[i][j] = min(l, dp[i-1][j+1] + 1); 22 ans = max(ans, dp[i][j]); 23 } 24 } 25 printf("%d ", ans); 26 } 27 return 0; 28 }
Problem J HDU 1158 Employment Planning
题目没给人数,其实很小。
dp[i][j]表示前i个月,最后一个月雇j个人花费。考虑雇人和裁人。

1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 const int INF = 2147483647; 5 int a[13], dp[13][222]; 6 7 int main(void) 8 { 9 int n; 10 while(~scanf("%d", &n) && n) 11 { 12 int h, s, f; 13 scanf("%d%d%d", &h, &s, &f); 14 for(int i = 1; i <= n; i++) scanf("%d", a + i); 15 for(int j = a[1]; j <= 200; j++) dp[1][j] = (h + s) * j; 16 for(int i = 2; i <= n; i++) 17 { 18 for(int j = a[i]; j <= 200; j++) 19 { 20 dp[i][j] = INF; 21 for(int k = a[i-1]; k <= 200; k++) 22 { 23 if(j > k) dp[i][j] = min(dp[i][j], dp[i-1][k] + (j - k) * h + s * j); 24 else dp[i][j] = min(dp[i][j], dp[i-1][k] + (k - j) * f + s * j); 25 } 26 } 27 } 28 int ans = INF; 29 for(int i = a[n]; i <= 200; i++) ans = min(ans, dp[n][i]); 30 printf("%d ", ans); 31 } 32 return 0; 33 }