Educational Codeforces Round 64-小结

2023大厂真题提交网址(含题解):

www.CodeFun2000.com(http://101.43.147.120/)

最近我们一直在将收集到的机试真题制作数据并搬运到自己的OJ上,供大家免费练习,体会真题难度。现在OJ已录入50+道2023年最新大厂真题,同时在不断的更新。同时,可以关注"塔子哥学算法"公众号获得每道题的题解。
在这里插入图片描述

前言:

已经过了: A,B,C,D,E,F

这一场比较牛,第三题就已经2000分了。

A.

std出锅了。主要是3 1 2 这样的会使得共点存在.

B. 贪心: 1800
题目大意:

给你一个只含小写字母的字符串。问你重新排列这字符串使得相邻字符在字符表中都不相邻.(a,z不相邻)。

思路:

方法一:随机化

其实我们发现相同的字母放在一起是最好的。如果分开放,会导致更多的限制.

所以先去重,接着考虑如何放。当时没想太清楚。只是觉得合法的情况占绝大多数。不合法的情况反而少。所以random_suffle 500次check即可.500次以内没出正确结果就没有合法情况.

使用方法:

srand(unsigned(time(NULL)));
...
random_shuffle(a.begin(), a.end());

方法二:贪心
不让我们相差为1.那么就构造相差为2呗。那么就将奇数位放在一起,偶数位的字母放在一起。然后拼在一起即可。

其实这个问题的非法情况很少。当不同字符个数 d i f ≥ 4 dif \geq 4 dif4的时候,就一定存在解了。所以就连dfs都能做.

d i f = 1 dif=1 dif=1 不存在解。

d i f = 2 ∣ ∣ 3 dif=2||3 dif=2∣∣3 ,当它们字母序相邻时不存在解。反之有解.

C.
题目大意:

在一维坐标轴上给你若干个点。问你匹配最多的点对。使得两点之间的距离大于等于z.
n ≤ 1 e 5 n \leq 1e5 n1e5

题目思路:贪心+二分

对于任意一个已经匹配的点对 ( l , r ) (l,r) (l,r).如果有未匹配的点 x < l x < l x<l.那么可以换成 ( x , r ) (x,r) (x,r)而不会使得答案变差。
对于任意一个已经匹配的点对 ( l , r ) (l,r) (l,r).如果有未匹配的点 x > r x > r x>r.那么可以换成 ( l , x ) (l,x) (l,x)而不会使得答案变差。

所以对于一个确定个数 k k k,如果能找到 k k k个点对。那么这 k k k个点对一定可以被转化成最左边 k k k个点与最右边 k k k个点相匹配。然后为了最大化两点对之间的最小差距,我们一定是左边最小匹配右边最小,依次这么进行。

然后 k k k越大,越难成立。符合单调性。二分答案即可。

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define vi vector<int>
#define vl vector<ll>
const int maxn = 1e6 + 5;
const int mod = 1e9 + 7;
int a[maxn] , b[maxn];
int main()
{
    ios::sync_with_stdio(false);
    int n , z; cin >> n >> z;
    for (int i = 1 ; i <= n ; i++) cin >> a[i];
    sort(a + 1 , a + 1 + n);
    int l = 1 , r = n / 2;
    while (l <= r){
        int mid = l + r >> 1 , cnt = 0;
        for (int i = n - mid + 1 ; i <= n ; i++)
            b[++cnt] = a[i];
        bool ok = true;
        for (int i = 1 ; i <= mid ; i++)
            if (b[i] - a[i] < z) ok = false;
        if (ok) l = mid + 1;
        else r = mid - 1;
    }
    cout << r << endl;
    return 0;
}
D. 0-1-Tree: 树形dp/巧妙并查集
题目大意:

给你一张只有0/1边权的树。问你有多少条有序路径满足它先访问一系列0,后面全访问1.

方法一:树形dp

这个方法很显然。过程类似树形背包。对一个点,每次新增一颗子树,就计算一遍贡献。边dp边求答案.

d p ( i , 0 / 1 / 2 / 3 ) dp(i,0/1/2/3) dp(i,0/1/2/3)代表从 i i i的子树的点到 i i i点路径为 0..0 / 11..1 / 00..11 / 11..00 0..0/11..1/00..11/11..00 0..0/11..1/00..11/11..00形式的路径个数.然后对于 i i i点的不同子树,贡献拼接在一起即可。类似聪明可可那题

具体过程:三步走

1.先通过 d p ( v ) dp(v) dp(v)求出到 u u u点的路径个数,用 t m p tmp tmp数组存.

2.因为是边权,我们对于 u u u点,拉出一个连接着 u u u的,边权等于 ( u , v ) (u,v) (u,v)的虚点 x x x.然后统计点对答案.然后删除虚点.

3.将 t m p tmp tmp数组归到 d p ( u ) dp(u) dp(u)中。

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define vi vector<int>
#define vll vector<ll>
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
int a[maxn];
vector<pii> e[maxn];
ll dp[maxn][4] , tmp[5];
ll ans = 0;
void dfs (int u , int fa , int c)
{
    bool ye = true;
    for (auto g : e[u]){
        int v = g.first , w = g.second;
        if (v == fa) continue;
        ye = false;
        dfs(v , u , w);
        // tmp 代表从子树v到u这个点有多少个路径
        memset(tmp , 0 , sizeof tmp);
        if (w == 0){
            tmp[0] = dp[v][0] + 1;
            tmp[1] = 0;
            tmp[2] = dp[v][2] + dp[v][1];
            tmp[3] = 0;
        }else {
            tmp[0] = 0;
            tmp[1] = dp[v][1] + 1;
            tmp[2] = 0;
            tmp[3] = dp[v][3] + dp[v][0];
        }
        // 统计答案
        dp[u][w]++;
        ans += dp[u][0] * (2 * tmp[0] + tmp[1] + tmp[2]);
        ans += dp[u][1] * (2 * tmp[1] + tmp[0] + tmp[3]);
        ans += dp[u][2] * tmp[0];
        ans += dp[u][3] * tmp[1];
        dp[u][w]--;
        for (int i = 0 ; i < 4 ; i++){
            dp[u][i] += tmp[i];
        }
    }
    return ;
}
int main()
{
    ios::sync_with_stdio(false);
    int n; cin >> n;
    for (int i = 1 ; i < n ; i++){
        int x , y , z;  cin >> x >> y >> z;
        e[x].pb(mp(y,z));
        e[y].pb(mp(x,z));
    }
    dfs(1 , 0 , 0);
    cout << ans << endl;
    return 0;
}
方法二:巧妙并查集

由于左半边全是 0 0 0,右半边全是 1 1 1.那这条路径上一定会有一个点是 0 / 1 0/1 0/1分割点。所以我们可以枚举这个分割点。这就是这个方法的核心思想。

有了这个思想这题就很简单了,求出只走0的连通块,再求出只走1的连通块。

对于点 x x x,答案就是 s z [ 0 ] ∗ s z [ 1 ] − 1 sz[0] * sz[1] -1 sz[0]sz[1]1.减一是去除掉 x − > x x->x x>x这样的路径。

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define vi vector<int>
#define vll vector<ll>
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
struct Node{int x , y;};
vector<Node> e[2];
int f[2][maxn];
ll sz[2][maxn];
int getf (int x , int f[]) {return x == f[x] ? x : f[x] = getf(f[x] , f);}
bool mer (int a , int b , int f[] , ll sz[])
{
    int fa = getf(a , f) , fb = getf(b , f);
    if (fa == fb) return false;
    f[fb] = fa;
    sz[fa] += sz[fb];
    return true;
}
int main()
{
    ios::sync_with_stdio(false);
    int n; cin >> n;
    for (int i = 1 ; i < n ; i++){
        int x , y , z;  cin >> x >> y >> z;
        e[z].push_back({x , y});
    }
    ll ans = 0;
    for (int t = 0 ; t <= 1 ; t++){
        for (int i = 1 ; i <= n ; i++) f[t][i] = i , sz[t][i] = 1;
        for (auto g : e[t]) mer(g.x , g.y , f[t] , sz[t]);
    }
    for (int i = 1 ; i <= n ; i++) ans += sz[0][getf(i , f[0])] * sz[1][getf(i , f[1])] - 1;
    cout << ans << endl;
    return 0;
}

// 后面两题反而还容易了。。

E. Special Segments of Permutation
题目大意:

给你一个长度为 n n n的排列,问你有多少个子数组满足: a l + a r = m a x { a l , . . , a r } a_l+a_r=max\{a_l,..,a_r\} al+ar=max{al,..,ar}.
n ≤ 1 e 5 n \leq 1e5 n1e5

题目思路:

考虑枚举。枚举左式的话就是枚举左端点,找右端点。不太好做。发现枚举右边比较好做。对于整体最大值,所有经过它的子数组右式都等于它.又由于是排列,我们只需要枚举一边,找另一边是否存在 m x − a mx-a mxa即可。找完之后分成两个子区间分治求解.因为子数组分为,完全在 m x mx mx左边,完全在 m x mx mx右边。跨过 m x mx mx的。 解决完第三种后递归求解两边即可。注意我们要枚举短的一边,这样复杂度最大为 O ( n l o g n ) O(nlogn) O(nlogn).

为了找到区间最大值,我们需要再求个st表.

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define vi vector<int>
#define vll vector<ll>
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
int a[maxn] , pos[maxn] , dp[maxn][20] , lg[maxn] , ans , n;
void st ()
{
    lg[0] = -1;
    for (int i = 1 ; i <= n ; i++){
        dp[i][0] = a[i];
        lg[i] = lg[i >> 1] + 1;
    }
    for (int j = 1 ; j <= 19 ; j++)
        for (int i = 1 ; i + (1 << j) - 1 <= n ; i++)
            dp[i][j] = max (dp[i][j - 1] , dp[i + (1 << (j - 1))][j - 1]);
}
int ask (int l , int r)
{
    int len = r - l + 1;
    return max(dp[l][lg[len]] , dp[r - (1 << lg[len]) + 1][lg[len]]);
}
void dfs (int l , int r)
{
    if (r - l + 1 < 3) return ;
    int mx = ask(l , r) , id = pos[mx];
    int len1 = id - l , len2 = r - id;
    if (len1 < len2){
        for (int i = l ; i < id ; i++){
            int v = pos[ mx - a[i] ];
            if (id + 1 <= v && v <= r) ans++;
        }
    }else {
        for (int i = id + 1 ; i <= r ; i++){
            int v = pos[ mx - a[i] ];
            if (l <= v && v < id) ans++;
        }
    }
    dfs(l , id - 1);
    dfs(id + 1 , r);
    return ;
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> n;
    for (int i = 1 ; i <= n ; i++){
        cin >> a[i];
        pos[a[i]] = i;
    }
    st();
    dfs(1 , n);
    cout << ans << endl;
    return 0;
}
F. Card Bag
题目大意:

n n n个数.你随机从里面拿数。当你拿出来的序列是严格递增的,并且最后一次拿的数等于上一次拿的数时,你赢了,如果还是严格递增,继续。如果不符合递增,输了。拿完也输了。问你这个过程赢的概率。
1 ≤ a i ≤ n ≤ 5000 1 \leq a_i \leq n \leq 5000 1ain5000

题目思路:

性质:
1.每次拿数事件独立
2.一个数 k k k出现次数至少2次才可能以它为结尾作为赢的局面。

一个显然的做法:对原数组排序去重。枚举以 x x x为结尾作为赢的局面。那么接下来再枚举这个序列最终的长度。

那么问题转化为:求从大于等于 x x x的数中选择出 k k k种不同的数,且必选 x x x的方案数。很简单的一个dp求解出来即可。令其为 d p ( x , k ) dp(x,k) dp(x,k)

a n s x = ∑ i = 1 a [ i ] ≤ x d p ( x , k ) ∗ ( n u m [ x ] − 1 ) ∏ j = n n − i j ans_x=\sum_{i=1}^{a[i]\leq x}\frac{dp(x,k)*(num[x]-1)}{\prod_{j=n}^{n-i}j} ansx=i=1a[i]xj=nnijdp(x,k)(num[x]1)

答案累和即可。

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define vi vector<int>
#define vll vector<ll>
const int maxn = 5000 + 5;
const int mod = 998244353;
int a[maxn];
ll ksm (ll a , ll b){ ll ans = 1 , base = a;
while (b){if (b & 1) ans = ans * base % mod;b >>= 1;base = base * base % mod;}return ans;}
ll dp[maxn][maxn] , inv[maxn];
int main()
{
    ios::sync_with_stdio(false);
    int n; cin >> n;
    int m = n;
    for (int i = 1 ; i <= n ; i++) inv[i] = ksm(i , mod - 2);
    map<int,ll> q;
    for (int i = 1 ; i <= n ; i++){
        cin >> a[i];
        q[a[i]]++;
    }
    sort(a + 1 , a + 1 + n);
    n = unique(a + 1 , a + 1 + n) - a - 1;
    //reverse(a + 1 , a + 1 + n);
    dp[0][0] = 1;
    for (int i = 1 ; i <= n ; i++){
        dp[i][0] = 1;
        for (int j = 1 ; j <= i ; j++){
            dp[i][j] = (dp[i - 1][j] + q[a[i]] * dp[i - 1][j - 1]%mod)%mod;
        }
    }
    ll ans = 0;
    for (int i = 1 ; i <= n ; i++){
        ll d , res , fm = inv[m];
        for (int j = 1 ; j <= i ; j++){
            fm = fm * inv[m - j] %mod;
            d = (dp[i][j] - dp[i - 1][j] + mod) % mod;
            res = fm * d %mod;
            res = res * (q[a[i]] - 1) % mod;
            ans = (ans + res) % mod;
        }
    }
    cout << ans << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值