首页 > 代码库 > 51nod四级题(第一波汇总)
51nod四级题(第一波汇总)
1051 最大子矩阵和()
思路:
用前缀和维护成块 然后 和 最大子序列和 同理。前缀和这块 O(n2) 后面最大子序列和 O(n),O(n3)。
1 #include <bits/stdc++.h> 2 using namespace std; 3 typedef long long LL; 4 const int maxn = 500 + 5; 5 6 LL M[maxn][maxn], sum[maxn][maxn];// 第i行前j列的和 7 8 int main() 9 { 10 int n, m; 11 scanf("%d%d", &m, &n); 12 memset(sum, 0LL, sizeof(sum)); 13 for(int i = 1; i <= n; i++) 14 { 15 for(int j = 1; j <= m; j++) 16 { 17 scanf("%I64d", &M[i][j]); 18 sum[i][j] = sum[i][j - 1] + M[i][j]; 19 } 20 } 21 22 LL Max = 0;// 23 24 for(int l = 1; l <= m; l ++) 25 { 26 Max = max(Max, sum[1][l]); 27 for(int r = l; r <= m; r++) 28 { 29 LL temp = 0; 30 for(int h = 1; h <= n; h++) 31 { 32 temp += sum[h][r] - sum[h][l - 1]; 33 if(temp < 0) temp = 0; 34 else if(temp > Max) Max = temp; 35 } 36 } 37 } 38 cout << Max << endl; 39 return 0; 40 }
1060 最复杂的数(反素数)
思路:
百度反素数,一切都明白了。
http://blog.csdn.net/ACdreamers/article/details/25049767这篇博客讲的很详细。把这几个例题看懂,然后这题就很好解决了。
!!!另外 你们可以尝试着不写maxcnt这个变量到dfs函数里头去,然后每次i从1取到64试试看,看看复杂度的差距。
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int maxn = 500 + 5; 4 const int mod = 1000000007; 5 typedef long long LL; 6 typedef unsigned long long ull; 7 const ull INF = ~0ULL; 8 9 //反素数的基础题 10 //求解1-n中约数数量最多的数字和它的约数数量。 11 ull ans, n; 12 LL ans_num; 13 int prim[16] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53}; 14 15 void dfs(int deep, ull temp, LL num, int maxcnt)//选择到哪一个素数了 当前的数的大小,因子的数量 当前质因数最多的数量 16 { 17 if(deep >= 16) return ;//素数用完了。 18 if(num == ans_num && temp < ans) 19 { 20 ans = temp; 21 } 22 if(num > ans_num) 23 { 24 ans_num = num, ans = temp; 25 } 26 27 for(int i = 1; i <= maxcnt; i++) 28 { 29 if(n / prim[deep] < temp) break; 30 dfs(deep + 1, temp *= prim[deep], num * (i + 1), i);//剪枝, 其实是n < temp * prim[deep] 31 32 } 33 } 34 35 int main() 36 { 37 int T; 38 scanf("%d", &T); 39 while(T--) 40 { 41 cin >> n; 42 ans = INF, ans_num = 0; 43 dfs(0, 1, 1, 64); 44 cout << ans << " " << ans_num << endl; 45 } 46 return 0; 47 }
1070 Bash游戏 V4(斐波那契博弈)
思路:
知道裴波那契博弈以后就很简单了。。有兴趣的看一下斐波那契博弈的证明。
斐波那契博弈:有一堆个数为n的石子,游戏双方轮流取石子,满足:
1)先手不能在第一次把所有的石子取完;
2)之后每次可以取的石子数介于1到对手刚取的石子数的2倍之间(包含1和对手刚取的石子数的2倍)。
约定取走最后一个石子的人为赢家,求必败态。
先手胜当且仅当n不是Fibonacci数。换句话说,必败态构成Fibonacci数列。
1 #include <bits/stdc++.h> 2 using namespace std; 3 typedef long long LL; 4 const int maxn = 500 + 5; 5 LL fib[100]; 6 int main() 7 { 8 fib[0] = 1, fib[1] = 2; 9 for(int i = 2; i <= 50; i++) 10 { 11 fib[i] = fib[i - 1] + fib[i - 2]; 12 } 13 int T; 14 scanf("%d", &T); 15 while(T--) 16 { 17 int n; 18 scanf("%d", &n); 19 int pos = lower_bound(fib, fib + 50, n) - fib; 20 if(fib[pos] == n) 21 { 22 puts("B"); 23 } 24 else 25 { 26 puts("A"); 27 } 28 } 29 return 0; 30 }
我把证明藏在下面,想看的自己点开。
1 这个游戏叫做Fibonacci Nim,肯定和Fibonacci数列:f[n]:1,2,3,5,8,13,21,34,55,89,… 有密切的关系。如果试验一番之后,可以猜测: 2 3 就像“Wythoff博弈”需要“Beatty定理”来帮忙一样,这里需要借助“Zeckendorf定理”(齐肯多夫定理):任何正整数可以表示为若干个不连续的Fibonacci数之和。 4 5 先看看FIB数列的必败证明: 6 7 1、当i=2时,先手只能取1颗,显然必败,结论成立。 8 9 2、假设当i<=k时,结论成立。 10 11 则当i=k+1时,f[i] = f[k]+f[k-1]。 12 13 则我们可以把这一堆石子看成两堆,简称k堆和k-1堆。 14 15 (一定可以看成两堆,因为假如先手第一次取的石子数大于或等于f[k-1],则后手可以直接取完f[k],因为f[k] < 2*f[k-1]) 16 17 对于k-1堆,由假设可知,不论先手怎样取,后手总能取到最后一颗。下面我们分析一下后手最后取的石子数x的情况。 18 19 如果先手第一次取的石子数y>=f[k-1]/3,则这小堆所剩的石子数小于2y,即后手可以直接取完,此时x=f[k-1]-y,则x<=2/3*f[k-1]。 20 21 我们来比较一下2/3*f[k-1]与1/2*f[k]的大小。即4*f[k-1]与3*f[k]的大小,由数学归纳法不难得出,后者大。 22 23 所以我们得到,x<1/2*f[k]。 24 25 即后手取完k-1堆后,先手不能一下取完k堆,所以游戏规则没有改变,则由假设可知,对于k堆,后手仍能取到最后一颗,所以后手必胜。 26 27 即i=k+1时,结论依然成立。 28 29 对于不是FIB数,首先进行分解。 30 31 分解的时候,要取尽量大的Fibonacci数。 32 33 比如分解85:85在55和89之间,于是可以写成85=55+30,然后继续分解30,30在21和34之间,所以可以写成30=21+9, 34 35 依此类推,最后分解成85=55+21+8+1。 36 37 则我们可以把n写成 n = f[a1]+f[a2]+……+f[ap]。(a1>a2>……>ap) 38 39 我们令先手先取完f[ap],即最小的这一堆。由于各个f之间不连续,则a(p-1) > ap + 1,则有f[a(p-1)] > 2*f[ap]。即后手只能取f[a(p-1)]这一堆,且不能一次取完。 40 41 此时后手相当于面临这个子游戏(只有f[a(p-1)]这一堆石子,且后手先取)的必败态,即先手一定可以取到这一堆的最后一颗石子。 42 43 同理可知,对于以后的每一堆,先手都可以取到这一堆的最后一颗石子,从而获得游戏的胜利。
1086 背包问题 V2(裸的多重背包)
思路:
裸的东西。。百度“背包九讲”吧,01背包,完全背包,多重背包。然后这个是用二进制优化的?。
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int maxn = 100 + 5; 4 5 6 int w[maxn], v[maxn], num[maxn]; 7 int f[50000 + 5]; 8 int n, V; 9 10 11 void zero(int c,int val) 12 { 13 for(int i = V; i >= c; i--) 14 f[i] = max(f[i], f[i-c] + val); 15 } 16 void comple(int c,int val) 17 { 18 for(int i = c; i <= V; i++) 19 f[i] = max(f[i], f[i-c] + val); 20 } 21 void multi(int c,int w,int num) 22 { 23 if(c * num >= V) 24 comple(c ,w); 25 else 26 { 27 int k = 1; 28 while(k < num)//小于等于 29 { 30 zero(k * c, k * w); 31 num -= k; 32 k *= 2; 33 } 34 zero(num * c, num * w); 35 } 36 } 37 38 int main() 39 { 40 scanf("%d%d", &n, &V); 41 for(int i = 0; i < n; i++) 42 { 43 scanf("%d%d%d", &w[i], &v[i], &num[i]); 44 } 45 for(int i = 0; i < n; i++) 46 { 47 multi(w[i], v[i], num[i]); 48 } 49 cout << f[V] << endl; 50 return 0; 51 }
1103 N的倍数(抽屉原理)
思路:
这题是spj,输出一种可行解就行了。抽屉原理嘛,就是sum = a[i]%n的加和,加和sum=0,说明从第一个数到当前这个数的和能被n整除,或者出现两个相同的sum,那么它们之间这段数字加和能被n整除。
其实这题感觉自己写的是有点丑的。
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int maxn = 50000 + 5; 4 int a[maxn]; 5 map<int, int>ma; 6 int main() 7 { 8 int n; 9 scanf("%d", &n); 10 for(int i = 0; i < n; i++) 11 { 12 scanf("%d", &a[i]); 13 } 14 int sum = 0, ok = 0, left = 0, right; 15 for(int i = 0; i < n; i++) 16 { 17 sum = (sum + a[i]) % n; 18 19 if(sum == 0) 20 { 21 ok = 1; 22 right = i; 23 break; 24 } 25 else if(ma.count(sum) == 0) 26 { 27 ma[sum] = i; 28 } 29 else//ma[sum]!=0 30 { 31 ok = 1; 32 left = ma[sum] + 1, right= i; 33 break; 34 } 35 36 } 37 if(ok) 38 { 39 printf("%d\n", right - left + 1); 40 for(int i = left; i <= right; i++) 41 { 42 printf("%d\n", a[i]); 43 } 44 } 45 else 46 { 47 puts("No Solution"); 48 } 49 return 0; 50 }
1109 01组成的N的倍数(bfs)
思路:
这题其实是一个bfs,就是你的最高位肯定是1吧,从1开始不断的后面加,每次加0或者加1,然后一小段一小段求%,而且很明显队列里最多只有n个元素,因为%n只有n种情况。详细见代码吧。
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int maxn = 1e6 + 5; 4 int pre[maxn], p[maxn]; 5 6 void show(int x) 7 { 8 if(x != -1) 9 { 10 show(pre[x]); 11 printf("%d", p[x]); 12 } 13 } 14 15 16 int main() 17 { 18 int n; 19 scanf("%d", &n); 20 21 queue<int>que; 22 que.push(1); 23 pre[1] = -1; 24 p[1] = 1; 25 while(!que.empty()) 26 { 27 int top = que.front();que.pop(); 28 29 int p1 = (top * 10) % n; 30 int p2 = (top * 10 + 1) % n; 31 32 if(p1 == 0) 33 { 34 show(top); 35 puts("0");//因为p1是top后加上0求余得到的 36 break; 37 } 38 else if(p2 == 0) 39 { 40 show(top); 41 puts("1");//因为p2是top后加上1求余得到的 42 break; 43 } 44 if(pre[p1] == 0) 45 { 46 pre[p1] = top; 47 p[p1] = 0;//因为p1是top后加上0求余得到的 48 que.push(p1); 49 } 50 if(pre[p2] == 0) 51 { 52 pre[p2] = top; 53 p[p2] = 1;//因为p2是top后加上1求余得到的 54 que.push(p2); 55 } 56 } 57 puts(""); 58 59 return 0; 60 } 61 62 //其实p数组是一个剪枝, 就是当同余的时候吧,如果这个数没出现过 那么更新pre,压进队,不然就不更新pre。因为同余的话,之前出现的数肯定更小。
先睡了orz
51nod四级题(第一波汇总)