Noip2015提高组解题报告

Day1

 

T1神奇的幻方

一道简单异常的小模拟,我们只需要确定数字1的位置,然后根据题意枚举即可,简简单单就A了,什么也不卡。

然而这题,我刚开始学OI的时候,因为当时比较蠢,被这题花式吊打啊....根本不会啊.....

ε=(´ο`*)))唉又想起没学OI的自己了..

虽然题简单,还是惯例丢代码

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn = 60;
 4 int a[maxn][maxn];
 5 int n, x, y;
 6 
 7 inline int read() {
 8     int x = 0, y = 1;
 9     char ch = getchar();
10     while(!isdigit(ch)) {
11         if(ch == '-') y = -1;
12         ch = getchar();
13     }
14     while(isdigit(ch)) {
15         x = (x << 1) + (x << 3) + ch - '0';
16         ch = getchar();
17     }
18     return x * y;
19 }
20 
21 int main() {
22     n = read();
23     x = 1, y = n / 2 + 1;
24     a[x][y] = 1;
25     for(register int i = 2; i <= n * n; ++i) {
26         if(x == 1 && y != n) {
27             x = n, y += 1;
28             a[x][y] = i;
29         }
30         else if(x != 1 && y == n) {
31             x -= 1, y = 1;
32             a[x][y] = i;
33         }
34         else if(x == 1 && y == n) {
35             x += 1;
36             a[x][y] = i;
37         }
38         else if(x != 1 && y != n) {
39             if(!a[x - 1][y + 1]) {
40                 x -= 1, y += 1;
41                 a[x][y] = i;
42             }
43             else if(a[x - 1][y + 1]) {
44                 x += 1;
45                 a[x][y] = i;
46             }
47         }
48     }
49     for(register int i = 1; i <= n; ++i) {
50         for(register int j = 1; j <= n; ++j)
51             printf("%d ", a[i][j]);
52         printf("\n");
53     }
54     return 0;
55 }

T2信息传递

 

大意就是说一堆人会一直传话,形成一个环,问你最小的环里有多少个人

显然你可以直接tarjan跑强联通分量,当然你也可以跑并查集等做法做

并查集写法简单讲就是在路径压缩同时维护环的大小,对于给出的传递者与被传递者,判断是不是一个集合里的,不是就合并

是就更新答案

 1 #include<bits/stdc++.h>
 2 #define ll long long
 3 #define uint unsigned int
 4 #define ull unsigned long long
 5 using namespace std;
 6 const int maxn = 200010;
 7 const int inf = 1000000007;
 8 int t, fa[maxn];
 9 int dis[maxn], ans;
10 int n;
11 
12 inline int read() {
13     int x = 0, y = 1;
14     char ch = getchar();
15     while(!isdigit(ch)) {
16         if(ch == '-') y = -1;
17         ch = getchar();
18     }
19     while(isdigit(ch)) {
20         x = (x << 1) + (x << 3) + ch - '0';
21         ch = getchar();
22     }
23     return x * y;
24 }
25 
26 int getfather(int x) {
27     if(x == fa[x]) return x;
28     int son_in_son = fa[x];
29     fa[x] = getfather(fa[x]);
30     dis[x] += dis[son_in_son];
31     return fa[x];
32 }
33 
34 int main() {
35 //    freopen("message.in", "r", stdin);
36 //    freopen("message.out", "w", stdout);
37     n = read();
38     for(register int i = 1; i <= n; ++i) fa[i] = i;
39     ans = inf;
40     for(register int i = 1; i <= n; ++i) {
41         t = read();
42         int u = getfather(i), v = getfather(t);
43         if(u != v) {
44             fa[u] = v;
45             dis[i] = dis[t] + 1;
46         }
47         else ans = min(ans, dis[i] + dis[t] + 1);
48     }
49     printf("%d\n", ans);
50     return 0;
51 }

tarjan就更简单了,跑强连通分量,统计每个环中节点的大小,然后找最小的大小不为1环的就好了

 1 #include<bits/stdc++.h>
 2 #define ll long long
 3 #define uint unsigned int
 4 #define ull unsigned long long
 5 using namespace std;
 6 const int maxn = 200010;
 7 const int inf = 1000000007;
 8 struct shiki {
 9     int net, y;
10 }e[maxn << 1];
11 int lin[maxn], len = 0;
12 int dfn[maxn], low[maxn];
13 int num = 0, cnt = 0, top = 0;
14 int c_num[maxn], s[maxn];
15 bool in_s[maxn];
16 int sum[maxn];
17 int n, t, ans;
18 
19 inline int read() {
20     int x = 0, y = 1;
21     char ch = getchar();
22     while(!isdigit(ch)) {
23         if(ch == '-') y = 1;
24         ch = getchar();
25     }
26     while(isdigit(ch)) {
27         x = (x << 1) + (x << 3) + ch - '0';
28         ch = getchar();
29     }
30     return x * y;
31 }
32 
33 inline void insert(int xx, int yy) {
34     e[++len].y = yy;
35     e[len].net = lin[xx];
36     lin[xx] = len;
37 }
38 
39 void tarjan(int x) {
40     dfn[x] = low[x] = ++num;
41     s[++top] = x, in_s[x] = 1;
42     for(int i = lin[x]; i; i = e[i].net) {
43         int to = e[i].y;
44         if(!dfn[to]) {
45             tarjan(to);
46             low[x] = min(low[x], low[to]);
47             
48         }
49         else if(in_s[to]) low[x] = min(low[x], dfn[to]);
50     }
51     if(dfn[x] == low[x]) {
52         cnt++; int k;
53         do {
54             k = s[top--], in_s[k] = 0;
55             c_num[k] = cnt;
56         }while(x != k);
57     }
58 }
59 
60 int main() {
61     memset(sum, 0, sizeof(sum));
62     n = read();
63     for(register int i = 1; i <= n; ++i) {
64         t = read();
65         insert(i, t);
66     }
67     for(register int i = 1; i <= n; ++i)
68         if(!dfn[i]) tarjan(i);
69     for(register int i = 1; i <= n; ++i)
70         sum[c_num[i]]++;
71     ans = inf;
72     for(register int i = 1; i <= cnt; ++i)
73         if(sum[i] != 1) ans = min(ans, sum[i]);
74     printf("%d\n", (ans != inf) ? ans : 1);
75     return 0;
76 }

T3斗地主

这题当年显然恶心的不少的人

这题确实比较恶心,尤其是那诡异的一堆牌的出法,因为和真实斗地主不一样,比较不适

对于这点,我们本着:“所有题面没说是不合法的情况,都是合法的” 的原则,可以知道最烦人的大小王,他们不是对,他们是单牌,所以炸弹带大小王是合法的!

因为炸弹可以带两张单牌。

我们贪心的想一想,显然我们要想出牌次数最小,一定是要尽可能的先把所有能一次丢走顺子和带牌都出完,最后剩下的牌在甩完就好,所以我们可以爆搜顺子和带牌加上最后剩下的牌的出牌次数,答案求min

好吧和贪心并没有什么关系,我们做这题首先要有一个合理的搜索顺序:先搜顺子和带牌,最后处理剩余的牌

因为显然,除了顺子,带牌,剩下的无论是什么都可以一次出完,而相比枚举出掉什么单牌或对子或炸弹

显然顺子和带牌的情况更方便处理,这样我们就可以爆搜了,奥对,顺子和带牌先搜哪个后搜哪个都是可以A题的

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 const int maxn = 25;
  4 const int inf = 1000000007;
  5 int sum[maxn];
  6 int T, n, ans;
  7 
  8 inline int read() {
  9     int x = 0, y = 1;
 10     char ch = getchar();
 11     while(!isdigit(ch)) {
 12         if(ch == '-') y = -1;
 13         ch = getchar();
 14     }
 15     while(isdigit(ch)) {
 16         x = (x << 1) + (x << 3) + ch - '0';
 17         ch = getchar();
 18     }
 19     return x * y; 
 20 }
 21 
 22 void dfs_kill(int x) {//出牌次数 
 23     /*
 24     可以公开的情报:
 25     出牌方式有火箭,炸弹,单牌,对牌,三不带,三带单,三带对,
 26     顺子,连对,三顺, 四带二(且带的两张牌不要求相同) 
 27     */
 28     if(x >= ans) return;
 29     //顺子势力 
 30     int op = 0;//单顺 
 31     for(register int i = 3; i <= 14; ++i) {//2与双王不可用 
 32         if(sum[i] < 1) op = 0;//打断顺子
 33         else {
 34             op++;//长度加1 
 35             if(op >= 5) {
 36                 for(register int j = i - op + 1; j <= i; ++j) sum[j]--;//出牌 
 37                 dfs_kill(x + 1);
 38                 for(register int j = i - op + 1; j <= i; ++j) sum[j]++;//回溯 
 39             }
 40         } 
 41     } 
 42     op = 0;//连对
 43     for(register int i = 3; i <= 14; ++i) {
 44         if(sum[i] < 2) op = 0;//打断连对
 45         else {
 46             op++;
 47             if(op >= 3) {
 48                 for(register int j = i - op + 1; j <= i; ++j) sum[j] -= 2;
 49                 dfs_kill(x + 1);
 50                 for(register int j = i - op + 1; j <= i; ++j) sum[j] += 2;
 51             }
 52         } 
 53     } 
 54     op = 0;//三顺
 55     for(register int i = 3; i <= 14; ++i) {
 56         if(sum[i] < 3) op = 0;
 57         else {
 58             op++;
 59             if(op >= 2) {
 60                 for(register int j = i - op + 1; j <= i; ++j) sum[j] -= 3;
 61                 dfs_kill(x + 1);
 62                 for(register int j = i - op + 1; j <= i; ++j) sum[j] += 3;
 63             }
 64         }
 65     } 
 66     //带牌
 67     for(register int i = 2; i <= 14; ++i) {//大小王不能带牌  
 68         if(sum[i] < 3) continue;//连三带都不行的 
 69         sum[i] -= 3;//大家都先搞三带 
 70         for(register int j = 2; j <= 16; ++j) {//三带一居然能带大小王?? 
 71             if(sum[j] < 1 || j == i) continue;
 72             sum[j]--;
 73             dfs_kill(x + 1);
 74             sum[j]++;
 75         }
 76         for(register int j = 2; j <= 14; ++j) {//三带二,大小王不算对子 
 77             if(sum[j] < 2 || j == i) continue;
 78             sum[j] -= 2;
 79             dfs_kill(x + 1);
 80             sum[j] += 2;
 81         }
 82         sum[i] += 3;
 83         if(sum[i] > 3) {//一些群众可以四带 
 84             sum[i] -= 4;
 85             for(register int j = 2; j <= 15; ++j) {//带单牌之时,大小王算单牌 
 86                 if(sum[j] < 1 || j == i) continue;
 87                 sum[j]--;
 88                 for(register int k = 2; k <= 15; ++k) {
 89                     if(sum[k] < 1 || (k == j && k != 15) || k == i) continue;
 90                     sum[k]--;
 91                     dfs_kill(x + 1);
 92                     sum[k]++;
 93                 }
 94                 sum[j]++;
 95             }
 96             for(register int j = 2; j <= 14; ++j) {//带双牌之时,大小王不算对子 
 97                 if(sum[j] < 2 || j == i) continue;
 98                 sum[j] -= 2;
 99                 for(register int k = 2; k <= 14; ++k) {
100                     if(sum[k] < 2 || k == j || k == i) continue;
101                     sum[k] -= 2;
102                     dfs_kill(x + 1);
103                     sum[k] += 2;
104                 }
105                 sum[j] += 2;
106             }
107             sum[i] += 4;
108         }
109     }
110     //已经处理完了顺子,连对,三顺,三带一,三带二,四带二单,四带二对
111     //对于剩下的势力,显然可以一次性丢出去
112     for(register int i = 2; i <= 15; ++i) if(sum[i]) x++;
113     ans = min(ans, x); 
114 }
115 
116 int main() {
117 //    freopen("landlords.in", "r", stdin);
118 //    freopen("landlords.out", "w", stdout);
119     T = read(), n = read();
120     while(T--) {
121         memset(sum, 0, sizeof(sum));
122         ans = inf;
123         for(register int i = 1; i <= n; ++i) {
124             int which = read(), col = read();
125             if(which == 0) sum[15]++;//大小王放在同一个位置 
126             else if(which == 1) sum[14]++;//塞进一个A,因为A可以丢进顺子等组合且比较大,放在后面
127             else sum[which]++; 
128         }
129         dfs_kill(0);
130         printf("%d\n", ans);
131     }
132     return 0;
133 }

这样我们就把Noip2015Day1给AK了,实际上就这套题的难度来看,前两题简直是送分,我写前两题甚至没超过半个小时(大概?)

最后T3确定好规则和搜索顺序,因为数据随机,所以直接爆搜并不难过。这样,你就有个极大的优势了

 Day2

跳石头

挺经典的二分答案题目(不)

二分一个距离,然后开始判定是否合法:

定义一个变量now表示现在在哪块石头上,ans表示能拿掉的石头数量

若枚举到的石头i与now的距离小于二分出的距离,将ans++;

否则令now = i;

若最后ans <= m,则距离不够(显然会有人想知道为什么ans==m不行,我们想一下,我们能够跳到一块石头为i+m,那么按照我们的判定方式,就将i+m拿掉了,然而我们能不能跳到第i+m+1块石头呢?因为必然要有一块岩石做终点,不能跳到水里,所以我们至少要能够拿掉m+1块石头才能说明我们一定能拿掉m块石头并且保证有起点和终点)

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn = 50010;
 4 int a[maxn];
 5 int L, m, n;
 6 
 7 inline int read() {
 8     int x = 0, y = 1;
 9     char ch = getchar();
10     while(!isdigit(ch)) {
11         if(ch == '-') y = -1;
12         ch = getchar();
13     }
14     while(isdigit(ch)) {
15         x = (x << 1) + (x << 3) + ch - '0';
16         ch = getchar();
17     }
18     return x * y;
19 }
20 
21 inline bool check(int x) {
22     int ans = 0, now = 0;
23     for(int i = 1; i <= n; ++i) 
24         if(a[i] - now < x) ans++;
25         else now = a[i];
26     return ans <= m;
27 }
28 
29 int main() {
30     L = read(), n = read(), m = read();
31     for(int i = 1; i <= n; ++i) a[i] = read();
32     int l = 0, r = L;
33     while(l < r) {
34         int mid = l + r >> 1;
35         if(check(mid)) l = mid + 1;
36         else r = mid - 1;
37     }
38     if(!check(l)) l -= 1;
39     printf("%d\n", l);
40     return 0;
41 }

子串

 

这题,还挺好写的....

高端做法不会,但是我们可以很容易的想到一个四维的状态

f[i, j, k, 0/1]表示A串取到了第i个字符,B串匹配了j个字符,使用了k个子串,第i个字符取或是不取

方程:

f[i][j][k][0] = (f[i-1][j][k][0] + f[i-1][j][k][1]) % mod

如果ai != bj 则 f[i][j][k][1] = 0

否则 f[i][j][k][1] = (f[i-1][j - 1][k][1] + (f[i-1][j - 1][k - 1][0] + f[i-1][j - 1][k - 1][1]

因为空间问题(毕竟是四维嘛...)我们使用滚动数组即可

初态:f[0][0][0][0] = f[1][0][0][0] = 1

末态:f[n][m][k][0]+f[n][m][k][1]

 然后就简简单单地A题了

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int mod = 1000000007;
 4 const int maxn = 1010;
 5 const int maxm = 210;
 6 int f[2][maxm][maxm][2];
 7 char a[maxn], b[maxm];
 8 int n, m, l, id = 1;
 9 
10 inline int read() {
11     int x = 0, y = 1;
12     char ch = getchar();
13     while(!isdigit(ch)) {
14         if(ch == '-') y = -1;
15         ch = getchar();
16     }
17     while(isdigit(ch)) {
18         x = (x << 1) + (x << 3) + ch - '0';
19         ch = getchar();
20     }
21     return x * y;
22 }
23 
24 int main() {
25 //    freopen("a.in", "r", stdin);
26 //    freopen("a.out", "w", stdout);
27     n = read(), m = read(), l = read();
28     scanf("%s%s", a + 1, b + 1);
29     f[0][0][0][0] = f[1][0][0][0] = 1;
30     for(register int i = 1; i <= n; ++i) {
31         for(register int j = 1; j <= m; ++j)
32             for(register int k = 1; k <= l; ++k) {
33                 f[i&1][j][k][0] = (f[(i-1)&1][j][k][0] + f[(i-1)&1][j][k][1]) % mod;
34                 if(a[i] != b[j]) f[i&1][j][k][1] = 0;
35                 else f[i&1][j][k][1] = (f[(i-1)&1][j - 1][k][1] + (f[(i-1)&1][j - 1][k - 1][0] + f[(i-1)&1][j - 1][k - 1][1]) % mod) % mod;
36             }        
37     }
38     printf("%d\n", (f[n & 1][m][l][0] + f[n & 1][m][l][1]) % mod);
39     return 0;
40 }

运输计划

 

两天来最难的题,当然对于一些大佬,这题比斗地主简单

题解:LCA+差分+二分

先咕咕咕一下回头一定补咕咕咕咕

posted @ 2018-10-18 19:34  YuWenjue  阅读(882)  评论(0编辑  收藏  举报