每日一练
1.18
Problem A Simple Molecules
题意:给一个三元分子各原子的共价键数,问能否构造出符合题意的分子。
简析:设三个原子共价键数分别为a,b,c,且原子1与2,2与3,3与1,之间相连的键数为x,y,z,
得到线性方程组
$$ \left\{
\begin{aligned}
a = z + x \\
b = x + y\\
c = y + z
\end{aligned}
\right.
$$
容易解得
$$ \left\{
\begin{aligned}
x = \frac{a + b - c}{2} \\
y = \frac{b + c - a}{2}\\
z = \frac{c + a - b}{2}
\end{aligned}
\right.
$$
只要解得x,y,z都是非负整数即可,否则无解。
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 int main() 5 { 6 int a, b, c; 7 scanf("%d %d %d", &a, &b, &c); 8 int x = a + b - c, y = b + c - a, z = c + a - b; 9 if(x < 0 || y < 0|| z < 0 || x % 2 || y % 2 || z % 2) puts("Impossible"); 10 else printf("%d %d %d\n", x / 2 , y / 2 , z / 2 ); 11 return 0; 12 }
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)$。
1 #include <iostream> 2 using namespace std; 3 typedef long long LL; 4 LL gcd(LL a, LL b) {return ( a % b ? gcd(b, a % b) : 0 ) + a / b;} 5 int main() 6 { 7 LL a, b; 8 cin >> a >> b; 9 cout << gcd(a, b) << endl; 10 return 0; 11 }
备注:此题并不是第一次遇到,回想迎新杯热身赛B题切蛋糕,与此题并不是想法类似,而是代码都完全一样哦。要温故知新呐。
1.19
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\n",x,a[1]); 23 if(a[1] > x ) break; 24 x--; 25 // printf("---x = %d \n",x); 26 a[1]++; 27 q.push(x); 28 ans++; 29 } 30 printf("%d\n",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\n",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.20
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注释掉就会超时。
1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 int dp[1111], cpy[1111]; 5 6 int main(void) 7 { 8 int n, m, x, ok = 0; 9 scanf("%d%d", &n, &m); 10 for(int i = 0; i < n; i++) 11 { 12 scanf("%d", &x); 13 for(int j = 0; j < m; j++) cpy[j] = dp[j]; 14 dp[x%=m] = 1; 15 for(int j = 0; j < m; j++) if(cpy[j]) dp[(j+x)%m] = 1; 16 if(dp[0]) {ok = 1;break;}// 该处break删去会TLE 17 } 18 puts(ok? "YES" : "NO"); 19 return 0; 20 }
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,所以对素数和幂的处理方式也很随意了。
1 #include <iostream> 2 #include <cstdio> 3 #include <vector> 4 using namespace std; 5 vector<int> ans; 6 7 int main(void) 8 { 9 int n; 10 scanf("%d", &n); 11 for(int i = 2; i <= n; i++) 12 { 13 int prime = 1; 14 for(int j = 2; j * j <= i; j++) if(i % j == 0) prime = 0; 15 if(!prime) continue; 16 int cur = i; 17 while(cur <= n) ans.push_back(cur), cur *= i; 18 } 19 printf("%d\n", ans.size()); 20 for(int i = 0; i < ans.size(); i++) printf("%d ", ans[i]); 21 puts(""); 22 return 0; 23 }
1.21
Problem A Finding Team Member
题意:2n个人,给出每2人配对的得分,求每个人的最优配对。
简析:对所有配对的得分排序,从大到小贪心的将没有配对的人配对即可。(注意数组开的大小?
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 using namespace std; 5 int ans[888]; 6 7 struct node 8 { 9 int i, j, v; 10 node(int I = 0, int J = 0, int V = 0){i = I, j = J, v = V;} 11 friend bool operator < (node A, node B){return A.v > B.v;} 12 }p[888888]; 13 14 int main(void) 15 { 16 int n, x, cnt = 0; 17 scanf("%d", &n); 18 for(int i = 1; i <= 2 * n; i++) 19 for(int j = 1; j < i; j++) 20 scanf("%d", &x), p[cnt++] = node(i, j, x); 21 sort(p, p + cnt); 22 for(int k = 0; k < cnt; k++) 23 { 24 int i = p[k].i, j = p[k].j; 25 if(!ans[i] && !ans[j]) ans[i] = j, ans[j] = i; 26 } 27 for(int i = 1; i <= 2 * n; i++) printf("%d ", ans[i]); 28 puts(""); 29 return 0; 30 }
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}$.
1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 5 int main(void) 6 { 7 int a, b; 8 scanf("%d%d", &a, &b); 9 if(a < b) puts("-1"); 10 else printf("%.12lf\n", 1.0 * (a + b) / ( (a + b) / (2 * b) * 2 )); 11 return 0; 12 }
1.22
Problem A Anton and currency you all know
题意:给一个奇数,要求交换两个数字使它成为偶数,并且值要尽可能大。
简析:设最右的数为d,从左到右找第一个比d小的偶数与之交换即可,若偶数都比d大则要选择最右边的那个偶数,除此之外无解。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 using namespace std; 6 const int maxn = 1e5 + 10; 7 char s[maxn]; 8 9 int main(void) 10 { 11 scanf("%s", s); 12 int l = strlen(s), p = -1, d = s[l-1] - '0'; 13 for(int i = 0; s[i]; i++) 14 { 15 int x = s[i] - '0'; 16 if(x % 2) continue; 17 p = i; 18 if(x < d) break; 19 } 20 if(p == -1) {puts("-1");return 0;} 21 swap(s[p], s[l-1]); 22 printf("%s\n", s); 23 return 0; 24 }
Problem B Anya and Ghosts
题意:来了m个不知道什么鬼,要求每个鬼来的时候要点r根烛,每根烛只能点t秒,每秒只能点一根烛,
给出每个鬼来的时间,问最少要几根烛?
简析:因为数据很小,做法比较暴力了。首先无解当且仅当t < r。
t >= r 时,按照时间从早到晚,检查每个鬼来的时候烛有没有r根,没有的话补到r根即可。
补的时候贪心的从后往前补,因为点烛的时间越往后,蜡烛能照到后面的鬼就越多。
1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 int w[2333], l[2333]; 5 6 int main(void) 7 { 8 int m, t, r, ans = 0; 9 scanf("%d %d %d", &m, &t, &r); 10 for(int i = 0; i < m; i++) scanf("%d", w + i); 11 if(r > t) {puts("-1"); return 0;} 12 for(int i = 0; i < m; i++) 13 { 14 int now = w[i] + 500, cnt = 0; 15 for(int j = 0; j < t; j++) if(l[now-j]) cnt++; 16 if(cnt >= r) continue; 17 for(int j = 0; cnt - r; j++) if(!l[now-j]) l[now-j] = 1, cnt++, ans++; 18 } 19 printf("%d\n", ans); 20 return 0; 21 }
1.23
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,或者用并查集维护联通的点等等。
1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 bool cl[111][111][111]; 5 int ans[111][111]; 6 7 int main(void) 8 { 9 int n, m; 10 scanf("%d %d", &n, &m); 11 for(int i = 0; i < m; i++) 12 { 13 int a, b, c; 14 scanf("%d %d %d", &a, &b, &c); 15 if(cl[c][a][b]) continue; 16 cl[c][a][b] = cl[c][b][a] = 1, ans[a][b]++, ans[b][a]++; 17 for(int j = 1; j <= n; j++) 18 for(int k = 1; k <= n; k++) 19 if( (a == j||cl[c][j][a]) && (b == k||cl[c][k][b]) && !cl[c][j][k]) 20 cl[c][j][k] = cl[c][k][j] = 1, ans[j][k]++, ans[k][j]++; 21 } 22 int q; 23 scanf("%d", &q); 24 for(int i = 0; i < q; i++) 25 { 26 int u, v; 27 scanf("%d %d", &u, &v); 28 printf("%d\n", ans[u][v]); 29 } 30 return 0; 31 }
司老大的并查集。
1 #include <cstdio> 2 #include <vector> 3 #include <cstring> 4 #include <algorithm> 5 using namespace std; 6 7 const int maxn = 100 + 10; 8 9 int G[maxn][maxn]; 10 int n, m; 11 int p[maxn][maxn]; 12 int findp(int color, int x) { return x == p[color][x] ? x : p[color][x] = findp(color, p[color][x]); } 13 14 struct Edge 15 { 16 int u, v; 17 Edge(int u=0, int v=0):u(u), v(v) {} 18 }; 19 20 vector<Edge> edges[maxn]; 21 22 int main() 23 { 24 //freopen("in.txt", "r", stdin); 25 int n, m, Mc = 0; 26 scanf("%d%d", &n, &m); 27 28 for(int i = 1; i <= m; ++i) 29 for(int j = 1; j <= n; ++j) 30 p[i][j] = j; 31 32 33 for(int i = 0; i < m; ++i) 34 { 35 int a, b, c; 36 scanf("%d%d%d", &a, &b, &c); 37 Mc = max(Mc, c); 38 int pa = findp(c, a); 39 int pb = findp(c, b); 40 if(pa != pb) 41 { 42 p[c][pa] = pb; 43 } 44 } 45 46 int Q; 47 scanf("%d", &Q); 48 for(int i = 0; i < Q; ++i) 49 { 50 int u, v, cnt = 0; 51 scanf("%d%d", &u, &v); 52 for(int c = 1; c <= Mc; ++c) 53 if(findp(c, u) == findp(c, v)) 54 cnt++; 55 printf("%d\n", cnt); 56 } 57 58 return 0; 59 }
路人的dfs。
1 #include<iostream> 2 #include<algorithm> 3 #include<vector> 4 #include<utility> 5 #include<cstring> 6 7 using namespace std; 8 9 const int MAXN = 200; 10 11 int n, m; 12 vector<pair<int, int>> adj[MAXN]; 13 bool vis[MAXN]; 14 15 bool dfs(int v, int c, int des){ 16 if (vis[v]) return false; 17 if (v == des) return true; 18 vis[v] = 1; 19 for (pair<int, int> e:adj[v]) 20 if (e.second == c) 21 if (dfs(e.first, c, des)) return true; 22 return false; 23 } 24 25 int main(){ 26 cin >> n >> m; 27 for (int i = 0; i < m; i++){ 28 int a, b, c; cin >> a >> b >> c; a--, b--; 29 adj[a].push_back({b, c}); 30 adj[b].push_back({a, c}); 31 } 32 33 int q; cin >> q; 34 while (q--){ 35 int a, b; cin >> a >> b; a--, b--; 36 int ans = 0; 37 for (int i = 1; i <= m; i++){ 38 memset(vis, 0, sizeof(vis)); 39 if(dfs(a, i, b)) ans++; 40 } 41 cout << ans << endl; 42 } 43 return 0; 44 }
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的都卡掉了。
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 using namespace std; 5 int p[30005], dp[505][30005]; 6 7 int main(void) 8 { 9 int n, d, x; 10 scanf("%d %d", &n, &d); 11 for(int i = 0; i < n; i++) scanf("%d", &x), p[x]++; 12 int l = max(1, d - 250), r = d + 250; 13 for(int i = 30000; i >= d; i--) 14 { 15 for(int j = l; j < r; j++) 16 { 17 if(j != 1 && i + j - 1 <= 30000) dp[j-l][i] = max(dp[j-l][i], dp[j-l-1][i+j-1]); 18 if(i + j <= 30000) dp[j-l][i] = max(dp[j-l][i], dp[j-l][i+j]); 19 if(i + j + 1 <= 30000) dp[j-l][i] = max(dp[j-l][i], dp[j-l+1][i+j+1]); 20 dp[j-l][i] += p[i]; 21 } 22 } 23 printf("%d\n", dp[d-l][d]); 24 return 0; 25 }
1.24
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\n", 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\n", ans); 19 return 0; 20 }
regular1:
Problem A HDU 1009 FatMouse' Trade
先对价格比值排序,然后按比值从大到小取即可。
数据的坑在于F可能为0,如果存成int要注意不要除0,存成double要注意精度。
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 using namespace std; 5 6 struct node 7 { 8 int J, F; 9 double v; 10 }p[1111]; 11 12 bool cmp(node A, node B) 13 { 14 return A.v > B.v; 15 } 16 17 int main(void) 18 { 19 int M, N; 20 while(~scanf("%d%d", &M, &N)) 21 { 22 if(M == -1 && N == -1) break; 23 double ans = 0; 24 for(int i = 0; i < N; i++) 25 { 26 scanf("%d%d", &p[i].J, &p[i].F); 27 if(!p[i].F) ans += p[i].J, i--, N--; 28 else p[i].v = 1.0 * p[i].J / p[i].F; 29 } 30 sort(p, p + N, cmp); 31 for(int i = 0; i < N; i++) 32 { 33 if(M >= p[i].F) M -= p[i].F, ans += p[i].J; 34 else {ans += p[i].v * M; break;} 35 } 36 printf("%.3lf\n", ans); 37 } 38 return 0; 39 }
Problem B HDU 1050 Moving Tables
这题可以不用贪心的做法。一个操作相当于一次区间覆盖,问题转化为区间覆盖次数最大值。
对于一个覆盖,我们给它的左端点l标记+1,即sum[l]++,右端点右侧r+1标记-1,即sum[r+1]--。
然后从前到后sum[i] += sum[i-1],得到的sum就是每个点被覆盖次数。取最大值即可。
这个方法很常见,复杂度是O(n),希望大家都能掌握。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 using namespace std; 6 int sum[222]; 7 8 int main(void) 9 { 10 int T; 11 scanf("%d", &T); 12 while(T--) 13 { 14 int N; 15 scanf("%d", &N); 16 memset(sum, 0, sizeof(sum)); 17 for(int i = 0; i < N;i++) 18 { 19 int l, r; 20 scanf("%d%d", &l, &r); 21 if(l > r) swap(l, r); 22 sum[(l+1)/2]++, sum[(r+3)/2]--; 23 } 24 int ans = 0; 25 for(int i = 1; i <= 200; i++) 26 { 27 sum[i] += sum[i-1]; 28 ans = max(ans, sum[i]); 29 } 30 printf("%d0\n", ans); 31 } 32 return 0; 33 }
Problem C POJ 1017 Packets
本人的想法比较朴素,就是从大到小装,代码比较丑。
1 include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 using namespace std; 5 6 int main(void) 7 { 8 while(1) 9 { 10 int flag = 0, a[7]; 11 for(int i = 1; i <= 6; i++) 12 { 13 scanf("%d", a + i); 14 if(a[i]) flag = 1; 15 } 16 if(!flag) break; 17 a[1] = max(0, a[1] - 11 * a[5]); 18 int l = max(0, 5 * a[4] - a[2]) * 4; 19 a[2] = max(0, a[2] - 5 * a[4]), a[1] = max(0, a[1] - l); 20 int ans = a[6] + a[5] + a[4]; 21 ans += (a[3] + 3) / 4, a[3] %= 4; 22 if(a[3] == 1) l = max(0, 20 - 4 * a[2]) + 7, a[2] = max(0, a[2] - 5); 23 else if(a[3] == 2) l = max(0, 12 - 4 * a[2]) + 6, a[2] = max(0, a[2] - 3); 24 else if(a[3] == 3) l = max(0, 4 - 4 * a[2]) + 5, a[2] = max(0, a[2] - 1); 25 else l = 0; 26 a[1] = max(0, a[1] - l); 27 ans += (a[2] + 8) / 9, a[2] %= 9; 28 if(a[2]) a[1] = max(0, a[1] - 36 + 4 * a[2]); 29 ans += ( a[1] + 35 ) / 36; 30 printf("%d\n", ans); 31 } 32 return 0; 33 }
路人的优雅姿势。
1 #include <stdio.h> 2 void main() 3 { 4 int N, a, b, c, d, e, f, y, x;//N用来存储需要的箱子数目,y用来存储 2*2 的空位数目 5 // x 用来存储 1*1 的空位数目。 6 int u[4]={0, 5, 3, 1}; 7 //数组u 表示3*3 的产品数目分别是 4的倍数,4 的倍数+1, 4 的倍数+2, 4 的倍数+3 8 //时,为3*3的产品打开的新箱子中剩余的 2*2的空位的个数 9 10 while(1){ 11 scanf("%d%d%d%d%d%d", &a, &b, &c, &d, &e, &f); 12 if (a == 0 && b == 0 && c == 0 && d == 0 && e == 0 && f == 0) break; 13 N = f + e + d + (c + 3) / 4; 14 //这里有一个小技巧 - (c+3)/4 正好等于 c 除以4向上取整的结果,下同 15 y = 5 * d + u[c % 4];//每一个4*4的箱子装过后还可以再装5个2*2的箱子 16 //还有3*3的箱子如果没填满6*6的箱子的话,也可以用来装2*2的箱子 17 //5*5的箱子则只能装1*1的情况了 18 if(b > y) N += (b - y + 8 ) / 9;//如果要装的2*2的箱子的数目比提供的空间要多, 19 //那么多的部分要新开一些箱子 20 x = 36 * N - 36 * f - 25 * e - 16 * d - 9 * c - 4 * b; 21 if(a > x) N += ( a - x + 35 ) / 36; 22 printf("%d/n", N); 23 } 24 }
Problem D HDU 1051 Wooden Sticks
以l和w为一二关键字排序后,维护一个标记数组。
每次从前往后找,如果找到l与w都比当前值大的木头,做上标记,并更新当前l与w值,直到所有木头被标记。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 using namespace std; 6 int used[5555]; 7 8 struct node 9 { 10 int l, w; 11 }m[5555]; 12 13 bool cmp(node A, node B) 14 { 15 if(A.l != B.l) return A.l < B.l; 16 return A.w < B.w; 17 } 18 19 int main(void) 20 { 21 int T; 22 scanf("%d", &T); 23 while(T--) 24 { 25 int N; 26 scanf("%d", &N); 27 int ans = N; 28 memset(used, 0, sizeof(used)); 29 for(int i = 0; i < N; i++) scanf("%d%d", &m[i].l, &m[i].w); 30 sort(m, m + N, cmp); 31 for(int i = 0; i < N; i++) 32 { 33 if(used[i]) continue; 34 int pos = i, pw = m[i].w; 35 for(int j = i + 1; j < N; j++) 36 { 37 if(used[j] || m[j].w < pw) continue; 38 ans--, pw = m[j].w, pos = j, used[j] = 1; 39 } 40 } 41 printf("%d\n", ans); 42 } 43 return 0; 44 }
Problem E HDU 1338 Game Prediction
一共m*n张牌,从大到小枚举每张牌,并维护一个cnt值,表示当前别人手中比自己大的牌数目。
如果枚举到一张牌不属于自己,那么给cnt++,
如果枚举到这张牌属于自己,若cnt不为0,说明此时别人手中有比自己大的牌,则给cnt--,含义为别人需要出掉一张比自己大的牌来压掉当前这张牌。
若cnt为0,此时这张牌是最大的了,ans++。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 using namespace std; 6 int a[2222]; 7 8 int main(void) 9 { 10 int m, n, kase = 0; 11 while(~scanf("%d %d", &m, &n) && m ) 12 { 13 int ans = 0, cnt = 0, x; 14 memset(a, 0, sizeof(a)); 15 for(int i = 0; i < n; i++) scanf("%d", &x), a[x] = 1; 16 for(int i = m * n; i > 0; i--) 17 { 18 if(a[i]) 19 { 20 if(cnt) cnt--; 21 else ans++; 22 } 23 else cnt++; 24 } 25 printf("Case %d: %d\n", ++kase, ans); 26 } 27 return 0; 28 }
Problem F ZOJ 2229 Ride to School
首先,无视所有时间为负数的人,因为如果追不上他,就不能和他一起到学校,
如果追的上他,而你的出发时间比他晚,说明你速度比他还快,不必和他一起走。
其次,剩下人中最早到学校的人的到达时间即为答案,假设你最开始跟的人就是最早到学校的,那么符合结论。
如果是被后面出发的人反超,中间必然有一个交点,此时去跟快的即可。
1 #include <iostream> 2 #include <cstdio> 3 #include <cmath> 4 #include <algorithm> 5 using namespace std; 6 7 int main(void) 8 { 9 int N; 10 while(~scanf("%d", &N) && N) 11 { 12 int ans = 9999999; 13 for(int i = 1; i <= N; i++) 14 { 15 int v, t; 16 scanf("%d%d", &v, &t); 17 if(t < 0) continue; 18 ans = min(ans, t + (int) ceil(3600 * 4.5 / v)); 19 } 20 printf("%d\n", ans); 21 } 22 return 0; 23 }
Problem G HDU 1789 Doing Homework again
按deadline排序,维护当前已经做的作业数目,
遍历所有作业,如果当前已做数目小于当前作业的deadline,可以完成这个。
如果大于等于deadline,则看已完成中是否有score比当前作业小的,有则替换掉。
因为数目比较少,找最小值时直接扫一遍就可以了。
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 using namespace std; 5 6 struct task 7 { 8 int t, s, d; 9 }tk[1111]; 10 11 bool cmp(task A, task B) 12 { 13 return A.t < B.t; 14 } 15 16 int main(void) 17 { 18 int T; 19 scanf("%d", &T); 20 while(T--) 21 { 22 int N, ans = 0, cnt = 0; 23 scanf("%d", &N); 24 for(int i = 0; i < N; i++) scanf("%d", &tk[i].t); 25 for(int i = 0; i < N; i++) scanf("%d", &tk[i].s), tk[i].d = 0; 26 sort(tk, tk + N, cmp); 27 for(int i = 0; i < N; i++) 28 { 29 if(tk[i].t > cnt) {cnt++; continue;} 30 int Min = tk[i].s, pos = i; 31 for(int j = 0; j < i; j++) 32 { 33 if(tk[j].d) continue; 34 if(tk[j].s < Min) Min = tk[j].s, pos = j; 35 } 36 tk[pos].d = 1, ans += tk[pos].s; 37 } 38 printf("%d\n", ans); 39 } 40 return 0; 41 }
Problem H HDU 2037 今年暑假不AC
按end时间排序,从前往后取不覆盖的即可。
这里用了下pair<int,int>,排序默认以first和second为第一二关键字,可以不用自定义比较函数。
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 using namespace std; 5 typedef pair<int, int> pii; 6 pii s[111]; 7 8 int main(void) 9 { 10 int n; 11 while(~scanf("%d", &n) && n) 12 { 13 for(int i = 0; i < n; i++) scanf("%d%d", &s[i].second, &s[i].first); 14 sort(s, s + n); 15 int ed = 0, ans = 0; 16 for(int i = 0; i < n; i++) 17 if(ed <= s[i].second) ans++, ed = s[i].first; 18 printf("%d\n", ans); 19 } 20 return 0; 21 }
Problem I POJ 1456 Supermarket
G题的N扩大版,用了一个优先队列维护最小值了。(不会可以先放放)
1 #include <iostream> 2 #include <cstdio> 3 #include <queue> 4 #include <algorithm> 5 using namespace std; 6 typedef pair<int, int> pii; 7 pii p[11111]; 8 9 int main(void) 10 { 11 int n; 12 while(~scanf("%d", &n)) 13 { 14 for(int i = 0; i < n; i++) scanf("%d %d", &p[i].second, &p[i].first); 15 sort(p, p + n); 16 priority_queue <int> pq; 17 for(int i = 0; i < n; i++) 18 { 19 if(p[i].first > pq.size()) pq.push(-p[i].second); 20 else if(-pq.top() < p[i].second) 21 { 22 pq.pop(); 23 pq.push(-p[i].second); 24 } 25 } 26 int ans = 0; 27 while(!pq.empty()) ans -= pq.top(), pq.pop(); 28 printf("%d\n", ans); 29 } 30 return 0; 31 }
Problem J POJ 1328 Radar Installation
考虑每个岛为圆心,d为半径画圆,会与海岸线交于两点,在两点间放置雷达都能覆盖小岛,问题就转化到了线段上。
对于每个岛的线段左端点排序,然后取掉这个线段覆盖的其他所有线段,不能覆盖时则再放置一个雷达,以此类推。
这里表述不是很好理解,具体可以看代码。
1 #include <iostream> 2 #include <cstdio> 3 #include <cmath> 4 #include <algorithm> 5 using namespace std; 6 7 struct point 8 { 9 double l, r; 10 }p[1111]; 11 12 bool cmp(point A, point B) 13 { 14 return A.l < B.l; 15 } 16 17 int main(void) 18 { 19 int n, d, kase = 0; 20 while(~scanf("%d %d", &n, &d) && n) 21 { 22 int ok = 1; 23 for(int i = 0; i < n; i++) 24 { 25 int x, y; 26 scanf("%d%d", &x, &y); 27 if(y > d) {ok = 0;continue;} 28 p[i].l = x - sqrt(d * d - y * y); 29 p[i].r = 2 * x - p[i].l; 30 } 31 sort(p, p + n, cmp); 32 printf("Case %d: ", ++kase); 33 if(!ok) {puts("-1");continue;} 34 int cnt = 0; 35 double cur = -1e10; 36 for(int i = 0; i < n; i++) 37 { 38 if(p[i].l > cur) cur = p[i].r, cnt++; 39 else if(p[i].r < cur) cur = p[i].r; 40 } 41 printf("%d\n", cnt); 42 } 43 return 0; 44 }
Problem K POJ 2437 Muddy roads
排序后从左到右贪心的放置木板,同时维护一个当前木板延伸到的最右边位置即可。
1 #include <iostream> 2 #include <cstdio> 3 #include <cmath> 4 #include <algorithm> 5 using namespace std; 6 7 struct point 8 { 9 int l, r; 10 }p[11111]; 11 12 bool cmp(point A, point B) 13 { 14 return A.l < B.l; 15 } 16 17 int main(void) 18 { 19 int N, L; 20 while(~scanf("%d%d", &N, &L)) 21 { 22 for(int i = 0; i < N; i++) scanf("%d%d", &p[i].l, &p[i].r); 23 sort(p, p + N, cmp); 24 int cnt = 0, cur = 0; 25 for(int i = 0; i < N; i++) 26 { 27 if(cur >= p[i].r) continue; 28 if(cur > p[i].l) p[i].l = cur; 29 cnt += (p[i].r - p[i].l + L - 1) / L; 30 cur = p[i].l + (p[i].r - p[i].l + L - 1) / L * L; 31 } 32 printf("%d\n", cnt); 33 } 34 return 0; 35 }
Problem L ZOJ 2883 Shopaholic
从大到小排序,每三个为一组,一组中最小的不用钱。
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 using namespace std; 5 int a[22222]; 6 7 int main(void) 8 { 9 int T; 10 scanf("%d", &T); 11 while(T--) 12 { 13 int n; 14 scanf("%d", &n); 15 for(int i = 0; i < n; i++) scanf("%d", a + i); 16 sort(a, a + n); 17 int ans = 0; 18 for(int i = 0; i < n; i++) if(i % 3 == 2) ans += a[n-i-1]; 19 printf("%d\n", ans); 20 } 21 return 0; 22 }
regular2:
G题不属于本专题。挂错致歉。
Problem A POJ 3273 Monthly Expense
二分expense,然后判断能否达到,此时贪心的从前往后取即可。
1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 typedef long long LL; 5 const int maxn = 1e5 + 10; 6 int a[maxn]; 7 8 int main(void) 9 { 10 int N, M; 11 scanf("%d%d", &N, &M); 12 for(int i = 0; i < N; i++) scanf("%d", a + i); 13 LL l = 0, r = 1e9, mid; 14 while(l < r) 15 { 16 mid = (l + r) / 2; 17 int ok = 1; 18 LL cnt = 1, cur = 0; 19 for(int i = 0; i < N; i++) 20 { 21 if(a[i] > mid) {ok = 0; break;} 22 if(a[i] + cur <= mid) cur += (LL) a[i]; 23 else cnt++, cur = a[i]; 24 } 25 if(cnt > M) ok = 0; 26 if(ok) r = mid; 27 else l = mid + 1; 28 } 29 printf("%I64d\n", r); 30 return 0; 31 }
Problem B POJ 3104 Drying
二分时间,然后判断每件衣服能否自然干,不能则放在dryer上一定的时间。
要注意k = 0的情况。
1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 const int maxn = 1e5 + 10; 5 typedef long long LL; 6 LL a[maxn]; 7 8 int main(void) 9 { 10 LL n, k; 11 while(~scanf("%I64d", &n)) 12 { 13 for(int i = 0; i < n; i++) scanf("%I64d", a + i); 14 scanf("%I64d", &k); 15 LL l = 0, r = 1e9, mid; 16 while(l < r) 17 { 18 mid = (l + r) / 2; 19 LL cnt = 0; 20 for(int i = 0; i < n; i++) 21 { 22 if(a[i] <= mid) continue; 23 else if(k == 1) {cnt = mid + 1;break;} 24 else cnt += ( a[i] - mid + k - 2 ) / (k - 1) ; 25 } 26 if(cnt <= mid) r = mid; 27 else l = mid + 1; 28 } 29 printf("%I64d\n", r); 30 } 31 return 0; 32 }
Problem C POJ 1064 Cable master
先化成int的厘米,二分最大长度,得到答案后化回米。
1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 const int maxn = 11111; 5 int a[maxn]; 6 7 int main(void) 8 { 9 int N, K; 10 scanf("%d%d", &N, &K); 11 for(int i = 0; i < N; i++) 12 { 13 int z, x; 14 scanf("%d.%d", &z, &x); 15 a[i] = z * 100 + x; 16 } 17 int l = 0, r = 1e7, mid; 18 while(l < r) 19 { 20 mid = (l + r + 1) / 2; 21 int cnt = 0; 22 for(int i = 0; i < N; i++) cnt += a[i] / mid; 23 if(cnt < K) r = mid - 1; 24 else l = mid; 25 } 26 printf("%d.%02d\n", l / 100, l % 100); 27 return 0; 28 }
Problem D POJ 2456 Aggressive cows
二分最小距离,贪心的从左往右放,判断是否可行。
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 using namespace std; 5 int a[111111]; 6 7 int main(void) 8 { 9 int N, C; 10 scanf("%d %d", &N, &C); 11 for(int i = 0; i < N; i++) scanf("%d", a + i); 12 sort(a, a + N); 13 int l = 0, r = 1e9, mid; 14 while(l < r) 15 { 16 mid = (l + r + 1) / 2; 17 int cnt = 1, cur = a[0]; 18 for(int i = 1; i < N; i++) 19 if(a[i] - cur >= mid) cur = a[i], cnt++; 20 if(cnt >= C) l = mid; 21 else r = mid - 1; 22 } 23 printf("%d\n", l); 24 return 0; 25 }
Problem E POJ 3111 K Best !!强烈推荐做此题
二分比值,假设为x。每次要判断是否存在一个子集使得sigma{v}/sigma{w} >= x。
上式移项成为sigma{v-x*w}>=0,所以只要按照v-x*w排序,贪心的取前k个,看能否满足sigma{v}/sigma{w} >= x即可。
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 using namespace std; 5 const int maxn = 1e5 + 10; 6 double x; 7 8 struct jewel 9 { 10 int id, v, w; 11 }j[maxn]; 12 13 bool cmp(jewel A, jewel B) 14 { 15 return A.v - x * A.w > B.v - x * B.w; 16 } 17 18 int main(void) 19 { 20 int n, k; 21 scanf("%d %d", &n, &k); 22 for(int i = 0; i < n; i++) scanf("%d%d", &j[i].v, &j[i].w), j[i].id = i + 1; 23 double l = 0, r = 1e6; 24 while(l < r - 1e-9) 25 { 26 x = (l + r) / 2; 27 sort(j, j + n, cmp); 28 int s1 = 0, s2 = 0; 29 for(int i = 0; i < k; i++) s1 += j[i].v, s2 += j[i].w; 30 if(1.0 * s1 / s2 < x) r = x; 31 else l = x; 32 } 33 for(int i = 0; i < k; i++) printf("%d ", j[i].id); 34 return 0; 35 }
Problem F POJ 3685 Matrix
先二分第k小的值M,由于每一列是递增的,可以再二分出一列中小于M的数的个数,求和后与k比较。
1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 typedef long long LL; 5 6 LL a(LL i, LL j) 7 { 8 return i * i + i * 100000LL + j * j - j * 100000LL + i * j; 9 } 10 11 int main(void) 12 { 13 int T; 14 scanf("%d", &T); 15 while(T--) 16 { 17 LL N, M; 18 scanf("%I64d %I64d", &N, &M); 19 LL L = - 1e12, R = 1e12, mid; 20 while(L < R) 21 { 22 mid = L + (R - L) / 2; 23 LL cnt = 0LL; 24 for(int j = 1; j <= N; j++) 25 { 26 LL l = 0, r = N, i; 27 while(l < r) 28 { 29 i = r - (r - l) / 2; 30 if(a(i, j) <= mid) l = i; 31 else r = i - 1LL; 32 } 33 cnt += l; 34 } 35 if(cnt < M) L = mid + 1LL; 36 else R = mid; 37 } 38 printf("%I64d\n", R); 39 } 40 return 0; 41 }
Problem H POJ 1759 Garland
二分第二个灯的高度,递推会发现它与最后一个灯的高度是线性的。
此题的精度大概比较坑。
1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 double h[1111]; 5 int N; 6 7 bool judge(double mid) 8 { 9 h[1] = mid; 10 for(int i = 2; i < N; i++) 11 { 12 h[i] = 2 * (h[i-1] + 1) - h[i-2]; 13 if(h[i] < 1e-8) return false; 14 } 15 return true; 16 } 17 18 int main(void) 19 { 20 while(~scanf("%d %lf", &N, h)) 21 { 22 double l = 0, r = h[0], mid; 23 while(l < r - 1e-8) 24 { 25 mid = (l + r) / 2; 26 if(!judge(mid)) l = mid; 27 else r = mid; 28 } 29 printf("%.2f", h[N-1]); 30 } 31 return 0; 32 }
bonus:见紫书。
cf & bc:见bc、cf首页。