比赛-某训练赛 Round1 (Aug, 2017)
题目依次为 NKOJ 上 P3496 P4236 P3774 P2407
1.数三角形
方法很多, 比如推出三边 x y z 的限制关系, 然后加加减减得到计算式子
不过也可以用观察法, 暴力计算出 n 为 1 至 13 对应的结果为:
0 0 0 1 3 7 13 22 34 50 70 95 125
相邻两数差为:
0 0 1 2 4 6 9 12 16 20 25 30
这些相邻两数相邻差又为:
0 1 1 2 2 3 3 4 4 5 5
找到规律了, 如果结果第 i 项为第 i - 1 项的值加上 plus , 则每次 plus 增加的值为 (i / 2 - 1)
比如结果第 7 项值为 13 , 上一项值为 7 , 此时 plus 为 6 , 上一次的 plus 为 4 , 两个 plus 的差正正好为 (7 / 2 - 1) = 2
写出递推算法,时间复杂度 O(n) .
1 #include <stdio.h> 2 3 long long int ans, plus; 4 int n; 5 6 int main() 7 { 8 int i; 9 scanf("%d", &n); 10 for (i = 4; i <= n; ++i) 11 ans += plus += (i >> 1) - 1; 12 printf("%lld\n", ans); 13 return 0; 14 }
2.翻硬币
SG(i) 表示硬币在左起第 i 位的局面
0 对应 SG 的值为 0, SG(0) = 0
1 后续局面为 0 , 所以 SG(1) = mex(SG(0)) = 1
01后续局面为 00, 10, 所以 SG(2) = mex(SG(0), SG(1)) = 2
001 后续局面为 000, 100, 010, 所以 SG(3) = mex(SG(0), SG(1), SG(2)) = 3
0001 后续局面为 1000, 0100, 0010, 所以 SG(4) = mex(SG(1), SG(2), SG(3)) = 4
...
推导出 SG(i) = i mod 5
将正面朝上的硬币 SG 值按位异或就可以得出总的 SG 值了
1 #include <stdio.h> 2 3 int n, k, ans; 4 5 int main() 6 { 7 int i, j; 8 scanf("%d", &k); 9 while (k--) { 10 ans = 0; 11 scanf("%d", &n); 12 for (i = 1; i <= n; ++i) { 13 scanf("%d", &j); 14 if (j) 15 ans ^= i % 5; 16 } 17 if (ans) 18 printf("Yes\n"); 19 else 20 printf("No\n"); 21 } 22 return 0; 23 }
3.小鸟
动态规划, 用单调队列优化
f[i] 表示停留在第 i 棵树上的最小疲劳值, 可以得到方程:
f[i] = min{ f[j] + h[j]>=h[i]?1:0 }
i 的范围是 [1, n], j 的范围是 [i-k, i-1]
裸动规的时间复杂度是 O(nk) , 可以过 60% 的数据, 用单调队列优化后时间复杂度是 O(n)
构造一个根据 f 值递增的单调队列, 记录树的编号, 当队首对应树的编号小于 i - k 时说明无法飞到第 i 棵树, 将队首出队;
操作之后的队首对应的就是可以转移到 f[i] 的最小 f[j] 值, 再根据高度判断疲劳值是否 +1, 然后将 f[i] 对应树的编号在保
持单调性的情况下入队
注意, 为了使 f[i] 尽量小, 即疲劳值尽量不增加, 应该使第 j 棵树的高度尽量高, 因此单调队列还应该在 f 相同时把 d (即树
高度)较大的放在队首
1 #include <stdio.h> 2 3 int n, q, k; 4 int d[500005], f[500005], que[500005]; 5 6 int main() 7 { 8 int i, j, head, tail; 9 scanf("%d", &n); 10 for (i = 1; i <= n; ++i) 11 scanf("%d", &d[i]); 12 scanf("%d", &q); 13 for (i = 1; i <= q; ++i) { 14 scanf("%d", &k); 15 f[1] = 0; 16 head = tail = 1; 17 que[tail++] = 1; 18 for (j = 2; j <= n; ++j) { 19 while (j - k > que[head]) 20 ++head; 21 f[j] = f[que[head]] + (d[que[head]] <= d[j]); 22 while (head < tail && (f[j] < f[que[tail - 1]] || f[j] == f[que[tail - 1]] && d[j] >= d[que[tail - 1]])) 23 --tail; 24 que[tail++] = j; 25 } 26 printf("%d\n", f[n]); 27 } 28 return 0; 29 }
4.乘车路线
法一.搜索 + 剪枝
首先随意用一种图论算法(Floyd-Warshall 也行, 毕竟 n 最大 100, 但我还是写了个 SPFA)得到起点到终点最小费用, 如果大于 k,
说明钱带得实在太少啦, 跑不动! 如果钱足够的话,就开始搜索咯
用 sid_w[i] 记录点 i 到终点 n 的最小费用
用 sid_t[i] 记录点 i 到终点 n 的最短长度(原题是“长度”,可训练赛的时候被改成了“时间”)
如果走最便宜的路钱还不够, 即 当前剩余费用 - 欲搜索道路费用 < 搜索目标点到终点最小费用 ,剪枝
如果走最短的路还比已经得出的合理最短路程远, 即 当前已走长度 + 欲搜索道路长度 >= 合理最短长度, 剪枝
另外还有一点就是搜索某个点时最好先标记, 搜索完之后再取消标记, 避免回路和环
1 #include <cstdio> 2 #include <algorithm> 3 4 const int INF = 100000000; 5 6 int n, k, m, ans = INF; 7 int que[1000005], sid_w[1000005], sid_t[1000005]; 8 int fst[105], nxt[10005], u[10005], v[10005], w[10005], t[10005]; 9 int anti_fst[105], anti_nxt[10005]; 10 bool bok[1000005], flag[1000005]; 11 char tt[30]; 12 13 inline void getnum(int &num) 14 { 15 char tt; 16 while ((tt = getchar()) < '0' || tt > '9'); 17 num = tt - '0'; 18 while ((tt = getchar()) >= '0' && tt <= '9') 19 num = num * 10 + tt - '0'; 20 return ; 21 } 22 23 inline void putnum(int num) 24 { 25 int top = 0; 26 do 27 tt[++top] = num % 10; 28 while (num /= 10); 29 while (top) 30 putchar(tt[top--] + '0'); 31 return ; 32 } 33 34 void anti_SPFA(int beg, int *dis, int *weight) 35 { 36 int head, tail, i, node; 37 //init 38 for (i = 1; i <= n; ++i) { 39 dis[i] = INF; 40 bok[i] = false; 41 } 42 43 dis[beg] = 0; 44 head = tail = 1; 45 que[tail++] = beg; 46 bok[beg] = true; 47 while (head < tail) { 48 bok[node = que[head++]] = false; 49 for (i = anti_fst[node]; i; i = anti_nxt[i]) { 50 if (dis[node] + weight[i] >= dis[u[i]]) 51 continue; 52 dis[u[i]] = dis[node] + weight[i]; 53 if (bok[u[i]]) 54 continue; 55 que[tail++] = u[i]; 56 bok[u[i]] = true; 57 } 58 } 59 return ; 60 } 61 62 void dfs(int node, int rem, int tim) 63 { 64 if (node == n) { 65 ans = std:: min(ans, tim); 66 return ; 67 } 68 int i; 69 for (i = fst[node]; i; i = nxt[i]) 70 if (!flag[v[i]] && rem - w[i] >= sid_w[v[i]] 71 && tim + t[i] + sid_t[v[i]] < ans) { 72 flag[v[i]] = true; 73 dfs(v[i], rem - w[i], tim + t[i]); 74 flag[v[i]] = false; 75 } 76 return ; 77 } 78 79 int main() 80 { 81 82 int i, t1, t2, j; 83 getnum(k); 84 getnum(n); 85 getnum(m); 86 87 88 for (i = 1; i <= m; ++i) { 89 getnum(u[i]); 90 getnum(v[i]); 91 getnum(t[i]); 92 getnum(w[i]); 93 nxt[i] = fst[u[i]]; 94 fst[u[i]] = i; 95 anti_nxt[i] = anti_fst[v[i]]; 96 anti_fst[v[i]] = i; 97 98 } 99 100 anti_SPFA(n, sid_w, w); 101 if (sid_w[1] > k) { 102 printf("NO\n"); 103 return 0; 104 } 105 106 anti_SPFA(n, sid_t, t); 107 108 dfs(1, k, 0); 109 putnum(ans); 110 111 return 0; 112 }
法二.二维最短路
dis[i][cost] 表示起点到 i 点在费用最大为 cost 的情况下的最短长度, 跑一个 SPFA 或者 Dijkstra 之后答案是 dis[n][k]
我写的是 SPFA :
1 #include <stdio.h> 2 3 const int INF = 100000000; 4 5 int fst[100005], nxt[100005], v[100005], l[100005], t[100005]; 6 int dis[10005][10005], que[100005]; 7 int n, k, w, u; 8 bool bok[100005]; 9 10 void SPFA(int beg) 11 { 12 int i, j, head, tail, node; 13 for (i = 1; i <= n; ++i) { 14 bok[i] = false; 15 for (j = 0; j <= w; ++j) 16 dis[i][j] = INF; 17 } 18 for (j = 0; j <= w; ++j) 19 dis[beg][j] = 0; 20 head = tail = 1; 21 que[tail++] = beg; 22 bok[beg] = true; 23 while (head < tail) { 24 bok[node = que[head++]] = false; 25 for (i = fst[node]; i; i = nxt[i]) 26 for (j = t[i]; j <= w; ++j) { 27 if (dis[node][j - t[i]] + l[i] >= dis[v[i]][j]) 28 continue; 29 dis[v[i]][j] = dis[node][j - t[i]] + l[i]; 30 if (bok[v[i]]) 31 continue; 32 que[tail++] = v[i]; 33 bok[v[i]] = true; 34 } 35 } 36 return ; 37 } 38 39 int main() 40 { 41 int i; 42 scanf("%d%d%d", &w, &n, &k); 43 for (i = 1; i <= k; ++i) { 44 scanf("%d%d%d%d", &u, &v[i], &l[i], &t[i]); 45 nxt[i] = fst[u]; 46 fst[u] = i; 47 } 48 SPFA(1); 49 if (dis[n][w] != INF) 50 printf("%d\n", dis[n][w]); 51 else 52 printf("NO\n"); 53 return 0; 54 }
改悔:
1题这类找通项公式题可以暴力算些结果再观察规律嘛, 毕竟对于数学渣来说推式子挺耗时间的
搜索剪枝一定要剪干净啊!剪一半真的只有 50 分, 不标记已访问点导致程序原地转圈也是!
说到二维最短路, 我已经不会写次短路的代码了!
单调队列一年前就看过课件,结果忘光了, DP + 单调队列 很常见嘛
题目:
P3496数三角形 | ||
|
问题描述
给出一个正整数n,从1,2,3.....n 中选出三个不同整数,使得以它们为三边长可以组成三角形,问
总共有多少种不同的三角形?
例如,n=5 时有三种:(2,3,4) , (2,4,5) , (3,4,5)
输入格式
一个正整数n
输出格式
一个整数,表示三角形的个数
样例输入 1
5
样例输出 1
3
样例输入 2
30
样例输出 2
1925
样例输入 3
8
样例输出 3
22
提示
【数据范围】
对于30%的数据,3<=n<=100
对于100%的数据,3<=n<=1,000,000
P4236翻硬币 | ||
|
问题描述
两个玩家在玩一个有趣的翻硬币游戏。
有 N 枚硬币排成一排,有的正面朝上,有的反面朝上。从左往右硬币按1 到N 编号。玩家轮流操作。每次操作,玩家选一枚正面朝上的硬币,将它翻转,同时在该硬币左侧连续四个硬币中,再任选一个硬币,将其翻转。
具体而言,假设第i号硬币正面朝上。若将第i号硬币翻转后,必须在编号为i-1,i-2,i-3,i-4的四个硬币中选一个进行翻转。若i<=4,则可只翻转i号硬币,也可以再在1到i-1之间选一个进行翻转。
谁没有硬币可翻谁就算输。两个玩家都非常聪明,问先手是否获胜?
输入格式
第一行,一个正整数T,表示接下来有T组测试数据。对于每组测试数据:
第1行,一个整数N,表示硬币的数量。
第2行,N个空格间隔的整数(0和1),从左往右依次表示游戏开始前硬币的情况,其中数字0表示正面朝下,数字1表示正面朝上。
输出格式
T行,每行对应一组测试数据的答案。若先手胜输出”Yes” 否则输出“No”
样例输入
5
9
1 0 1 1 1 0 1 0 0
13
0 0 1 1 1 1 0 1 1 1 0 0 1
12
1 0 1 0 1 0 1 1 1 0 0 0
9
0 0 0 0 1 0 0 0 0
13
1 0 1 0 1 1 0 0 0 0 0 0 1
样例输出
Yes
Yes
Yes
No
No
提示
对于30%的数据: 1≤N≤100
对于100%的数据: 1≤N≤100000 ,T≤10
P3774小鸟 | ||
|
问题描述
有一排n棵树,第i棵树的高度是Di。
一群小鸟要从第1棵树飞到第n棵树去玩。
不同小鸟的飞跃能力不同,第i只小鸟的飞跃能力为ki,表示如果当前它位于第x号树,那么它可以飞到x+1,x+2,......,x+ki号树上去,也就是一次可以飞过ki棵树。
如果小鸟飞到一棵不矮于当前树的树,那么他的劳累值会+1,否则不会。
小鸟们希望最小化劳累值,请你计算每只小鸟达到终点所需最小劳累值。
输入格式
第一行,一个整数N(2<=N<=100000)
第二行,N个空格间隔的整数,第i个数表示第i棵树的高度Di。(1<=Di<=10^9)
第三行,一个整数Q(1<=Q<=25),表示小鸟的数量
接下来Q行,每行一个整数,其中第i个整数表示第i只小鸟的飞跃能力ki。
输出格式
Q行,每行一个整数,表示对应小鸟的劳累值
样例输入
9
4 6 3 6 3 7 2 6 5
2
2
5
样例输出
2
1
提示
第1只小鸟停落的树编号为1, 3, 5, 7, 8, 9.在3飞到5时劳累值+1,在7飞到8时劳累值+1
P2407乘车路线 | |
|
问题描述
编号为 1.. N 的N座城镇用若干仅供单向行驶的道路相连,每条道路上均有两个参数:道路长度(length)和在该条道路上行驶的费用(cost)。
BOB 准备从城镇 1 出发到达城镇 N,但他目前只有 W 块钱,为此,你需要帮助他寻找一条从城镇1到城镇 N 在他能支付的前提下的一条最短路线。
输入格式
第一行为钱的数目W (0<=w<=1000)
第二行为城镇数目N(2<=N<=100)
第三行为为道路条数K(1<=K<=10000)
随后的 K 行每行为一条道路的信息,包含 4个数值(S,D,L,T)其中S为道路的起点, D为道路的终点 , L为道路长度, T为所需支付 费用。
(1<=S,D<=N,1<=L<=100,0<=T<=100)
输出格式
输出最短长度,若无解,则输出“NO”;
样例输入
5
6
7
1 2 2 3
2 4 3 3
3 4 2 4
1 3 4 1
4 6 2 1
3 5 2 0
5 4 3 2
样例输出
11