杭电多校2022.7.20第一场部分题解

本文探讨了三个算法问题:BDragonslayer涉及最短路径规划,CBackpack涉及背包问题的异或和最大化,ILaser涉及棋盘上的激光路径覆盖。通过位操作和广度优先搜索等技巧,给出了高效解法。同时,LAliceAndBob展示了在数字游戏中如何判断胜利条件。
摘要由CSDN通过智能技术生成

B Dragon slayer

题意:在一个n*m的地图中已知起点和终点,还有若干堵墙,拆掉一堵墙需要一点体力,求从起点到终点最少消耗的体力。

题解:把边先放到方块中,规定将竖直的边放入L_wall,若数组值为1则表示左边有边;把水平的边放入D_wall,若数组值为1则表示下边有边。然后枚举所有的边存在的可能,判断所有边存在方式的能否到达终点,最后取Min。因为枚举了所有的边存在的可能,只要判断当前图下,消耗当前体力下,能否继续成立。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <vector>
#include <map>
#include <bitset>

using namespace std;
#define quick_cin() cin.tie(0),cout.tie(0),ios::sync_with_stdio(false)
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
typedef pair<double, double> PDD;

const int N = 20, INF = 0x3f3f3f3f;
struct node
{
    int x1, y1, x2, y2;
}wall[N];

int mp[N][N];
int st_wall[N];
int L_wall[N][N], D_wall[N][N];//L表示竖着的墙(若一则表示左边有墙),D表示横着的墙(若1则表示下边有墙)
int n, m, k;
int x3, y3, xt, yt;
int ans = INF;
PII q[N * N];

bool bfs()
{
    memset(mp, 0, sizeof mp);
    memset(L_wall, 0, sizeof L_wall);
    memset(D_wall, 0, sizeof D_wall);
    int hh = 0, tt = 0;
    q[0] = {x3, y3};
    mp[x3][y3] = 1;
    for (int i = 0; i < k; i ++ )
    {
        if (st_wall[i])
        {
            int x1 = wall[i].x1, x2 = wall[i].x2, y1 = wall[i].y1, y2 = wall[i].y2;
            if (x1 == x2)
            {
                for (int col = y1; col < y2; col ++ )
                {
                    L_wall[x1][col] = 1;
                }
            }
            else
            {
                for (int row = x1; row < x2; row ++ )
                {
                    D_wall[row][y1] = 1;
                }
            }
        }
    }
    while (hh <= tt)
    {
        auto s = q[hh ++ ];
        int x = s.first, y = s.second;
        if (x == xt && y == yt)
        {
            return 1;
        }
        if (x >= 1 && L_wall[x][y] == 0 && !mp[x - 1][y])
        {
            q[ ++ tt ] = {x - 1, y};
            mp[x - 1][y] = 1;
        }
        if (x + 1 < n && L_wall[x + 1][y] == 0 && !mp[x + 1][y])
        {
            q[ ++ tt ] = {x + 1, y};
            mp[x + 1][y] = 1;
        }
        if (y >= 1 && D_wall[x][y] == 0 && !mp[x][y - 1])
        {
            q[++ tt ] = {x, y - 1};
            mp[x][y - 1] = 1;
        }
        if (y + 1 < m && D_wall[x][y + 1] == 0 && !mp[x][y + 1])
        {
            q[ ++ tt ] = {x, y + 1};
            mp[x][y + 1] = 1;
        }
    }
    return 0;
}
void dfs(int u, int num)
{
    if (num >= ans) return;//若答案更新的体力已经比当前的体力小了,就不必继续判断
    if (u == k)
    {
        if (bfs()) ans = num;
        return;
    }
    st_wall[u] = 1;//墙存在;
    dfs(u + 1, num);
    st_wall[u] = 0;//墙不存在;
    dfs(u + 1, num + 1);

}
void solve()
{
    cin >> n >> m >> k >> x3 >> y3 >> xt >> yt;
    ans = INF;
    memset(st_wall, 0, sizeof st_wall);
    for (int i = 0; i < k; i ++ )
    {
        int x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        if (x1 == x2)
        {
            if (y1 > y2) swap(y1, y2);
            wall[i] = {x1, y1, x2, y2};
        }
        else
        {
            if (x1 > x2) swap(x1, x2);
            wall[i] = {x1, y1, x2, y2};
        }
    }
    dfs(0, 0);
    cout << ans << endl;
}
signed main()
{
    quick_cin();
    int T;
    cin >> T;
    while (T -- )
    {
        solve();
    }
    return 0;
}

C Backpack

题意:给定一个大小背包,背个物品有体积和价值,求如何在恰好塞满背包的前提下让所有背包里的物品价值总异或和最大。

题解:引入未使用的头文件<bitset>,设置f[N]保存所有的异或和是i的体积的所有可能性,将体积按位存入,即体积是多少就存在多少位,⚠️注意是存在多少位,而不是按二进制存入,每次就在那一位上存1。对于状态转移,此前是f[i] = max(f[i], f[i - v]);而f[i-v]在现在情形下减去就是左移v位,故再建数组使g[i]是j[i]左移v位的值。最后便利所有可能的体积f[i] |= g[i ^ w]。最终异或和从大到小遍历,寻找能在第m位是1的最大异或值。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <vector>
#include <map>
#include <bitset>

using namespace std;

#define quick_cin() cin.tie(0),cout.tie(0),ios::sync_with_stdio(false)
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
typedef pair<double, double> PDD;

const int N = 1050, M = N * 2, INF = 0x3f3f3f3f;

bitset<N> f[N], g[N];//f[i]表示异或和是i的体积的所有可能
//把体积的之间的关系转换为位数之间的差距
int n, m;

int main()
{
    quick_cin();
    int T;
    cin >> T;
    while (T -- )
    {
        cin >> n >> m;
        for (int i = 0; i <= 1024; i ++ ) f[i].reset(), g[i].reset();
        f[0][0] = 1;//初始化
        
        for (int i = 1; i <= n; i ++ )
        {
            int v, w;
            cin >> v >> w;
            
            for (int j = 0; j < 1024; j ++ )
                g[j] = f[j] << v;
            //此时任意g可以转移到f,此时g相当于加了v后到f
            
            for (int j = 0; j < 1024; j ++ )
            {
                f[j] |= g[j ^ w];
            }
        }
        
        int ans = -1;
        for (int i = 1024; i >= 0; i -- )
        {
            if (f[i][m])
            {
                ans = i;
                break;
            }
        }
        cout << ans << endl;
    }
    return 0;
}

I Laser

题意:棋盘中有n个点,请问是否存在一个点,他朝8个方向射出的光线能否包含所有点(米字形)。

题解:取第一个点,如果成立,因为它一定在原点的横竖左斜右斜四个边的某一边上,故假设第一个点在其中一个边上,判断是否成立。判断过程中,每次改变xy的横纵坐标来达到换边判断的效果。若在判断过程中不在同一横线上,则判断另3个点的可能性,若不行则break。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <vector>
#include <map>
#include <bitset>

using namespace std;
#define quick_cin() cin.tie(0),cout.tie(0),ios::sync_with_stdio(false)
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
typedef pair<double, double> PDD;

const int N = 1000010, M = N * 2, INF = 0x3f3f3f3f;

int n, m;
int a[N], b[N], x[N], y[N];

inline bool pd(int X, int Y)
{
    if (!X || !Y || X + Y == 0 || X == Y) return true;
    return false;
}

inline bool check(int X, int Y)
{
    for(int i = 1 ; i <= n ; i ++ )
        if(!pd(X - x[i], Y - y[i])) return false;
    return true;
}


inline bool check()
{
    bool f = true;
    
    for (int i = 1; i <= n; i ++ )
    {
        if (x[i] == x[1]) continue;//在同一行的
        f = false;
        //枚举所有可能的激光点
        if (check(x[1], y[i])) return true;
        if (check(x[1], y[i] - (x[1] - x[i]))) return true;
        if (check(x[1], y[i] + (x[1] - x[i]))) return true;
        break;//存在不符合的点直接退出循环
    }
    if (f) return true;
    return false;
}

inline bool solve()
{
    cin >> n;
    for (int i = 1; i <= n; i ++ ) cin >> a[i] >> b[i];

    //可以以下换答案所在的方位看作改变横纵坐标的走向
    
    // 假设x1在最终答案的横线上
    for (int i = 1; i <= n; i ++ ) x[i] = a[i], y[i] = b[i];
    if (check()) return true;
    // 假设x1在最终答案的竖线上
    for (int i = 1; i <= n; i ++ ) x[i] = b[i], y[i] = a[i];
    if (check()) return true;
    // 假设x1在最终答案的k=-1的斜线上
    for (int i = 1; i <= n; i ++ ) x[i] = a[i] + b[i], y[i] = a[i] - b[i];
    if (check()) return true;
    // 假设x1在最终答案的k=1的斜线上
    for (int i = 1; i <= n; i ++ ) x[i] = a[i] - b[i], y[i] = a[i] + b[i];
    if (check()) return true;
    
    return false;
}


int main()
{
    quick_cin();
    int T;
    cin >> T;
    while (T -- )
    {
        if (solve()) puts("YES");
        else puts("NO");
    }
    return 0;
}

L Alice And Bob

题意:有n个数,每次Alice可以把这些数分成两个集合,Bob可以删除掉任意一个集合里的所有元素,然后另一个集合内所有元素减1,如果集合中出现了0,那么Alice获胜,如果集合内没有元素了Bob获胜。

题解:从头开始看,不难发现当1的个数大于等于2或者2的个数大于等于4.....即i的个数大于等于2^i的时候,Alice必胜;此外,对于一个i,他上面的数对他的贡献是i/2最多有一半数量的i+1能够变成i,也就是说如果想要达到必胜态,我们从大到小累加,从n累加到n-1,然后n-1到n-2,最后数会越来越小,看最后到0的时候是否不为0。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <vector>
#include <map>
#include <bitset>

using namespace std;

#define quick_cin() cin.tie(0),cout.tie(0),ios::sync_with_stdio(false)
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
typedef pair<double, double> PDD;

const int N = 1e6 + 10, M = N * 2, INF = 0x3f3f3f3f;

int n, m;
int a[N];

int main()
{
    quick_cin();
    int T;
    cin >> T;
    while (T -- )
    {
        cin >> n;
        for (int i = 0; i <= n; i ++ )
            cin >> a[i];
        for (int i = n - 1; i >= 0; i -- )
            a[i] += a[i + 1] / 2;
        
        if (a[0]) puts("Alice");
        else puts("Bob");
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值