hut 训练赛第二场 解题报告
题目来源 HDU 2008-10 Programming Contest
ID | Origin | Title |
---|---|---|
Problem A | HDU 2520 | 我是菜鸟,我怕谁 |
Problem B | HDU 2521 | 反素数 |
Problem C | HDU 2522 | A simple problem |
Problem D | HDU 2523 | SORT AGAIN |
Problem E | HDU 2524 | 矩形A + B |
Problem F | HDU 2525 | Clone Wars |
Problem G | HDU 2526 | 浪漫手机 |
Problem H | HDU 2527 | Safe Or Unsafe |
Problem A
仔细读题, 这个不是匀变速运动, 每一秒初,速度直接改变. 化简下可以得出结果为 n*n
参考代码:
#include<stdio.h> #include<stdlib.h> #include<string.h> const int mod = 1e4; const int N = 1e5; typedef long long LL; int main(){ int T, n; scanf("%d", &T); while( T-- ) { scanf("%d",&n); LL res = 1LL*n*n; printf("%lld\n", res%mod); } return 0; }
Problem B
仔细读题,题目虽然前面一句讲的是反素数,却非要求反素数. 只需求所给区间[a,b]因子个数最多的数,当有多个解时,输出最小的.
我们可以预处理, 将数 x 的倍数全部+1 , 通过这样枚举 x 就可以了
参考代码
#include<stdio.h> #include<string.h> #include<stdlib.h> #define MAX(a,b) (a)>(b)?(a):(b) const int N = 5010; bool vis[N]; int g[N], n; void init() { memset( vis ,0 , sizeof(vis)); for(int i = 1; i < N; i++) g[i] = 1; for(int i = 1; 2*i < N; i++ ) g[2*i]++; for(int i = 3; i < N; i++) { g[i]++; for(int j = 2; j*i < N; j++) g[ j*i ]++; } int Max = 0; for(int i = 1; i < N; i++) { if( Max < g[i] ) vis[i] = true; Max = MAX( Max, g[i]); } } int main() { init(); int T; scanf("%d",&T); while( T-- ) { int a, b; scanf("%d%d",&a,&b); int Max = 0, res; for(int i = a; i <= b; i++) if( g[i] > Max ) { Max = g[i]; res = i; } printf("%d\n", res ); } return 0; }
Problem C
模拟除法运算, 并标记余数,当余数为0 或者 余数已被标记, 则分别为 整除解, 第一个循环节.
不过这题有点恶心的地方是,如果计算写到函数中,就TLE. 只能在 main函数中操作. 1K ms . 依旧百思不得其解
PS: 有兴趣的还可以了解下手动开根号. 12年湖南省赛就有这么一题
参考代码
#include<stdio.h> #include<string.h> #include<stdlib.h> #include<math.h> const int N = 1e6+10; int res[N], size; bool vis[N]; inline void solve(int n){ size = 0; memset( res, 0, sizeof(res)); memset( vis, 0, sizeof(vis)); int x = 1; vis[x] = true; while( x ) { x *= 10; res[size++] = x/n; x %= n; if( vis[x] ) break; vis[x] = true; } } int main(){ int T, n; scanf("%d",&T); while( T-- ) { scanf("%d", &n); if( n < 0 ) { printf("-"); n = -n; } if( n == 1 ) printf("1\n"); else{ size = 0; memset( vis, 0, sizeof(vis)); int x = 1; vis[x] = true; while( x ) { x *= 10; res[size++] = x/n; x %= n; if( vis[x] ) break; vis[x] = true; } //solve(n); printf( "0." ); for(int i = 0; i < size; i++) printf("%d",res[i]); puts(""); } } return 0; }
Problem D
因为N = 1000 , 两两组合后极端情况有 10^6 个值, 排序时间复杂度 Nlog(N) 其中 N = n*n , 又 C组测试数据, 总时间复杂度为 C*n^2*log( n^n)
当 C >= 1000 时, 时间复杂度接近 10^9 , 1,2秒内可能搞不定.
仔细观察, 0 <= a[i] <= 2000 , 那么 0 <= a[i] - a[j] <= 2000 , 当a[ i] >= a[j]时
不同的数值只可能出现 2001 个. 所以我们可以使用 一个 2000的辅助数组 b[2002] 用来记录 | a[i] - a[j] | 的不同结果数量
然后对数组 b[] 从小到大统计到第 k 个就是我们需要的结果了
将 数组 a[] 排序后 可以避免绝对值计算
参考代码:
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<iostream> #include<math.h> #include<algorithm> using namespace std; const int N = 1010; int a[N],vis[4200],cnt, n, K; int main() { int T; scanf("%d",&T); while( T-- ) { scanf("%d%d",&n,&K); memset( vis, 0, sizeof(vis)); cnt = 0; for(int i = 0; i < n; i++) scanf("%d",&a[i]); sort( a, a+n ); for(int i = 0; i < n; i++) for(int j = 0; j < i; j++) vis[ a[i]-a[j] ] = 1; int pos = 0; while(K) K -= vis[pos++]; printf("%d\n", pos-1 ); } return 0; }
Problem E
递推题. 我们定义 F[n][m] 为n行m列的矩形数量
因为 n行m列的矩形 是由 n-1行m列 通过增加一行 , 或者 n行m-1列 通过增加一列 得到.
我们现在只考虑 其由 n-1行m列 通过 增加一行 得到.
通过分解. m列, 每一列都增加了一个 1 单位的小矩形.
只对当前一列考虑时 , 其增加了 n 个 ( 考虑 n+1行1列的矩阵, 增加一行则增加了 n个矩形 ), 总共 m 列,则增加了 m*n个矩阵
再考虑两列相邻组合情况,总共有 m-1 个 两两相邻矩阵, 同样是 n个. 总共m-1,则增加了 (m-1)*n
再 3列, 4列 ... m列. 分别是 (m-2)*n . (m-3)*n ... n
累加后, m*n + (m-1)*n + .. + 1*n
提取 公因子n出来后, 可得 n( 1+2+...+m )
然后等差数列求和的到. n*m*(1+m)/2
所以 F[n][m] = F[n-1][m] + n*m*(1+m)/2
预处理 F[1][i] , F[i][1] 的情况然后 预处理就可以了. n, m才100
参考代码
#include<stdio.h> #include<string.h> #include<stdlib.h> typedef long long LL; LL f[110][110]; void init() { memset( f, 0, sizeof(f)); for(int i = 1; i <= 100; i++) f[1][i] = f[i][1] = (1+i)*i/2; for(int i = 2; i <= 100; i++) for(int j = 2; j <= 100; j++) f[i][j] = f[i-1][j] + 1LL*i*j*(j+1)/2; } int main() { init(); int T; scanf("%d",&T); while( T-- ) { int n, m; scanf("%d%d",&n,&m); printf("%lld\n", f[n][m]); } return 0; }
Problem F
每一天有士兵死亡,有士兵提供材料进行克隆,有士兵克隆成功.
注意: 士兵生存天数为[1,d] , 可提供克隆材料为生存天数 [1,a] , 培育天数为 [0,k] , 第 k 天培育成功后, k+1 天可以执行任务和提供材料.
这里士兵生存天数和提供克隆材料都是1开始, 是因为对于克隆培育来说, 当第 k 天培育成功后, 相对于 士兵生存天数与提供克隆材料为 第0天.
当k+1时,已经是第1天了, 所以我们需要区分开 时间点的相关关系问题.
解题代码:
#include<stdio.h> #include<string.h> #include<stdlib.h> typedef long long LL; LL D[110], K[110], A[110], ans; int n, d, a, k, x; int main() { int T; scanf("%d", &T); while( T-- ) { // 初始n个士兵 // 每个士兵可存活 d 天 // 培育士兵 k 天成功, k+1 天开始执行任务 // 成功克隆后,前 a 天可提取材料 scanf("%d%d%d%d%d",&n,&d,&a,&k,&x); memset( D, 0, sizeof(D) ); memset( K, 0, sizeof(K) ); memset( A, 0, sizeof(A) ); //初始化第0天的状态 D[1] = 0; //目前存活了0天的士兵数量 A[1] = 0; //可提取材料的士兵 K[k] = n; //第0天培育成功n个士兵,并在下一天执行任务 ans = 0; for(int day = 1; day <= x; day++) { LL s1 = 0, s2 = 0; for(int i = k; i >= 0; i-- ) K[i+1] = K[i]; for(int i = d; i >= 1; i-- ) D[i+1] = D[i]; for(int i = a; i >= 1; i-- ) A[i+1] = A[i]; //培养士兵k+1天开始执行任务并提供材料 D[1] = A[1] = K[k+1]; for(int i = 1; i <= d; i++) s1 += D[i]; ans += s1*5; //提取材料 for(int i = 1; i <= a; i++) s2 += A[i]; K[0] = s2; } printf("%lld\n", ans ); } return 0; }
Problem G
貌似这套题出题人有点缺心眼..
8种模式对比得出下一行的情况.按行匹配就好了.
对于边界我们可以将 0和 m+1 列置为 0 ,这样就简化判断了.
参考代码
#include<string.h> #include<stdlib.h> #include<algorithm> #include<stdio.h> using namespace std; int dir[8][4]; int mp[1010][1010]; int n, m; char str[1010], tmp[10]; int GetKey(int x,int y,int z){ int a[3] = {x,y,z}; for(int i = 0; i < 8; i++) { bool flag = true; for(int j = 0; j < 3 && flag; j++) { if( dir[i][j] != a[j] ) flag = false; } if( flag ) return i; } } bool legal( int x, int y) { if( (x>=1)&&(x<=n) && (y>=1)&&(y<=m) ) return true; return false; } int main() { int T; scanf("%d", &T); while( T-- ) { scanf("%d", &n); for(int i = 0; i < 8; i++) { scanf("%s %d", tmp, &dir[i][3]); for(int j= 0; j < 3; j++) dir[i][j] = tmp[j]-'0'; } scanf("%s", str); m = strlen( str ); memset( mp, 0, sizeof(mp)); for(int i = 1; i <= m; i++) mp[1][i] = str[i-1]-'0'; for(int i = 1; i < n; i++) for(int j = 1; j <= m; j++) { int k; k = GetKey( mp[i][j-1], mp[i][j], mp[i][j+1] ); mp[i+1][j] = dir[k][3]; } for(int i = 1; i <= n; i++) { for(int j = 1; j <= m; j++) printf("%d",mp[i][j]); puts(""); } } return 0; }
Problem H
哈夫曼编码 : 我们可以这样理解, 对于一个容器, 每一个节点有一个值 (权值), 点与点之间无连接.
每次从容器中取出两个权值最小的顶点 相连 , 并将两个顶点权值和相加后合并成一个顶点放回容器. 如此反复.得出只有一个顶点的情况,
整个过程中 所有合并的和相加,就是 整颗树的权值了.
本题中, 顶点为字母,其出现次数为其权值. 按照上述方法求出后与给定值对比就可以了.
如果使用优先队列,则要注意一个字符的情况的处理
参考代码:
#include<stdio.h> #include<string.h> #include<stdlib.h> #include<algorithm> #include<queue> using namespace std; priority_queue< int, vector<int>, greater<int> > Q; char str[101010]; int cnt[30]; int main() { int T, n; scanf("%d",&T); while( T-- ) { scanf("%d", &n); scanf("%s", str); memset( cnt, 0, sizeof(cnt)); for(int i = 0; str[i]; i++) cnt[ str[i]-'a' ]++; while( !Q.empty() ) Q.pop(); for(int i = 0; i < 26; i++) if( cnt[i] ) Q.push(cnt[i]); int key = 0; if( Q.size() == 1 ) { key = Q.top(); puts( key <= n ? "yes" : "no"); continue; } while( Q.size() > 1 ) { int a = Q.top(); Q.pop(); int b = Q.top(); Q.pop(); // printf("a = %d, b = %d\n", a, b); key += (a+b); Q.push( a+b ); } puts( key <= n ? "yes" : "no" ); } return 0; }