2021牛客寒假算法基础集训营5

A. 美丽的路径(二分 + 搜索)

传送门

可以用并查集判联通性,无法到达直接NO。

YES的话考虑二分答案,权值 >= mid 的点赋为1,权值 < mid 的点赋为0

(1)从s开始搜索,如果有两个相邻的点都 > mid,那么check一定返回1,因为可以来回走这两个点

(2)在上一条不满足的前提下,有效路径只能是010101或101010,起点和终点不能都是0

(3)从s开始搜索,找是否有一条01交替的路径可以到达 t

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 7;
const int M = 4e5 + 7;
const int inf = 0x3f3f3f3f;
const int mod = 998244353;
inline int read() {
    int x = 0, f = 1;
    char ch = getchar();
    while(!isdigit(ch)) {
        if(ch == '-')
            f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}

bool vis[N], flag;
int n, m, s, t, a[N], head[N], tot, fa[N];
struct Edge {
    int v, nxt;
} edge[M];

void init() {
    tot = 0;
    memset(head, -1, sizeof(head));
    for(int i = 1; i <= n; ++i) fa[i] = i;
}

void addedge(int u, int v) {
    edge[tot].v = v;
    edge[tot].nxt = head[u];
    head[u] = tot++;
}

int Find(int x) {
    if(x == fa[x]) return x;
    return fa[x] = Find(fa[x]);
}

void Union(int x, int y) {
    int xx = Find(x);
    int yy = Find(y);
    if(xx != yy) fa[xx] = yy;
}

void dfs1(int u, int x) {
    vis[u] = 1;
    for(int i = head[u]; ~i; i = edge[i].nxt) {
        int v = edge[i].v;
        if(a[u] >= x && a[v] >= x) {
            flag = 1;
            return ;
        }
        if(!vis[v]) dfs1(v, x);
    }
}

void dfs2(int u, int x) {
    vis[u] = 1;
    if(u == t) return ;
    for(int i = head[u]; ~i; i = edge[i].nxt) {
        int v = edge[i].v;
        if(vis[v] || (a[u] >= x && a[v] >= x) || (a[u] < x && a[v] < x)) continue;
        dfs2(v, x);
    }
}

bool check(int x) {
    flag = 0;
    for(int i = 1; i <= n; ++i) vis[i] = 0;
    dfs1(s, x);
    if(flag) return 1;
    if(a[s] < x && a[t] < x) return 0;
    for(int i = 1; i <= n; ++i) vis[i] = 0;
    dfs2(s, x);
    return vis[t];
}

int lower_bound(int l, int r) {
    while(l < r) {
        int mid = (l + r + 1) >> 1;
        if(check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

int main() {
    int _, u, v;
    _ = read();
    while(_--) {
        n = read(), m = read(), s = read(), t = read();
        init();
        int minn = inf, maxx = 0;
        for(int i = 1; i <= n; ++i) {
            a[i] = read();
            minn = min(minn, a[i]);
            maxx = max(maxx, a[i]);
        }
        while(m--) {
            u = read(), v = read();
            addedge(u, v);
            addedge(v, u);
            Union(u, v);
        }
        if(Find(s) != Find(t)) {
            printf("NO\n");
            continue;
        }
        printf("YES\n%d\n", lower_bound(minn, maxx));
    }
    return 0;
}

B. 比武招亲(上)(思维 + 组合数)

传送门

单独计算 i 的贡献,

i 作为最大值时,从 i 种数里取m - 1个数有多少种

i 作为最小值时,从 n - i + 1 种数里取m - 1个数有多少种

从 n 个球中取 m 个,有多少种取法

(1)不放回,有序   A_{n}^{m}

(2)不放回,无序   C_{n}^{m}

(3)放回,有序   n^{m}

(4)放回,无序   C_{n - 1 + m}^{m}

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll mod =998244353;
const ll N = 2e6 + 7;

ll inv[N], fac[N];
ll qpow(ll a, ll b) {
    ll ans = 1;
    a %= mod;
    while(b) {
        if(b & 1)
            ans = a * ans % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans % mod;
}

void init() {
    inv[0] = 1;
    fac[0] = 1;
    for(ll i = 1; i < N; ++i) {
        fac[i] = fac[i - 1] * i % mod;
        inv[i] = qpow(fac[i], mod - 2);
    }
}

ll com(ll a, ll b) {
    return fac[a] * inv[b] % mod * inv[a - b] % mod;
}

int main() {
    init();
    ll n, m;
    scanf("%lld%lld", &n, &m);
    ll ans = 0;
    for(ll i = 1; i <= n; ++i) {
        ans = (ans + i * com(i + m - 2, m - 1) % mod) % mod;
        ans = (ans - i * com(n - i + m - 1, m - 1) % mod + mod) % mod;
    }
    printf("%lld\n", ans);
}

D. 石子游戏(差分)

传送门

题解没看懂,贴一篇不错的解释过程https://blog.csdn.net/weixin_45509601/article/details/113951375

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 1e5 + 10;
const int inf = 0x3f3f3f3f;

ll a[N], b[N];

int main(){
	int t, n, k;
	scanf("%d", &t);
	while(t--) {
        scanf("%d%d", &n, &k);
        for(int i = 1; i <= n; ++i) {
            scanf("%lld", &a[i]);
            b[i] = a[i] - a[i - 1];
        }
        ll ans = 0;
        bool flag = 1;
        for(int i = 2; i <= n; ++i) {
            if(b[i] < 0) {
                if(i + k > n + 1) {
                    flag = 0;
                    break;
                }
                ans -= b[i];
                b[i + k] += b[i];
                b[i] = 0;
            }
            else if(b[i] > 0) {
                if((i - 1) % k) {
                    flag = 0;
                    break;
                }
                ans += (i - 1) / k * b[i];
                b[i] = 0;
            }
        }
        if(!flag) printf("-1\n");
        else printf("%lld\n", ans);
	}
	return 0;
}

E. 树上博弈(状压dp)

传送门

数据大小摆明了要状压,定义dp[i] 为状态 i 的最大值,i 中为1的二进制位表示结点已经被删除,为0表示未被删除。这里边的储存比较灵活,用 e[i] 的第 j 个二进制位为1表示 i、j 之间有边。

PS:今天注意到一个事情,memset初始化为 -inf 时,数组中的值和 -inf 不相等,随即科普了一下memset的初始化原理:(摘自巨佬~)

因为char是1字节,memset是按照字节赋值的,相当于把每个字节都设为那个数,所以char型的数组可赋任意值,int是4个字节,当memset(,1,sizeof()); 1相当于ASSCII码的1,1转为二进制00000001,当做一字节,一字节8位,int为4字节,所以初始化完每个数为00000001000000010000000100000001 = 16843009 .

memset(,0xff,sizeof()),0xff转为二进制11111111,int为4字节所以最后为11111111111111111111111111111111为-1。(化为二进制补位,然后再赋值)。可以全赋值为0,0的二进制位000000000000000000000000000000000,还可以是-1,-1的二进制就是11111111111111111111111111111111,所以memset可以直接初始化(0,-1);

例如:0xff转为二进制位11111111,正好是一位,0x1f小于0xff,而0x59也小于0xff,所以这些都可以用来初始化,只要能填满8位的二进制,就可以了。
如果你想初始最大化,第一位为符号位,不能为1,剩下全是1,也就是7个1,1111111化为十六进制正好为0x7f,所以memset(,0x7f,sizeof());就可以了

当我们想将某个数组全部赋值为无穷大时(例如解决图论问题时邻接矩阵的初始化),就不能使用memset函数而得自己写循环了(写这些不重要的代码真的很痛苦),我们知道这是因为memset是按字节操作的,它能够对数组清零是因为0的每个字节都是0,现在好了,如果我们将无穷大设为0x3f3f3f3f,那么奇迹就发生了,0x3f3f3f3f的每个字节都是0x3f!所以要把一段内存全部置为无穷大,我们只需要memset(a,0x3f,sizeof(a))

所以说以后再初始化 -inf 就不要憨憨地把数组的值跟 -inf 比较了,判断a[i]是否是初始值直接跟数组被赋的初始值比较。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1100000;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const ll mod = 998244353;
ll dp[N], a[25], e[25];

int main() {
    int t, n, u, v;
    scanf("%d", &t);
    while(t--) {
        scanf("%d", &n);
        for(int i = 0; i < N; ++i) dp[i] = -inf;
        for(int i = 0; i <= n; ++i) e[i] = 0;
        for(int i = 0; i < n; ++i) scanf("%lld", &a[i]);
        for(int i = 0; i < n - 1; ++i) {
            scanf("%d%d", &u, &v);
            u--, v--;
            e[u] += 1 << v;
            e[v] += 1 << u;
        }
        for(int i = 0; i < n; ++i) dp[1 << i] = a[i];
        int bit = 1 << n;
        for(int i = 1; i < bit; ++i) {
            if(dp[i] == -inf) continue;
            for(int j = 0; j < n; ++j) {
                int tmp = 1 << j;
                if(!(i & tmp) && (i & e[j]))
                    dp[i | tmp] = max(dp[i | tmp], a[j] - dp[i]);
            }
        }
        printf("%lld\n", dp[bit - 1]);
    }
    return 0;
}

F. 我的心是冰冰的(签到)

传送门

树没有环,最多只要两种颜色

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf = 0x3f3f3f3f;
const ll MAXN = 2e5 + 7;
const ll MAXM = 5e5 + 7;

int main() {
    int t, n, u, v;
    scanf("%d", &t);
    while(t--) {
        scanf("%d", &n);
        for(int i = 1; i < n; ++i) scanf("%d%d", &u, &v);
        if(n == 1) printf("1\n");
        else printf("2\n");
    }
	return 0;
}

G. 模仿游戏(贪心)

传送门

哥哥肯定是要优先打新怪的,如果某时刻哥哥不在打怪但是妹妹队列中还有怪,哥哥要帮妹妹打一个

思路参考

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 2e5 + 7;
const ll M = 4e5 + 7;
const ll inf = 0x3f3f3f3f;
const ll mod = 998244353;

struct node {
    ll id, x;
    bool operator < (const node &a) const {
        return id < a.id;
    }
} s[N];

vector<ll>tim[N];
ll vis[N];

int main() {
    ll t, n;
    scanf("%lld", &t);
    while(t--) {
        scanf("%lld", &n);
        for(ll i = 1; i <= n; ++i) vis[i] = 0;
        for(ll i = 1; i < N; ++i) tim[i].clear();
        for(ll i = 1; i <= n; ++i) {
            scanf("%lld%lld", &s[i].id, &s[i].x);
            tim[s[i].id].emplace_back(s[i].x);
        }
        sort(s + 1, s + n + 1);
        ll q1 = 0, q2 = 0;
        for(ll i = 1; i <= 200000; ++i) {
            ll siz = tim[i].size();
            for(ll j = 0; j < siz; ++j) {
                if(vis[tim[i][j]] == 0) {
                    q1 = max(q1 + 1, i);
                    vis[tim[i][j]] = i;
                }
                else {
                    if(vis[tim[i][j]] == i) q2 = max(q2 + 1, i + 1);
                    else q2 = max(q2 + 1, i);
                }
            }
            if(q1 < q2 && q1 < i) q1 = i, q2--;
        }
        printf("%lld\n", max(q1, q2));
    }
    return 0;
}

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值