hut_csust 新生友谊赛题目解析
先给出 Lyush 大神的部分解题思路
HDU-4255 A Famous Grid BFS 知识点:素数筛选+模拟 难度:中等 Source Fudan Local Programming Contest 2012 思路:得到两个表,一个素数表,一个是矩阵表,然后搜索即可,一个要注意的地方是要把矩阵打的稍微大一点,因为可能要走到外面去。 HDU-4386 Quadrilateral 知识点:海伦公式推广,数学 难度:中等 Source 2012 Multi-University Training Contest 9 思路:这题就是一个结论了,当四边形为圆的内接四边形,面积最大。设四边长为abcd,半周长为p,则最大面积=sqrt((p-a)*(p-b)*(p-c)*(p-d)) POJ-2785 4 Values whose Sum is 0 知识点:hash 或者 二分 难度:中等 Source Southwestern Europe 2005 思路:两种做法,第一就是枚举出两个n*n的集合,然后再利用hash操作来得出结果。还有一种解法就是用到了二分查找。 POJ-2027 No Brainer 签名题 ZOJ-3622 Magic Number 知识点:想法,找规律 难度:中等 思路:打表,找规律(直接肯定不行)。一共也只有不超过50个数,可以直接用数组存下来。
题目来源
ID | Origin | Title |
---|---|---|
Problem A | HDU 4255 | A Famous Grid |
Problem B | HDU 4386 | Quadrilateral |
Problem C | POJ 2785 | 4 Values whose Sum is 0 |
Problem D | POJ 2027 | No Brainer |
Problem E | ZOJ 3622 | Magic Number |
Problem F | HDU 4300 | Clairewd’s message |
Problem G | HDU 4310 | Hero |
Problem H | HDU 4311 | Meeting point-1 |
Problem I | POJ 1061 | 青蛙的约会 |
A题:
预处理生成200*200的图形后,再筛选出10000以内素数进行标记,之后使用BFS(广度优先搜索即可)
参考代码:
#include<stdio.h> #include<stdlib.h> #include<iostream> #include<string.h> #include<algorithm> #include<queue> using namespace std; const int N = 200; const int maxn = 100000; int mp[N+10][N+10]; int p[10000], size; bool vis[maxn+10]; void InitGraph(int key, int x, int y, int n){ // printf("x = %d, y = %d\n", x, y); if( n <= 1 ) return; int r , c ; for(r = x, c = y; c <= y+n-1; c++) mp[r][c] = key--; for(r = x+1, c = y+n-1; r <= x+n-1; r++) mp[r][c] = key--; for(r = x+n-1, c = y+n-2; c >= y; c--) mp[r][c] = key--; for(r = x+n-2, c = y; r > x; r--) mp[r][c] = key--; r = x+1; c = y+1; n -= 2; InitGraph( key, r, c, n ); } void GetPrime(){ memset( vis, 0, sizeof(vis)); vis[0] = vis[1] = true; for(int i = 2; i < maxn; i++) { if( !vis[i] ) p[size++] = i; for(int j = 0; j < size && p[j]*i < maxn; j++) { vis[ p[j]*i ] = true; if( i%p[j] == 0 ) break; } } // printf("%d\n", size ); } void test(){ for(int i = 1; i <= N; i++) { for(int j = 1; j <= N; j++) printf("%5d", mp[i][j] ); puts(""); } } struct node{ int x, y, step; }st,ed,info,nxt; bool used[N+10][N+10]; int dir[4][2] = { {0,-1},{-1,0},{1,0},{0,1} }; queue<node> Q; bool legal( int x, int y) { if( x >= 1 && x <= N && y >= 1 && y <= N ) return true; return false; } void BFS(int s,int t){ for(int i = 1; i <= N; i++) for(int j = 1; j <= N; j++) { if( mp[i][j] == s ){st.x = i;st.y = j;st.step = 0; } if( mp[i][j] == t ){ed.x = i;ed.y = j; } } while( !Q.empty() ) Q.pop(); memset( used, 0, sizeof(used)); used[ st.x ][ st.y ] = true; Q.push(st); while( !Q.empty() ){ info = Q.front(); Q.pop(); // printf("x = %d, y = %d\n", info.x, info.y ); if( info.x == ed.x && info.y == ed.y ){ printf("%d\n", info.step); return; } for(int i = 0; i < 4; i++) { int x = info.x+dir[i][0], y = info.y+dir[i][1]; if( legal(x,y) && vis[ mp[x][y] ] && !used[x][y]) { used[x][y] = true; nxt.x = x; nxt.y = y; nxt.step = info.step+1; Q.push(nxt); } } } puts("impossible"); } int main(){ InitGraph( N*N, 1, 1, N );// test(); GetPrime(); int s, t, T = 1; while( scanf("%d%d",&s,&t) != EOF) { printf("Case %d: ", T++); BFS(s,t); } return 0; }
B题:
结论题,海伦公式推广到四边形,要注意限定在圆内接四边形
证明: 记圆内接四边形ABCD的四条边依次为a、b、c、d,则连接一条对角线AC,其对角为∠B和∠D并且∠B + ∠D = 180° -----------我给的角度不一定对应,你自己画个图吧 根据余弦定理: AC² = a² + b² - 2abcos∠B AC² = c² + d² - 2cdcos∠D = c² + d² + 2cdcos∠B -------已经用到 ∠B + ∠D = 180° 所以: a² + b² - 2abcos∠B = c² + d² + 2cdcos∠B 得到:cos∠B = (a² + b² - c² - d² ) / (2ab + 2cd) sinB = sinD = √(1 - cos²B) = √[(2ab + 2cd)²- (a² + b² - c² - d² )²] / (2ab + 2cd) .........用平方差公式 略....= √[ (a+b+c-d)(a+b-c+d) (a-b+c+d)(-a+b+c+d)] / (2ab + 2cd) S圆内接四边形面积 = S△ABC + S△ABD = absinB / 2 + cdsinD / 2 = (ab + cd)absinB / 2 = √[(s - a)(s - b)(s - c)(s - d)]
参考代码:
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<algorithm> 4 #include<math.h> 5 using namespace std; 6 const double esp = 1e-8; 7 double getarea( int a, int b, int c, int d) 8 { 9 double p = (a+b+c+d)/2.; 10 return sqrt( (p-a)*(p-b)*(p-c)*(p-d) ); 11 } 12 int main(){ 13 int T, a[4]; 14 scanf("%d", &T); 15 for(int Case = 1; Case <= T; Case++ ) 16 { 17 for(int i = 0; i < 4; i++) 18 scanf("%d", &a[i] ); 19 printf("Case %d: ", Case); 20 sort( a, a+4 ); 21 if( a[0]+a[1]+a[2] <= a[3] ) printf("-1\n"); 22 else 23 { 24 double ans = getarea( a[0], a[1], a[2], a[3] ); 25 printf("%.6lf\n", ans); 26 } 27 } 28 }
C题:
因为N = 4000, 则我们可以得出A,B集合的所有组合情况后存储起来, 然后再计算C,D集合组合情况的时候查询 -( C[i] + D[j] ) 是否属于 A,B组合集合中.
在存储 A,B集合组合情况, 我们需要接近O(1)时间进行查询.
这里,我们使用哈希的思想: 对于一个数 X, 我们将它对一个大素数P取模,
可能有部分数对P取模后,结果相同, 则我们对于任意一个 X%P ,建立一个链表, 链表节点上存储X本身.
这样,我们可以在接近O(1)的时间查询值X,是否存在与这一条链上.
参考代码:
#include<stdio.h> #include<iostream> #include<string.h> using namespace std; const int Mod = 10000007; const int N = 5000; typedef long long LL; struct node{ int key,cnt,next; }edge[Mod+10]; int head[Mod+10], idx; int a[N], b[N], c[N], d[N]; int n; void insert( int x ){ int u = x%Mod; if(u < 0) u += Mod; bool flag = true; for(int i = head[u]; ~i ; i = edge[i].next) // 查询是否已存在 if( edge[i].key == x ){ edge[i].cnt++; return; } // 不存在时插入 edge[idx].key = x; edge[idx].next = head[u]; edge[idx].cnt = 1; head[u] = idx++; } int find( int x ){ int u = x%Mod; if(u < 0) u += Mod; for(int i = head[u]; ~i; i = edge[i].next) if( edge[i].key == x ) return edge[i].cnt; return 0; } int main(){ while( scanf("%d", &n) != EOF) { memset( head, 0xff, sizeof(head)); idx = 0; for(int i = 0; i < n; i++) scanf("%d%d%d%d",&a[i],&b[i],&c[i],&d[i]); for(int i = 0; i < n; i++) for(int j = 0; j < n; j++) insert( a[i]+b[j] ); LL ans = 0; for(int i = 0; i < n; i++) for(int j = 0; j < n; j++) ans += find( -(c[i]+d[j]) ); printf("%lld\n", ans ); } return 0; }
D题:
签到题.
参考代码:
#include<stdio.h> int main(){ int n, x, y; scanf("%d",&n); while(n--) { scanf("%d%d", &x,&y); puts( x >= y ? "MMM BRAINS" : "NO BRAINS" ); } return 0; }
E题:
z = [ (x*10^y)+y ] % y == 0
若 Y 要满足此关系, 则 必定存在一个正整数 K, 使 K*Y 为 10^n, 例如 Y = 5, K = 2, Y = 25 ,K = 4
简单分析可以知道满足此要求的Y 有 1, 5, 25, 125 以及它们的 10^k 倍数
2^31-1 范围内, 总数不到100 ,则我们可以预处理出来后存储于辅助数组Q中,
则结果为 (1,m) - (1,n-1) (其中(1,m)为区间(1,m)中Q数组中出现的元素个数 )
参考代码:
#include<stdio.h> #include<algorithm> #include<math.h> using namespace std; typedef long long LL; const LL inf = 0xffffffff; LL Q[101]; int n; void init(){ n = 0; int a[5] = {1,2,5,25,125}; for(int k = 0; k < 5; k++) { for(int i = 0;;i++) { LL tmp = a[k]*(LL)(pow(10,i)); if( tmp > inf ) break; else Q[ n++ ] = tmp; } } sort( Q, Q+n ); // for(int i = 0; i < n; i++) // printf("%lld\n", Q[i]); } int sum( LL x ) { int res = 0; for(int i = 0; i < n; i++) if( x >= Q[i] ) res++; else return res; return res; } int main() { init(); LL a, b; while( scanf("%lld%lld",&a,&b) != EOF) { printf("%d\n", sum(b)-sum(a-1) ); } return 0; }
F题:
题目大意:
密文翻译,密文由小写字母构成,现给出a-z的对应密文字符。
现有一段密文和明文,但是被打断了,仅知道密文在前,明文在后,
让你输出最短的密文+明文序列。
解法二: 此题也可使用扩展KMP
解题思路:
取原串后面半截,同原串译文匹配,记录下能匹配到的最大长度
即为答案。这里有点小问题,待笔者斟酌后再行修改, 先给出目前解题代码
#include<iostream> #include<string> #include<map> using namespace std; const int N = 101010; map<char,char>mm; void solve(string s1,string s2){ int mid = ((int)s1.length()+1)/2; string s = s1.substr( mid, (int)s1.length()-mid+1 ); int pos = 0,cnt = 0; for(int i = 0; i < (int)s.length(); i++){ if(s[i] == s2[pos]) pos++; else pos = 0; } int len = mid+ ((int)s.length()-pos); //cout << "s1.length = " << s1.length() << endl; //cout << "s2.length = " << s2.length() << endl; string ans1 = s1.substr(0,len); string ans2 = s2.substr(0,len); cout << ans1+ans2 << endl; } int main(){ int t; cin>>t; while(t--){ string tab, s1, s2; cin>>tab>>s1; mm.clear(); for(int i = 0; i < 26; i++ ){ mm[ tab[i] ] = i+'a'; } for(int i = 0; i < (int)s1.length(); i++){ s2 += mm[ s1[i] ]; } // s2[(int)s1.length()] = '\0'; solve(s1,s2); } return 0; }
G题:
解法一: 贪心,按 DPS/HP的比例击杀对方英雄
解法二: DP
#include<stdio.h> #include<stdlib.h> #include<algorithm> #include<string.h> #include<iostream> using namespace std; struct node{ int hp, dps; double k; void read(){ scanf("%d%d",&dps,&hp); k = 1.0*dps/hp; } bool operator < (node tmp)const { return k > tmp.k; } }h[21]; int main(){ int n; while( scanf("%d", &n) != EOF) { for(int i = 0; i < n; i++) h[i].read(); sort( h, h+n ); int sum[21]; memset( sum, 0, sizeof(sum)); for(int i = 0; i < n; i++) { if( i == 0 ) sum[i] = h[i].dps; else sum[i] = sum[i-1]+h[i].dps; // printf("sum[%d] = %d\n", i, sum[i] ); } int res = sum[n-1]*h[0].hp; for(int i = 1; i < n; i++) { res += (sum[n-1]-sum[i-1])*h[i].hp; } printf("%d\n", res); } return 0; }
#include<iostream> #include<algorithm> using namespace std; const int inf = 0x7fffffff; const int N = (1<<21); int dp[N],hp_sum[N]; int hp[25], dps[25], n; int main(){ while(scanf("%d",&n)==1){ for(int i = 0; i < n; i++) scanf("%d%d",dps+i,hp+i); memset(hp_sum,0,sizeof(hp_sum)); for(int mask = 0; mask < (1<<n); mask++){ int i = 0, k = mask; dp[mask] = inf; while(k){ if(k&1) hp_sum[mask] += hp[i]; k >>= 1; i++; } //printf("hp_sum[%d] = %d\n", mask, hp_sum[mask]); } dp[0] = 0; for(int mask = 1; mask < (1<<n); mask++){ for(int i = 0; (1<<i) <= mask; i++){ if( (mask>>i)&1 == 1 ){ dp[mask] = min(dp[mask], dp[mask-(1<<i)]+hp_sum[mask]*dps[i]); } } } printf("%d\n", dp[(1<<n)-1]); // for(int mask = 0; mask < (1<<n); mask++) // printf("%d\n", dp[mask] ); } return 0; }
H题:
对于任意一点 (x0,y0), 其他点( xi,yi )到其距离和为
| x0 - xi | + | y0 - yi | (其中i 取值范围为 [1,n] 中去除x0,y0点)
我们可以分别求 x, y,
将x从小到大排序后, 对于小于 x0的, 有 ( i - 1 ) * x0 - sum( 0,i-1 ) ( sum( i,j )表示 i, j区间求和 )
对于大于x0的,有 sum( i, n ) - ( n - i ) * x0
同样的方法处理Y, 处理前缀和时间复杂度为O(n), 排序时间复杂度为 O(nlog(N))
总体时间复杂度为 O( Nlog(N) )
参考代码:
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<algorithm> using namespace std; #define MIN(a,b) (a)<(b)?(a):(b) typedef __int64 LL; const int N = 100007; struct node{ int x, y, id; }p[N]; int n; LL A[N], B[N], sum[N]; bool cmpx( node a, node b){ return a.x < b.x; } bool cmpy( node a, node b){ return a.y < b.y; } int main(){ int T; scanf("%d", &T); while( T-- ){ scanf("%d",&n); for(int i = 1; i <= n; i++) { scanf("%d%d", &(p[i].x), &(p[i].y) ); p[i].id = i; } // solve x sort( p+1, p+n+1, cmpx ); sum[0] = 0; for(int i = 1; i <= n; i++) sum[i] = sum[i-1]+p[i].x; for(int i = 1; i <= n; i++) { A[ p[i].id ] = 1LL*p[i].x*(i-1) - sum[i-1]; A[ p[i].id ] += (sum[n] - sum[i]) - 1LL*p[i].x*(n-i); } // solve y sort( p+1, p+n+1, cmpy ); sum[0] = 0; for(int i = 1; i <= n; i++) sum[i] = sum[i-1]+p[i].y; for(int i = 1; i <= n; i++) { B[ p[i].id ] = 1LL*p[i].y*(i-1) - sum[i-1]; B[ p[i].id ] += (sum[n] - sum[i]) - 1LL*p[i].y*(n-i); } LL ans = A[1]+B[1]; for(int i = 2; i <= n; i++) { ans = MIN( ans, A[ p[i].id ]+B[ p[i].id ] ); } printf("%I64d\n", ans ); } return 0; }
I 题:
转换成线性同余方程后,使用扩展GCD解即可.
设 k1 次后两青蛙相遇, 则满足条件:
( k1 * m + x ) % L = ( k1 * n + y ) % L
=> ( k1 * m + x ) % L - ( k1 * n + y ) % L = 0
=> ( ( k1 * m + x ) - ( k1 * n + y ) ) % L = 0
=> ( k1 * ( m - n ) + ( x - y ) ) % L = 0
=> k1 * ( m - n ) + ( x - y ) = L * k2 ( 其中 k2 为存在正整数 K2)
=> 令 a = m - n , b = -L, c = y-x
得 a * k1 + b * k2 = c 得线性同余方程, 使用 扩展GCD计算 得出解 即可 扩展GCD证明过程见笔者Q空间. Q: 361072774
#include<stdio.h> #include<math.h> typedef long long LL; LL exgcd( LL a, LL b, LL &x, LL &y) { if( b == 0 ){ x = 1; y = 0; return b; } int r = exgcd( b, a%b, x, y ); int t = x; x = y; y = t - (a/b)*y; return r; } LL gcd( LL a, LL b ) { return b == 0 ? a : gcd( b, a%b ); } int main(){ LL x, y, m, n, L; while( scanf("%lld%lld%lld%lld%lld", &x,&y,&m,&n,&L) != EOF) { LL a = m-n, b = -L, c = y-x; LL x, y, d = gcd( a, b ); if( c%d != 0 ) puts("Impossible"); else{ a /= d; b /= d; c /= d; exgcd( a, b, x, y ); x = c*x%b; if( x < 0 ) x += b; printf("%lld\n", x ); } } return 0; }
以上内容仅为笔者解题思路. 由于时间仓促,加之能力有限,欢迎读者指出不当处!