• 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("
    ");
    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
    ", 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
    ", (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
    ", 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
    ", 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
    ", (f[n & 1][m][l][0] + f[n & 1][m][l][1]) % mod);
    39     return 0;
    40 }

    运输计划

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

    题解:LCA+差分+二分

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

  • 相关阅读:
    笔试题 9.11
    shell脚本编程
    android源码中用到的设计模式
    struts2.0的工作流程
    无奈的信息产业部备案网站流程
    VS2005调试时弹出“无法附加。绑定句柄无效”对话框
    彻底删除项目的VSS源代码管理信息
    软件版本详细介绍
    导入SourceSafe过程中项目结构无法修改问题的解决方案
    建立良好的客户关系=节约项目成本
  • 原文地址:https://www.cnblogs.com/ywjblog/p/9812678.html
Copyright © 2020-2023  润新知