"蔚来杯"2022牛客暑期多校训练营6 A(放缩?,构造),M(博弈dp)
"蔚来杯"2022牛客暑期多校训练营6 A(放缩?,构造),M(博弈dp)
A
题意
给一个长 \(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;
}
大水题,但是因为一点不会博弈,想了些奇怪的思路···