比赛-某训练赛 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 }
P1

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 }
P2

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 }
P3

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 }
P4_(1)

法二.二维最短路

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 }
P4_(2)

 

改悔:

1题这类找通项公式题可以暴力算些结果再观察规律嘛, 毕竟对于数学渣来说推式子挺耗时间的

搜索剪枝一定要剪干净啊!剪一半真的只有 50 分, 不标记已访问点导致程序原地转圈也是!

说到二维最短路, 我已经不会写次短路的代码了!

单调队列一年前就看过课件,结果忘光了, DP + 单调队列 很常见嘛

题目:

P3496数三角形
时间限制 : 10000 MS   空间限制 : 65536 KB
评测说明 : 时限1000ms
问题描述

给出一个正整数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翻硬币
时间限制 : - MS   空间限制 : 165536 KB 
评测说明 : 1s
问题描述

两个玩家在玩一个有趣的翻硬币游戏。

有 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小鸟
时间限制 : - MS   空间限制 : 65536 KB 
评测说明 : 1000ms
问题描述

有一排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 


来源  POI 2014 Little Bird
P2407乘车路线
时间限制 : 10000 MS   空间限制 : 165536 KB
问题描述

编号为 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”;

样例输入




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


来源  CEOI1998
posted @ 2017-08-23 02:49  derchg  阅读(327)  评论(0编辑  收藏  举报