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
参考代码:
View Code
#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 就可以了
参考代码
View Code
#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年湖南省赛就有这么一题
参考代码
View Code
#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[] 排序后 可以避免绝对值计算
参考代码:
View Code
#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
参考代码
View Code
#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天了, 所以我们需要区分开 时间点的相关关系问题.
解题代码:
View Code
#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 ,这样就简化判断了.
参考代码
View Code
#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
哈夫曼编码 : 我们可以这样理解, 对于一个容器, 每一个节点有一个值 (权值), 点与点之间无连接.
每次从容器中取出两个权值最小的顶点 相连 , 并将两个顶点权值和相加后合并成一个顶点放回容器. 如此反复.得出只有一个顶点的情况,
整个过程中 所有合并的和相加,就是 整颗树的权值了.
本题中, 顶点为字母,其出现次数为其权值. 按照上述方法求出后与给定值对比就可以了.
如果使用优先队列,则要注意一个字符的情况的处理
参考代码:
View Code
#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; }


浙公网安备 33010602011771号