2017 ACM/ICPC Asia Regional Shenyang Online(部分题解)

HDU 6197 array array array

题意

输入n和k,表示输入n个整数和可以擦除的次数k,如果至多擦除k次能是的数组中的序列是不上升或者是不下降序列,就是魔力数组,否则不是。

解题思路

分别求最长不下降和不上升子序列的长度,看不符合要求的数字和k的大小。

这里使用优化后的求解最长不上升和不下降子序列的算法。

 1 #include <cstdio>
 2 #include <algorithm>
 3 using namespace std;
 4 
 5 const int maxn = 100010;
 6 const int inf = 99999999;
 7 int A[maxn], B[maxn], g[maxn], d[maxn];
 8 int n, t;
 9 
10 int main()
11 {
12     int T;
13     scanf("%d", &T);
14     while(T--) {
15         scanf("%d%d", &n, &t);
16         for(int i = 0; i < n; i++) {
17             scanf("%d", &A[i]);
18         }
19         
20         for(int i = 1; i <= n; i++) {
21             g[i] = inf;
22         }
23         for(int i = 0; i < n; i++) {
24             int k = upper_bound(g + 1, g + n + 1, A[i]) - g;
25             d[i] = k;
26             g[k] = A[i];
27         }
28         int s1 = 0;
29         for(int i = 0; i < n; i++)
30             if(s1 < d[i])
31                 s1 = d[i];
32         
33         int j = 0;
34         for(int i = n - 1; i >= 0; i--) {
35             B[j++] = A[i];
36         }
37         for(int i = 1; i <= n; i++) {
38             g[i] = inf;
39         }
40         for(int i = 0; i < n; i++) {
41             int k = upper_bound(g + 1, g + n + 1, B[i]) - g;
42             d[i] = k;
43             g[k] = B[i];
44         }
45         int s2 = 0;
46         for(int i = 0; i < n; i++)
47             if(s2 < d[i])
48                 s2 = d[i];
49         
50         //printf("%d %d\n",s1,s2);
51         int ans = max(s1, s2);//最长上升或者下降子序列,取大值意味者修改更少 
52         if(n - t <= ans)
53             printf("A is a magic array.\n");
54         else
55             printf("A is not a magic array.\n");
56     }
57     return 0;
58 }

HDU 6195 cable cable cable

题意

给出n个屏幕和k个光源,要求的是从n个屏幕中任意的k个屏幕显示不同颜色的光源至少需要几根电缆

解题思路

有两个出发点

从屏幕出发,前k个屏幕都单独和k个光源连接,剩下n-k个屏幕应该和每个光源都有连接,否则随意挑的时候,可能会出现重复。故总的电缆数是k+(n-k)*k。

从光源出发,每个光源至少应该和(n-k+1)个屏幕相连,这样选出的k个屏幕才能可以组成k种不同的光源点。故总的电缆数是(n-k+1)* k。

 1 #include <cstdio>
 2 
 3 int main()
 4 {
 5     long long n, k;
 6     while(scanf("%lld%lld", &n, &k) != EOF) {
 7         printf("%lld\n", k *( n - k + 1));
 8     }
 9     return 0;
10 } 

HDU 6198 number number number

题意

首先定义了斐波那契数列F0 = 0,F1 = 1;

             F= Fn - 1 + Fn - 2(n>=2).

给出一个k,如果一个正整数能够由连续(可重复)的斐波那契数加和得到,就是一个好数,否则就是一个坏数。现在给出一个k,问最小的坏数是多少。

解题思路

首先应该想到的应该是当k = 1 时,最小的坏数是5 - 1 = 4;

          当k = 2 时,最小的坏数是13 - 1 = 12;

          当k = 3 时,最小的坏数是34 - 1 = 33;

然后如果拿5为斐波那契数的第一项,8位第二项,可以发现规律就是对于输入的k,答案是第f(2*k+1)项斐波那契数 - 1。

不过题中给出的k的范围是(1<=k<=10^9),最大可能是第2*10^9项斐波那契数。就需要矩阵快速幂计算某一项斐波那契数了。代码如下:

 1 #include <cstdio>
 2 #include <cstring>
 3 typedef long long ll;
 4 const int mod = 998244353;
 5 struct Matrix {
 6     ll x[2][2];
 7 };
 8 Matrix Mmul(Matrix a, Matrix b) {
 9     Matrix tmp;
10     memset(tmp.x, 0, sizeof(tmp.x));
11     for(int i = 0; i < 2; i++) {
12         for(int j = 0; j < 2; j++) {
13             for(int k = 0; k < 2; k++) {
14                 tmp.x[i][j] = (tmp.x[i][j] + a.x[i][k] * b.x[k][j] % mod) % mod;
15             }
16         }
17     }
18     return tmp;
19 }
20 Matrix Mqpow(Matrix a, ll n) {
21     Matrix tmp;
22     for(int i = 0; i < 2; i++) {
23         for(int j = 0; j < 2; j++) {
24             tmp.x[i][j] = i == j ? 1 : 0;
25         }
26     }
27     
28     while(n) {
29         if(n&1)
30             tmp = Mmul(tmp, a);
31         a = Mmul(a, a);
32         n >>= 1;
33     }
34     return tmp;
35 }
36  int main()
37 {
38     ll k;
39     while(scanf("%lld", &k) != EOF) {
40         Matrix st;
41         st.x[0][0] = 1; st.x[0][1] = 1;
42         st.x[1][0] = 1; st.x[1][1] = 0;
43         
44         Matrix init;
45         init.x[0][0] = 1; init.x[0][1] = 0;
46         init.x[1][0] = 1; init.x[1][1] = 0;
47         
48         st = Mqpow(st, 2 * k + 1);
49         st = Mmul(st, init);
50         /*for(int i = 0; i < 2; i++) {
51             for(int j = 0; j < 2; j++) {
52                 printf("%lld ", st.x[i][j]);
53             }
54             puts("");
55         }*/
56         printf("%lld\n", (st.x[0][0] - 1 + mod) % mod);
57     }    
58     return 0;    
59 }

HDU 6201 transaction transaction transaction

题意

给出由n-1条边联通的n个顶点的图(可以将其方便的看为一棵树),每个顶点都有卖一本书的价格,但是每个地方都不同,一个商人想从中赚取差价,但是点与点之间还有路费,问这个人最多能赚取多少钱?

解题思路

先将其转换为一棵树来处理,定义两种状态分别是:dp[u][0] 表示从u及其子孙结点中的一点买书(包括所花的路费)的最小值(由于是负数,所以也即求最大值)

                                                                  dp[u][1]表示从u及其子孙结点中的一点卖书(包括所花的路费)的最大值

那么从u结点及其子孙结点买再卖给u及其子孙结点的利润,也就是一个结点的利润是dp[u][0] +dp[u][1]。最后的答案就是取所有结点的最大值即可。代码如下:

 1 #include <cstdio>
 2 #include <vector>
 3 #include <algorithm>
 4 using namespace std;
 5 const int maxn = 100000 + 10;
 6 
 7 struct Node {
 8     int v, w;
 9     Node(int _v, int _w):v(_v),w(_w){};
10 };
11 
12 vector<Node> vec[maxn];
13 int val[maxn];
14 int n, ans;
15 int dp[maxn][2];
16 
17 void dfs(int u, int pre) {
18     dp[u][0] = -val[u];//从u结点及其子孙结点中的一点买书所花的最少的钱(一定是一个负数) 
19     dp[u][1] = val[u]; //从u结点及其子孙结点中的一点卖书所赚的最多的钱 
20     for(int i = 0; i < vec[u].size(); i++) {
21         int v = vec[u][i].v;
22         int w = vec[u][i].w;
23         if(v == pre) continue;
24         dfs(v, u);
25         //当前结点(买书)和它的子结点(买书)“加上”路费的值 谁更大(都是负数) 
26         dp[u][0] = max(dp[u][0], dp[v][0] - w); 
27         //当前结点(卖书)和它的子结点(卖书)减去路费的值 谁更大 
28         dp[u][1] = max(dp[u][1], dp[v][1] - w);
29     }
30     ans = max(ans, dp[u][0] + dp[u][1]);
31 }
32 
33 int main()
34 {
35     int T;
36     scanf("%d", &T);
37     while(T--) {
38         scanf("%d", &n);
39         for(int i = 1; i <= n; i++) {
40             scanf("%d", &val[i]);
41             vec[i].clear();
42         }                
43         for(int i = 1; i <= n - 1; i++) {
44             int u, v, w;
45             scanf("%d%d%d", &u, &v, &w);
46             vec[u].push_back(Node(v, w));
47             vec[v].push_back(Node(u, w));
48         }
49         ans = 0;
50         dfs(1, -1);
51         printf("%d\n", ans);
52     }    
53     return 0;
54 }

 HDU 6205 card card card

题意

给出n摞牌,排成一行,然后n个数表示每摞有多少张牌,还给出每一摞牌的罚值,我们可以通过将第一摞移动到最后来改变次数,使得拿更多的牌,拿取一摞的规则是一张一张的拿到手中并且使他们面朝上,拿完之后翻转一定数目的牌使之朝下,如果能够翻转的牌数等于该摞牌的罚值,表示可以继续往后拿,直至拿完。问最少移动几次使得拿取最多的牌。

解题思路

问循环移动的次数,我们先将其复制一遍到它的后面,然后通过最大子段和的思想,每次选取满足条件的最大子段,不同的是限制的条件是a[i]-b[i]的差要大于等于0,而结果是所有牌的数目,所以需要分开计算。代码如下:

 1 #include <cstdio>
 2 const int inf = 99999999;
 3 const int maxn = 2000000 + 10;
 4 int a[maxn], b[maxn];
 5 
 6 int main()
 7 {
 8     int n;
 9     while(scanf("%d", &n) != EOF) {
10         for(int i = 1; i <= n; i++) {
11             scanf("%d", &a[i]); 
12             a[i + n] = a[i];
13         }
14         int x;
15         for(int i = 1; i <= n; i++) {
16             scanf("%d", &x); 
17             b[i] = a[i] - x;
18             b[i + n] = b[i];
19         }
20         
21         int sum1 = 0, sum2 = 0;
22         int ans = 0;
23         int l = 0, r = 0, tmp = 1;
24         for(int i = 1; i <= 2 * n; i++) {
25             sum1 += a[i];
26             sum2 += b[i];
27             if(sum1 > ans) {
28                 ans = sum1;
29                 l = tmp;
30                 r = i;
31             }
32             if(sum2 < 0) {
33                 sum1 = 0;
34                 sum2 = 0;
35                 tmp = i + 1;
36                 if(tmp > n + 1)
37                     break;
38             }
39         }
40         printf("%d\n", l-1); 
41     }    
42     return 0;
43 } 

 

posted @ 2018-10-24 20:47  Reqaw  阅读(246)  评论(0编辑  收藏  举报