2014 Multi-University Training Contest 9 部分题目解题报告
SYSU出题,感觉变成了手速场。。
HDOJ 4960 Another OCD Patient
题意:一个n<=5000的序列,每个数v[i],可以合并连续的一段加和为一个数,合并一段长度为i的费用为a[i],合并过的不能再合并。问使序列成为对称的序列的最小费用。
分析:显然会是dp。然后并不是很好想= =。。只能yy一些四维五维的dp。这时先贪心地处理一下。考虑两侧,如果不相等,只能向内合并直到相等,这样就可以每次给比较小的一侧多合并一块,直到相等,那么这一大块是至少要合并的一块,递归处理可以得到一个新的已经对称的序列,这时候数的值已经不重要了,只要记录每个块是由多少个数得到的。又想到有对称性,所以其实我们只要对一侧做dp。dp[i]表示前i块和对面的后i块合并的最小费用,dp[i] = min(dp[j] + cost(j+1, i) + cost(对面的j到i)),这就是1d1d的dp,直接2500^2地做。然后我们再枚举i,把dp[i]和对应中间剩下的一段的cost加上,取个最小的。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 6 typedef long long LL; 7 struct node{ 8 int ct, p; 9 } b[5010]; 10 int n; 11 int v[5010], a[5010], dp[5010]; 12 bool cmp(node a, node b) 13 { 14 return a.p < b.p; 15 } 16 int main() 17 { 18 while(scanf("%d", &n) && n) 19 { 20 for (int i = 1; i <= n; i++) scanf("%d", v+i); 21 for (int i = 1; i <= n; i++) scanf("%d", a+i); 22 int p1 = 0, p2 = n+1, ct1 = 0, ct2 = 0, num = 0, pos1 = 1, pos2 = n; 23 LL sum1 = 0, sum2 = 0; 24 while(p1 + 1 < p2) 25 { 26 if (sum2 < sum1) sum2 = sum2 + v[--p2], ct2 ++; 27 else sum1 = sum1 + v[++p1], ct1 ++; 28 if (sum1 == sum2){ 29 b[++num].ct = ct1; 30 b[num].p = pos1 ++; 31 b[++num].ct = ct2; 32 b[num].p = pos2 --; 33 sum1 = sum2 = 0; 34 ct1 = ct2 = 0; 35 } 36 } 37 //if (sum1 != sum2){ 38 if (ct1 || ct2){ 39 b[++num].ct = ct1 + ct2; 40 b[num].p = pos1 ++; 41 } 42 sort(b+1, b+1+num, cmp); 43 // for (int i = 1; i <= num; i++) 44 // printf("%d %d\n", b[i].ct, b[i].p); 45 dp[0] = 0; 46 for (int i = 1; i * 2 <= num; i++){ 47 int sum1 = 0, sum2 = 0; 48 for (int j = i; j > 0; j--){ 49 sum1 += b[j].ct; sum2 += b[num-j+1].ct; 50 if (j == i) dp[i] = dp[i-1] + a[sum1] + a[sum2]; 51 else dp[i] = min(dp[i], dp[j-1] + a[sum1] + a[sum2]); 52 } 53 } 54 int ans = a[n], sum = 0; 55 for (int i = 1; i <= num/2; i++){ 56 sum += b[i].ct + b[num-i+1].ct; 57 ans = min(ans, dp[i] + a[n-sum]); 58 } 59 printf("%d\n", ans); 60 } 61 return 0; 62 }
HDOJ 4961 Boring Sum
题意:长度10w的序列,每个数为ai<=10w,相对离它最近且为它的倍数的数有两个,在它前面的为bi,在它后面的为ci,如果没有那就是它本身。问所有数的bi*ci的和。
分析:注意到ai的范围,就有了个o(nsqrt(n))的做法。定义f[i]为一个数的值为i,那么离它最近且为它的倍数的数是多少,初始为i。然后从前往后扫,当前的数为x,那么它的b[i]即f[x],然后再修改它的约数的值即f[j](j|x)。再从后往前做一次得到c[i]。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<cstring> 3 using namespace std; 4 5 typedef long long LL; 6 const int maxn = 100010; 7 int n; 8 int f[maxn+100], a[maxn+100], b[maxn+100]; 9 int main() 10 { 11 while(scanf("%d", &n) && n) 12 { 13 for (int i = 0; i < n; i++) scanf("%d", &a[i]); 14 for (int i = 0; i <= maxn; i++) f[i] = i; 15 for (int i = 0; i < n; i++){ 16 int x = a[i]; 17 b[i] = f[x]; 18 for (int j = 1; j * j <= x; j++) 19 if (x % j == 0) f[x/j] = f[j] = x; 20 } 21 LL ans = 0; 22 for (int i = 0; i <= maxn; i++) f[i] = i; 23 for (int i = n-1; i >= 0; i--){ 24 int x = a[i]; 25 ans += (LL)f[x] * b[i]; 26 for (int j = 1; j * j <= x; j++) 27 if (x % j == 0) f[x/j] = f[j] = x; 28 } 29 //printf("%I64d\n", ans); 30 printf("%lld\n", ans); 31 } 32 return 0; 33 }
HDOJ 4965 Fast Matrix Calculation
题意:1000*6的矩阵和6*1000的矩阵相乘,然后求他们的一个比较大的幂次,最后统计每位mod6后的和。
分析:智商题。1000*1000的矩阵乘法就跪了,别提矩阵快速幂了。但是猛然发现矩阵乘法有结合性,(AB)^n即A(BA)^(n-1)B,然后就只要做6*6的矩阵快速幂了。。队友写了好几个矩阵乘法,因为大小不同。。晚上补题我就想写个重载就好了,结果一直爆栈。。1000*1000怎么传参都跪,运行就溢出。。无奈开成100*100,在本地调试完改成1000加上hdu的开栈指令勉强过了= =。。还很慢。。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #pragma comment(linker, "/STACK:1024000000,1024000000") 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 6 struct mat{ 7 int row, col; 8 int m[1010][1010]; 9 void clear() 10 { 11 row = col = 0; 12 memset(m, 0, sizeof(m)); 13 } 14 void operator =(const mat &p){ 15 row = p.row; 16 col = p.col; 17 for (int i = 0; i < row; i++) 18 for (int j = 0; j < col; j++) 19 m[i][j] = p.m[i][j]; 20 } 21 mat operator *(const mat &p); 22 } a, b, c, tmp, sum, ans; 23 24 mat mat::operator *(const mat &p){ 25 ans.row = row; 26 ans.col = p.col; 27 for (int i = 0; i < ans.row; i++) 28 for (int j = 0; j < ans.col; j++) 29 ans.m[i][j] = 0; 30 for (int i = 0; i < row; i++) 31 for (int j = 0; j < col; j++) 32 if (m[i][j]){ 33 for (int k = 0; k < p.col; k++) 34 ans.m[i][k] += m[i][j] * p.m[j][k]; 35 } 36 for (int i = 0; i < row; i++) 37 for (int j = 0; j < p.col; j++) 38 ans.m[i][j] %= 6; 39 return ans; 40 } 41 42 void print(const mat &tmp) 43 { 44 for (int i = 0; i < tmp.row; i++) 45 for (int j = 0; j < tmp.col; j++) 46 printf("%d%c", tmp.m[i][j], j == tmp.col-1? '\n': ' '); 47 } 48 49 int n, k; 50 int main() 51 { 52 while(scanf("%d %d", &n, &k) && n != 0 && k != 0) 53 { 54 for (int i = 0; i < n; i++) 55 for (int j = 0; j < k; j++) 56 scanf("%d", &a.m[i][j]); 57 for (int i = 0; i < k; i++) 58 for (int j = 0; j < n; j++) 59 scanf("%d", &b.m[i][j]); 60 a.row = n, a.col = k; 61 b.row = k, b.col = n; 62 tmp = b * a; 63 // print(tmp); 64 sum.row = sum.col = tmp.row; 65 for (int i = 0; i < sum.row; i++) 66 for (int j = 0; j < sum.col; j++) 67 sum.m[i][j] = 0; 68 for (int i = 0; i < sum.row; i++) 69 sum.m[i][i] = 1; 70 //tmp = mat_powmod(tmp, n*n-1, 6); 71 int exp = n * n - 1; 72 while(exp){ 73 if (exp & 1) sum = sum * tmp; 74 exp >>= 1; 75 tmp = tmp * tmp; 76 } 77 c = a * sum * b; 78 int ans = 0; 79 for (int i = 0; i < c.row; i++) 80 for (int j = 0; j < c.col; j++) 81 ans += c.m[i][j]; 82 printf("%d\n", ans); 83 } 84 return 0; 85 }
HDOJ 4968 Improving the GPA
题意:给你百分制平均分和课程门数,问gpa最高最低可以是多少。
分析:最多10门课,会想到是枚举搜索一类,正解就是枚举,枚举每个gpa段有几门,就可以算出最高和最低的可能的百分制分数,看给的分数是否在范围内,合法方案取最值即可。不过思考了一下,其实是有贪心策略的。平时考试最讨厌的就是89,84,79这类分数,考到90,85,60啥的感觉很幸运,这个也是同理。比如算最低分,我们先给所有课69分,然后算性价比,发现100分性价比很低,浪费很多分数,所以策略就是尽量多地凑100,剩下分数全给一个69分。最高分同理,先给60,然后都给到85,剩余分数给同一门课(计算发现至少不差)。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<cstdlib> 7 #include<set> 8 #include<map> 9 #include<queue> 10 #include<ctime> 11 #include<string> 12 using namespace std; 13 14 int T, ave, n; 15 double gpa[] = {2.0, 2.5, 3.0, 3.5, 4.0}; 16 int ct[10]; 17 double cal(int ct[]) 18 { 19 //for (int i = 0; i < 5; i++) 20 // printf("%d\n", ct[i]); 21 double ret = 0; 22 for (int i = 0; i < 5; i++) 23 ret = ret + gpa[i] * (double)ct[i]; 24 return ret / n; 25 } 26 double calmin(int ave, int tot, int n) 27 { 28 if (ave <= 9) return 2.0000; 29 tot -= 9 * n; 30 memset(ct, 0, sizeof(ct)); 31 ct[0] = n; 32 while(tot){ 33 ct[0]--; 34 if (tot >= 31){ 35 tot -= 31; 36 ct[4] ++; 37 } 38 else{ 39 if (tot >= 16) ct[4] ++; 40 else if (tot >= 11) ct[3] ++; 41 else if (tot >= 6) ct[2] ++; 42 else ct[1] ++; 43 tot = 0; 44 } 45 } 46 return cal(ct); 47 } 48 double calmax(int ave, int tot, int n) 49 { 50 if (ave >= 25) return 4.0000; 51 memset(ct, 0, sizeof(ct)); 52 ct[0] = n; 53 while(tot){ 54 if (tot > 9) ct[0] --; 55 else {tot = 0; continue;} 56 if (tot < 25){ 57 if (tot >= 20) ct[3] ++; 58 else if (tot >= 15) ct[2] ++; 59 else ct[1] ++; 60 tot = 0; 61 } 62 else {tot -= 25; ct[4] ++;} 63 } 64 return cal(ct); 65 } 66 int main() 67 { 68 scanf("%d", &T); 69 while(T--) 70 { 71 scanf("%d %d", &ave, &n); 72 ave -= 60; 73 int tot = ave * n; 74 double ansmin, ansmax; 75 ansmin = calmin(ave, tot, n); 76 ansmax = calmax(ave, tot, n); 77 printf("%.4f %.4f\n", ansmin, ansmax); 78 } 79 return 0; 80 }
HDOJ 4969 Just a Joke
题意:女生在半径为R的圆上做匀速圆周运动,男生从圆心开始追,速度大小恒定,保持圆心,男孩女孩为一条直线。男生速度更快,所以是肯定能追上的,但他只能跑D的距离,所以问能不能追到。
分析:大雾没学好啊。。开始以为是阿基米德螺线了,对着那个整了半天一直WA,后来发现阿基米德螺线是保持动射线上的等速(其实速度是变大的),这题是线速度恒定。。所以并不是高数题,而是大雾题。。把男生速度分解,v切^2 + v法^2 = v男^2,因为保持一条直线,所以角速度相同,v切/r = v女生/R,r是径向距离(可以理解成男生当前的半径),所以dr/dt = v法,这几个式子整理一下,把dr和dt分离开,只要做一个三角换元就可以得到t关于r的表达式,代入r=R,得到追上的时刻T,最后V男T和D比较一下就可以了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<cmath> 3 using namespace std; 4 5 const double eps = 1e-8; 6 int T; 7 double v1, v2, r, d; 8 int main() 9 { 10 scanf("%d", &T); 11 while(T--) 12 { 13 scanf("%lf%lf%lf%lf", &v1, &v2, &r, &d); 14 if (r*v2*asin(v1/v2)/v1 > d) puts("Why give up treatment"); 15 else puts("Wake up to code"); 16 } 17 return 0; 18 }
HDOJ 4970 Killing Monsters
题意:塔防。每个塔有攻击区间和攻击力,每个怪兽从某位置走到n,有生命值,问最后有多少活着。
分析:上来就当线段树了,区间修改区间查询。结果TLE了,一想其实这个写个o(n)的线段扫描就可以,结果队友敲了个线段扫描,不过是那种配合离散化的,需要排序的。。不过900+ms过了。。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<cstring> 3 using namespace std; 4 5 int n, m; 6 long long sum[100100]; 7 int ll[100100], dam[100100]; 8 int main() 9 { 10 while(scanf("%d", &n) && n) 11 { 12 scanf("%d", &m); 13 int l, r, d; 14 memset(ll, 0, sizeof(ll)); 15 for (int i = 0; i < m; i++){ 16 scanf("%d %d %d", &l, &r, &d); 17 ll[l] += d; ll[r+1] += -d; 18 } 19 dam[0] = 0; sum[n+1] = 0; 20 for (int i = 1; i <= n; i++) dam[i] = dam[i-1] + ll[i]; 21 for (int i = n; i >= 1; i--) sum[i] = sum[i+1] + dam[i]; 22 int k, ans = 0, pos; 23 long long h; 24 scanf("%d", &k); 25 for (int i = 0; i < k; i++){ 26 scanf("%lld %d", &h, &pos); 27 if (sum[pos] < h) ans++; 28 } 29 printf("%d\n", ans); 30 } 31 return 0; 32 }