首页 > 代码库 > 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 }
View Code

 

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 }
View Code

 

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 }
View Code


我把证明藏在下面,想看的自己点开。

技术分享
 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+934 
35 依此类推,最后分解成85=55+21+8+136 
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 同理可知,对于以后的每一堆,先手都可以取到这一堆的最后一颗石子,从而获得游戏的胜利。
View Code

 

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 }
View Code

 

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 }
View Code

 

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。因为同余的话,之前出现的数肯定更小。
View Code

 

先睡了orz

 

51nod四级题(第一波汇总)