Noip 模拟训练2

Noip 模拟训练2

  • 满分260。T1 100,T2 50,T3 60
  • 本人第一次70,修正后260
  • 较易

T1:扫雷

Description

  • 有一 N×M(1≤N、M≤100)的网格区域,标有“#”的网格为障碍区,标有数字的网格为雷区,数字即为该区域的地雷数。一辆扫雷车从左上角出发,且只能向右或向下走,扫雷车不能进入障碍区,此外它走过的网格中的地雷将被清除干净。你的任务是编一个程序,寻找一条从最左上角的网格(1,1)出发到达最右下角的网格(N,M)且扫雷最多的路线。下图是一个 4×5 的网格样例,最佳的扫雷路线可以清除 6 个地雷。

    1
    2 3
    3
    1

Input

  • 输入文件 mine.in 的第一行是两个整数N 和 M,两个数之间用一个空格分开。接下来的N 行,每行有M 个整数,其中负数表示该区域是障碍区,非负数表示雷区,且该整数表示这个区域的地雷总数,同一行中相邻的两个整数之间用一个空格分开。

Output

  • 输出文件 mine.out 仅有一行,若扫雷的路线不存在,即从左上角到右下角没有通路,则输出“No answer”,否则输出一个整数,表示最多可以清除地雷的个数。

Sample Input

4 5

0 1 0 0 0

2 –1 3 –1 0

0 3 0 –1 –1

-1 –1 0 1 0

Sample output

6

题解:

  • dp。但是拿了70。当时想到了有点会走不到的情况,但也只是想当然,没有落实到代码去,想着这个应该问题不大。所以以后的dp要注意可行性、更新的条件

  • 所以:
    1. 注意各种条件:边界条件,更新条件,初始化
    2. 不要想当然
#include <iostream>
#include <cstdio>
#define maxn 105
using namespace std;

int n, m;
int a[maxn][maxn], f[maxn][maxn];
bool can[maxn][maxn];

int main()
{
    can[0][1] = can[1][0] = 1; //初始化!!
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
        {
            cin >> a[i][j];
            if(a[i][j] < 0) a[i][j] = -1;
        }
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            if(a[i][j] != -1)
            {
                if(can[i - 1][j] || can[i][j - 1]) can[i][j] = 1;
                if(can[i - 1][j] || can[i][j - 1]) f[i][j] = max(f[i - 1][j], f[i][j - 1]) + a[i][j];
            }
    if(!can[n][m]) cout << "No answer";
    else cout << f[n][m];
    return 0;
}

T2:城市观光

Description

  • L 城是一个现代化的旅游城市,一条条笔直的街道纵横交错,构成规则的矩形网络,每条街道都用一个整数进行编号,如下图所示,网络的交点表示两条街道的交叉路口,其坐标表示为:(该点所在水平街道编号,该点所在垂直街道编号),规定城市中心坐标为(0,0).(没有图也没关系)

    城市有 M 个道口因维修交通灯系统而放置了路障无法通行,一群观光客步行游览城市, 为了让行程更加有趣,他们制订了如下规则:从城市中心出发,每步沿街道走过 1 个网格, 除了第一步有四个方向可走外,其余各步必须在前一步基础上左转或右转 90 度,要求第 N 步恰好回到出发点(0,0),而中间其余各点不能重复

    在上图中,从(0,0)出发走三步是不能回到起点;而走四步回到起点的路径只有两条: 其中一条是:(0,0)→(-1,0)→(-1,1)→(0,1)→(0,0),

    而另一条是:(0,0)→(0,1)→(-1,1)→(-1,0)→(0,0)。

    你的任务是对于给定的N、M 及M 个障碍物的分布,编程求出所有游览路径的数目。

Input

  • 输入文件 sight.in 的第一行是两个整数 N 和 M,其中 N 表示一条游览路线的步

    数,而 M 则表示障碍物的数目,其中 0≤N≤40,0≤M≤50。接下来的 M 行,每行用两个整数给出一个障碍物的坐标,相邻两个整数之间用一个空格分开。

Output

  • 输出文件 sight.out 仅有一行,给出满足要求的游览路线的数目。

Sample Input

4 2

0 –1

1 0

Sample output

2

题解:

  • 搜索,需要剪枝。但第一次写看错题了... ...“中间其余各点不能重复”这句话没看到。但第一次修正后也只拿到了1个点。因为没有想到要进行剪枝(想着不剪枝应该也行),剪枝的思路就是“走到一个地方之后判断还够不够步数回到起点”,但算这个步数很费劲,推了好久(见程序)。但进行完第二次修正后,还是只得了1个点。这时我猜想是否是map速度过慢?然后将坐标全部平移成正数存在数组里操作,然后就A掉了。

  • 所以:

    1. 坐标存在负数可以整体平移

    2. 搜索能剪枝就剪枝

    3. STL使用需谨慎

#include <iostream>
#include <cstdio>
#define maxn 2005
#define k 100 //平移系数
using namespace std;

int mp[maxn][maxn], need[maxn][maxn];
bool vis[maxn][maxn];
int t, n, ans;
int dx[5][3] = {
{0, 0, 0},
{0, 0, 0},
{0, 0, 0},
{0, 1, -1},
{0, -1, 1}
};
int dy[5][3] = {
{0, 0, 0},
{0, -1, 1},
{0, 1, -1},
{0, 0, 0},
{0, 0, 0}
};

inline int cal(int turn, int flag)
{
    if(flag == 1)
        {if(turn == 1) return 3; if(turn == 2) return 4; if(turn == 3) return 2; if(turn == 4) return 1;}
    else
        {if(turn == 1) return 4; if(turn == 2) return 3; if(turn == 3) return 1; if(turn == 4) return 2;}
    return 0;
}

inline int call(int x, int y) {
    x = abs(x - k), y = abs(y - k);
    if((x % 2 == 0 && y % 2 == 0) || (x % 2 != 0 && y % 2 != 0)) return 2 * x;
    else return 2 * (x - 1) + 1;
}

inline void dfs(int x, int y, int step, int turn)
{
    if(mp[x][y] == -1) return;
    if(!need[x][y]) need[x][y] = call(x, y);
    if(step + need[x][y] > t + 1) return;
    if(step > t)
    {
        if(x == k && y == k) {ans++; return;}
        return;
    }
    int xx, yy;
    xx = x + dx[turn][1], yy = y + dy[turn][1];
    if(!vis[xx][yy])
    {
        vis[xx][yy] = 1;
        dfs(xx, yy, step + 1, cal(turn, 1));
        vis[xx][yy] = 0;
    }
    if(xx == k && yy == k && step == t) dfs(xx, yy, step + 1, cal(turn, 1));
    xx = x + dx[turn][2], yy = y + dy[turn][2];
    if(!vis[xx][yy])
    {
        vis[xx][yy] = 1;
        dfs(xx, yy, step + 1, cal(turn, 2));
        vis[xx][yy] = 0;
    }
    if(xx == k && yy == k && step == t) dfs(xx, yy, step + 1, cal(turn, 2));
}

int main()
{
    freopen("sight.in", "r", stdin);
    freopen("sight.out", "w", stdout);
    
    vis[k][k] = 1;
    cin >> t >> n;
    for(int i = 1; i <= n; i++)
    {
        int x, y;   cin >> x >> y;
        mp[x + k][y + k] = -1;
    }
    vis[k][-1 + k] = 1, dfs(k, -1 + k, 2, 3), vis[k][-1 + k] = 0;
    vis[k][1 + k] = 1, dfs(k, 1 + k, 2, 4), vis[k][1 + k] = 0;
    vis[-1 + k][k] = 1, dfs(-1 + k, k, 2, 1), vis[-1 + k][k] = 0;
    vis[1 + k][k] = 1, dfs(1 + k, k, 2, 2), vis[1 + k][k] = 0;
    cout << ans;
    return 0;
}

素数伴侣

Description

  • 若两个正整数和为素数,我们称之为“素数伴侣”,如 2 和 5、6 和 13, 据说它们能应用到通信加密中。现在密码学会请你设计一个程序,从己有的 n 个正整数中挑选出若干对组成“素数伴侣”,挑选方案多种多样,例如有四个正整数:2、5、6、13,如果将 5 和 6 分为一组中只能得到一组“素数伴侣”,而将 2 和 5、6 和 13 编组将得到两组“素数伴侣”,能组成“素数伴侣”最多的方案称为“最佳方案”,当然密码学会希望你寻找出“最佳方案”。

Input

  • 输入文件 prime.in 的第一行有一个正整数 n,其中 n 不超过 400,表示待挑选的自然数的个数。第二行给出 n 个不超过 30000 的正整数,相邻的两个数之间用一个空格分

    开。

Output

  • 输出文件 prime.out 仅有一行,给出你求得的“最佳方案”组成“素数伴侣”的对数。

Sample Input

4

2 5 6 13

Sample Output

2

题解:

  • 本来的思路如下:
    • 对于俩数之和为质数的,俩数连一条边
    • 连完之后,就形成了一个图
    • 这个图其实是一棵树:(证明如下)
    • 已知A+B=质数,B+C=质数;问A+C=?
    • 因为质数为奇数,所以A、B分别为奇数和偶数
    • 当A为奇数时,可推出C为奇数,所以A+C=偶数!=质数
    • 当A为偶数时,可推出C为偶数,所以A+C=偶数!=质数
    • 综上,A+C=合数
    • 所以在图的意义下,不存在环,所以此图是一颗树
    • 那么图是一棵树后,我就可以取这棵树上的最长链,答案就是 最长链/2(向下取整)
  • But,全爆了。

  • 因为当A=1,B=1,C=1时,A+C=质数! 这就与上面推的结论矛盾,所以图里有环!!!

    所以这个思路需要改进。

  • 正解二分图匹配:数据如果没有1的话,我的算法是可行的。正解可以用匈牙利算法,所有的奇数是一边,所有的偶数是另一边,用奇数来找伴侣,当某个奇数找不到伴侣时,如果是1的话,就单独累加到一个变量中。配对成功的总数,再加上未配对的1总数除以2的商,就是答案。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#define maxn 405
using namespace std;

int n, cnt1, cnt2, ans, tot;
int a[maxn], b[maxn], mat[maxn];
bool vis[maxn];

bool cal(int x, int y)
{
    int t = x + y, last = (int)sqrt(t);
    for(int i = 2; i <= last; i++)
        if(t % i == 0) return 0;
    return 1;
}

bool find(int x)
{
    for(int i = 1; i <= cnt2; i++)
        if(cal(a[x], b[i]) && !vis[i])
        {
            vis[i] = 1;
            if(!mat[i] || find(mat[i]))
            {
                mat[i] = x;
                return 1;
            }
        }
    return 0;
}

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        int t;  cin >> t;
        if(t % 2 == 0) b[++cnt2] = t;
        else a[++cnt1] = t;
        if(t == 1) tot++;
    }
    for(int i = 1; i <= cnt1; i++)
    {
        memset(vis, 0, sizeof(vis));
        if(find(i))
        {
            ans++;
            if(a[i] == 1) tot--;
        }
    }
    cout << ans + (int)(tot / 2) << endl;
}

猜你喜欢

转载自www.cnblogs.com/BigYellowDog/p/11129905.html