"蔚来杯"2022牛客暑期多校训练营6 A(放缩?,构造),M(博弈dp)

"蔚来杯"2022牛客暑期多校训练营6 A(放缩?,构造),M(博弈dp)

题意

给一个长 \(n\) 的数组 \(a\) ,构造长度为 \(m\) 环形序列 \(c\) 使得每 \(a_i\) 个数字至少出现一次 \(i\)

数据保证 \(\sum_{i=1}^{n} \frac{1}{a_i} \le \frac{1}{2}\)

思路

暂且不管 \(\sum_{i=1}^{n} \frac{1}{a_i} \le \frac{1}{2}\) 这个奇怪的约束,我们考虑何时一定有解。

如果设 \(m\)\(c\) 的长度,任意一个数字出现的次数是 \(\lceil \frac{m}{a_i} \rceil\) 。于是有 \(\sum_{i=1}^{n} \lceil \frac{1}{a_i} \rceil \le 1\)。和约束形式一致,所以它其实是保证有解的。


另外有一种感性的想法,\(a_i\) 可以认为是步长或者周期,于是 \(\frac{1}{a_i}\) 就是出现频率。那么只要所有数的出现频率不超过 \(1\) 就可以了,也就是 \(\sum_{i=1}^{n} \lceil \frac{1}{a_i} \rceil \le 1\)


另一方面,因为 \(\sum_{i=1}^{n} \lceil \frac{1}{a_i} \rceil \le \frac{1}{2} \le 1\) 。说明我们可以一定程度上缩小 \(a_i\)

因为 \(\sum_{i=1}^{n} \lceil \frac{1}{a_i /2} \rceil \le 1\) ,所以最大可以将距离缩小一半。

考虑构造方案,因为即使不缩放也一定有解,且个别元素可以缩小距离。所以可以暴力构造,对 \(i\) 每隔 \(a_i\) 就放,如果该位置被占用就回退到第一个未被占用的地方。回退可以用并查集加速或者直接开set维护。(并不会证正确性)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<queue>
#include<map>
#include<stack>
#include<string>
#include<functional>
#include<random>
#include<iomanip>
#include<bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3fll
#define ull unsigned long long
#define endl '\n'
// #define int long long
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
using namespace std;
mt19937 mrand(random_device{}());
int rnd(int x) { return mrand() % x;}
using PII = array<int,2>;
const int MAXN =10 + 1e6 ,mod=1e9 + 7;
int fa[MAXN];
int root(int x) {
    return fa[x] == x ? x : (fa[x] = root(fa[x]));
}
void solve()
{    
    int n; cin >> n;
    vector<PII>a(n + 1);
    for(int i = 1;i <= n;i += 1) {
        cin >> a[i][0];
        a[i][1] = i;
    }

    sort(a.begin() + 1,a.end());

    iota(fa,fa + MAXN,0);

    int m = 1e5;
    vector<int> c(m+1);
    int beg = 1;
    for(int i = 1;i <= n;i += 1) {
        auto [d,val] = a[i];
        while(c[beg])
            beg += 1;
        for(int j = beg;j <= m;j = root(j + d)) {
            c[j] = val;
            fa[j] = j - 1;
            assert(j-1>=0);
        }
    }
    
    vector<int>b;
    for(auto &it : c)
        if(it)
            b.push_back(it);
    
    cout << b.size() << endl;
    for(auto it : b) {
        cout << it << " ";
    }
}
signed main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);

    //int T;cin>>T;
    //while(T--)
        solve();

    return 0;
}

另外一种构造就是先进行缩放,将其缩放到 \(2^k\) 上,这样如果从小到大处理,每次处理都不会被占用的情况,无需回退,写起来简单些。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<queue>
#include<map>
#include<stack>
#include<string>
#include<functional>
#include<random>
#include<iomanip>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3fll
#define ull unsigned long long
#define endl '\n'
#define int long long
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
using namespace std;
mt19937 mrand(random_device{}());
int rnd(int x) { return mrand() % x;}
using PII = array<int,2>;
const int MAXN =10 + 1e6 ,mod=1e9 + 7;

void solve()
{    
    int n; cin >> n;
    vector<PII>a(n + 1);
    for(int i = 1;i <= n;i += 1) {
        cin >> a[i][0];
        int x = 1;
        while(x * 2 < a[i][0])
            x <<= 1;
        a[i][0] = x;
        a[i][1] = i;
    }

    sort(a.begin() + 1,a.end());

    int beg = 0,m = a.back()[0];
    vector<int>c(m);
    for(int i = 1;i <= n;i += 1) {
        while(c[beg])
            beg += 1;
        auto [stp,val] = a[i];
        for(int j = beg;j < m;j += stp)
            c[j] = val;
    }
    cout << m << endl;
    for(auto &it : c)
        cout << (it ? it : 1) << " ";
    cout << endl;
}
signed main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);

    //int T;cin>>T;
    //while(T--)
        solve();

    return 0;
}

M

题意

AB两个人在一个网格上走,只允许向右或向左走,每次走一步,网格上有一些AB的必胜点,并且如果走到右下角仍没有决出胜负算平局。

A绝对聪明,B随机走。求A从 \((1,1)\) 出发是否一定可以胜、败、平局。

思路

终止态比较好处理所以倒着做dp就行。

定义 \(f[i][j]\) 表示从 \((i,j)\) 开始是否必胜。转移见代码。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<queue>
#include<map>
#include<stack>
#include<string>
#include<functional>
#include<cassert>
#include<random>
#include<iomanip>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3fll
#define ull unsigned long long
#define endl '\n'
#define int long long
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
using namespace std;
mt19937 mrand(random_device{}());
int rnd(int x) { return mrand() % x;}
using PII = array<int,2>;
const int MAXN =10 + 2e5 ,mod=1e9 + 7;

void solve()
{    
    int n,m; cin >> n >> m;
    vector<vector<char>> a(n + 1,vector<char>(m + 1));
    for(int i = 1;i <= n;i += 1)
        for(int j = 1;j <= m;j += 1)
            cin >> a[i][j];

    auto calc = [&](char want) {
        auto f = a;
        if(want != '.') {// A,B
            for(int i = 1;i <= n;i += 1) {
                for(int j = 1;j <= m;j += 1) {
                    if(f[i][j] == want)
                        f[i][j] = 1;
                    else if(f[i][j] != '.')
                        f[i][j] = 0;
                    else 
                        f[i][j] = -1;
                }
            }
            f[n][m] = (a[n][m] == want);
        }else {
            for(int i = 1;i <= n;i += 1)
                for(int j = 1;j <= m;j += 1) {
                    if(f[i][j] == 'A' or f[i][j] == 'B')
                        f[i][j] = 0;
                    else 
                        f[i][j] = -1;
                }
            f[n][m] = (a[n][m] == want);
        }

        for(int i = n;i > 0;i -= 1)
            for(int j = m;j > 0;j -= 1) {
                if(f[i][j] != -1)
                    continue;
                // f[i][j] = f[i+1][j]|f[i][j+1]
                if(i + 1 <= n and j + 1 <= m and i + j & 1)
                    f[i][j] = f[i + 1][j] & f[i][j + 1];
                else if(i + 1 <= n and j + 1 <= m)
                    f[i][j] = f[i + 1][j] | f[i][j + 1];
                else if(i + 1 <= n)
                    f[i][j] = f[i + 1][j];
                else 
                    f[i][j] = f[i][j + 1];
            }
        return f[1][1];
    };

    string t[2] = {"no","yes"};
    cout << t[calc('A')] << " " << t[calc('.')] << " " << t[calc('B')] << endl;
}
signed main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);

    int T;cin>>T;
    while(T--)
        solve();

    return 0;
}

大水题,但是因为一点不会博弈,想了些奇怪的思路···

posted @ 2022-08-09 12:54  Mxrurush  阅读(3)  评论(0编辑  收藏  举报