2017 ACM/ICPC Asia Regional Shenyang Online(部分题解)
题意
输入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 }
题意
给出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 }
题意
首先定义了斐波那契数列F0 = 0,F1 = 1;
Fn = 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 }
题意
给出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 }