C. Brave Seekers of Unicorns

题目给出了一个$unicorn$序列的定义,暂且叫它完美序列,完美序列满足如下条件:

  • 非空递增且序列元素的值域为 \([1,n]\)
  • 没有三个连续的元素异或和 \(=0\)

给定 一个整数n,对完美序列计数(需要求出完美序列个数),答案需要对 \(998244353\) 取模

Solution

设 $dp_i$ 表示以$i$ 结尾的数组的方案数即

\[dp[i]= \sum\limits_{j=1}^{i-1} (dp[j]-dp[i \bigoplus j]) (i⨁j<j)
\]

求出等于 \(i\oplus j\) 的数去掉同时维护前缀和,对于二进制下的 \(i\) ,如果第 \(x\) 位为 \(1\),那么$ [2x,2{x+1}-1]$之间的数都是(除了最高位的\(1\)除外),解释来说,因为要考虑$i⊕j⊕k=0 \(的情况,根据异或的性质\)i,j,k$三个数的每一个二进制数位上最多只有两个\(1\),那就枚举\(i\)的除最高位的二进制位为\(1\) 的数位 \(x\),固定 \(j\) 的第\(x\)位是也是\(1\),此时\(k\) 的第 $x $位为必然为\(0\),那么第\(x\) 位往后都可以随便乱取,不会有影响,也就是\(k\)的范围是\([2^x,2^{x+1}-1]\)

Code
ll dp[N],f[N],n;
void solve() {
cin >> n;
for (int i = 1; i <= n; ++i) {
f[i] = dp[i - 1] + 1;
int num = i, bit = 0;
while (num) {
if ((num & 1) && num != 1) {//此时i的位上是1
f[i] = (f[i] - dp[min(ksm(2, bit + 1) - 1, (ll) i)] + dp[min(ksm(2, bit) - 1, (ll) i)] + mod) % mod;
}
num >>= 1;
bit++;
}
dp[i] = (dp[i - 1] + f[i]) % mod;
}
cout << dp[n] << endl;
}

D. Bank Security Unification

题意:有 $n$个路由器,第 $i$ 个路由器的的频度是 $f_i$, 你选择任意个路由器打开,假设打开$k$了个路由器 $i_1 , i_2 , … , i_ k$,
,则此时的网络安全系数的值是 $∑ _{i=1}^{k-1}a_i\oplus a_{i+1}$ ,输出网络安全系数的最大值是多少,反之就是说从一个长度为 $n$ 序列 $a$ 中取出一个子序列,使得相邻两个元素的按位与值的和最大。求出这个最大的和

Solution

首先我们希望按位与和最大,就是在希望我们选取的相邻两个元素的二进制位尽可能相同,显然$dp[i][j]$表示当前在$i$位置 ,打开$j$ 个路由器的最大按位与值(安全系数),但是此时暴力转移的$dp$是$O(n^2)$ 的,而且$n=1e6$显然也开不了二维的$dp$数组,那么可以选择优化,让$dp_i$表示当前最后一位为 $i$ 的子序列答案最大是多少,如果考虑当前的一位,显然是与之二进制相同的位是贡献最大的,所以记录 $lst[i][0/1] $表示二进制第 $i$ 位上一个是$ 0/1$ 最近的位置在哪里,每次只从对应的$lst$ 转移而来,然后每次取更新$lst$的值

Code
ll n;
ll f[N],dp[N],bit[66][2];
void solve() {
cin >> n;
ll mx_bit = 0;
for (int i = 1; i <= n; ++i) {
cin >> f[i];
mx_bit = max(mx_bit, f[i]);
}
mx_bit = ceil(log2(mx_bit)) + 1;
// dbg(mx_bit);
for (int i = 1; i <= n; ++i) {//贪心取最近的相同的二进制位
if (i > 1) {
for (int j = 0; j<=mx_bit; ++j) {
int tmp = ((f[i] & (1ll << j)) > 0);
int pos = bit[j][tmp];//最近的每位二进制&相同的位置
dp[i] = max(dp[i], dp[pos] + (f[pos] & f[i]));
}
}
// dbg(dp[i]);
for (int j = 0; j <= mx_bit; ++j) {//更新
bit[j][((f[i] & (1ll << j)) > 0)] = i;
}
}
ll ans = 0;
for (int i = 1; i <= n; ++i) {
ans = max(ans, dp[i]);
}
cout << ans << endl;
}

G. Biological Software Utilities

给出一种好树的定义,删除掉一些边后能变成两两匹配的树是好树。现在给出节点的个数,求好树有多少种

Solution

考虑到树的形状没有定,且也不知道删除哪一些点,但是发现最后的情况一定是两两匹配的,那么倒过来想就是把很多两两匹配完的点,通过连一些边变成一些树。首先把 $2n$ 个点分成 $n$ 个两两相同的组合的总共情况是

\[\frac{(2n)!}{n!*2^n} = \frac{\begin{pmatrix} 2n\\n\end{pmatrix} * n!}{2^n}
\]

然后每个组合其实可以看成是一个点,然后有一个重要的结论,\(n\) 个点的生成树个数为 \(n^{n-2}\) 个。然后由于每个最后连的边,具体到这道题里都是四条,所以最后还要乘上 \(4^{n - 2}\)

所以最后的公式就是

\[\frac{\begin{pmatrix} 2n\\n\end{pmatrix}*n!}{2^n}*4^{n-1}*n^{n-2}
\]
Code
#include<bits/stdc++.h>
#define debug(x) cerr << #x << " = " << x << '\n';
using namespace std;
typedef long long ll;
#define int ll
const int inf = 0x3f3f3f3f;
const ll mod = 998244353;
const int N = 1e6 + 10;
int fac[N], inv[N];
ll ksm(ll a, ll b) {
ll ans = 1;
while (b) {
if (b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
ll c(ll k, ll n) {
return fac[n] % mod * inv[k] % mod * inv[n - k] % mod;
}
void solve() {
int n;
cin >> n;
if (n & 1) {
cout << 0 << '\n';
return;
}
if (n == 2) {
cout << 1 << '\n';
return;
}
int k = n / 2;
ll ans = c(k, n) * fac[k] % mod;
for (int i = 1; i <= k; ++i) {
ans = ans * ksm(2, mod - 2) % mod;
}
for (int i = 1; i <= k - 1; ++i) {
ans = ans * 4 % mod;
}
cout << ans * ksm(k, k - 2) % mod << '\n';
}
signed main() {
ios::sync_with_stdio(false);cin.tie(nullptr),cout.tie(nullptr);
int t = 1;
// cin >> t;
fac[0] = inv[0] = 1;
for (int i = 1; i <= 1e6; ++i) {
fac[i] = fac[i - 1] * i % mod;
inv[i] = ksm(fac[i], mod - 2) % mod;
}
while(t--) {
solve();
}
return 0;
}

I. Binary Supersonic Utahraptors

题意:略

Solution

答案恒定不变,签到题

Code
int n,m,k;
void solve() {
int cnt_y=0,cnt_r=0;
cin>>n>>m>>k;
for(int i=1,x;i<=n;++i){
cin>>x;
cnt_y+=(x==0);
}
for(int i=1,x;i<=m;++i){
cin>>x;
cnt_r+=(x>0);
}
cout<

J. Burnished Security Updates

题意:略

Solution

考虑二分图染色(签到题)

Code

const int maxn = 3e5 + 100, mod = 1e9 + 7;
int n, m, cnt, sz, col[maxn], ok = 1;
vector e[maxn];
void dfs(int u, int fa, int co) {
col[u] = co;
if (co == 1) cnt++;
sz++;
for (int v: e[u]) {
if (v == fa) continue;
if (col[v] == col[u]) {
ok = 0;
return;
}
if (!col[v]) dfs(v, u, -co);
}
}
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
int ans = 0;
for (int i = 1; i <= n; i++) {
if (!col[i]) {
cnt = sz = 0;
dfs(i, 0, 1);
if (!ok) {
cout << -1;
return 0;
}
ans += min(sz - cnt, cnt);
}
}
cout << ans;
}

M. Brilliant Sequence of Umbrellas

构造一个长度至少为 $\left \lceil \frac{2}{3}\sqrt{n} \right \rceil $ 的递增数组,使得两两做 $gcd$ 也是递增的。

Solution

由于 $gcd$ 的数组也是递增的,那么要让数组尽可能的长,就要让 $gcd$ 增长的尽可能慢。

通过打表前面几个数

\[1 \ 2 \ 6 \ 15 \ 35 \ 56 \ 72 \ 99 \\
gcd数组:2 \ 3 \ 5 \ 7 \ 8 \ 9 \ 11
\]

可以发现 \(gcd\) 数组要添加的数,应当是与最后一个和倒数第二个都互质的数,然后原数组就是 \(gcd\) 数组相乘回去

Code

typedef long long ll;
const int mod = 998244353;
const int N = 1e5 + 10;
vector a{1, 2, 6}, g{1, 2, 3};
void solve() {
ll n;
cin >> n;
ll k = ceil(2 * sqrt(n) * 1.0 / 3);
int cnt = 0;
for (int i = 0; i < a.size(); ++i) {
if (a[i] <= n) cnt++;
if (cnt >= k) break;
}
if (cnt >= k) {
cout << k << '\n';
for (int i = 0; i < k; ++i) {
cout << a[i] << " \n"[i == k - 1];
}
} else {
cout << "-1\n";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int t = 1;
for (int i = 4; i <= 1e6; ++i) {
if (__gcd(g.back(), i) == 1 $&&$ __gcd(g[g.size() - 2], i) == 1) {
if (g.back() * i <= 1e12) a.push_back(g.back() * i), g.push_back(i);
else break;
}
}
while (t--) {
solve();
}
return 0;
}

N. Best Solution Unknown

$n$个数,总共进行$n-1$次操作。每次操作选两个相邻的数,删去较小值,若两数相等,则任意删除其一,留下的数增大$1$。 问最后有可能剩下的数有哪些,按升序输出下标。
每次随机取比赛,求会有哪些人可能赢

Solution

队友:线段树+分治
因为本题中删除一个数后会改变相邻关系,所以某一段区间的最大值可以把该区间剩余的数删完的。那么把当前区间的最大值挑出来加入答案后,会分成左右两个小区间,其各自的区间最大值也是有可能留到最后的,因此可以用分治加线段树维护区间最值来做这题。

\([1,2,5,3,2]\) ,对于这组数来说,最终留下的数一定是5,因为在取出最大值5后,分成了 \([1, 2]\) 和$ [3, 2]$ 两个区间,但是我们发现两个区间的最大值2和3只能成长到3和4,也就是\([3,5,4]\), 所以无法留到最后。从这里我们可以发现,对于每个分出来的区间,都会有一个区间下限,如果该区间的最大值在成长之后达不到这个下限,那么这个区间就是无法产生“胜者”的。

接下来考虑如何获得每个区间的下限。我们用三元组\({L, R, V}\)来表示\([L, R]\)这段区间的下限值为\(V\),假设该区间最大值的下标为\(M\),那对于左区间的下限\(V_1\)来说,不仅要“打败"\(a_M\), 还要在删完右区间后达到\(V\), 只有这样才能继续往大的区间吃。

即\(V_1 = Max(a_M,V+M-R-1)\)。

Code

int n, a[maxn], maxx[maxn * 4];
vector ans;
void build(int id, int l, int r) {
if (l == r) {
maxx[id] = l;
} else {
build(ls, l, mid);
build(rs, mid + 1, r);
if (a[maxx[ls]] > a[maxx[rs]])
maxx[id] = maxx[ls];
else
maxx[id] = maxx[rs];
}
}
int query(int id, int l, int r, int ql, int qr) {
if (ql <= l && qr >= r) return maxx[id];
if (qr <= mid)
return query(ls, l, mid, ql, qr);
else if (ql > mid)
return query(rs, mid + 1, r, ql, qr);
else {
int res1 = query(ls, l, mid, ql, mid);
int res2 = query(rs, mid + 1, r, mid + 1, qr);
if(a[res1] > a[res2]) return res1;
else return res2;
}
}
void solve(int l, int r, int maxx) {
int pos = query(1, 1, n, l, r);
if(a[pos] + r - l < maxx) return;
ans.push_back(pos);
if(l <= pos - 1) solve(l, pos - 1, max(a[pos], maxx + pos - 1 - r));
if(pos + 1 <= r) solve(pos + 1, r, max(a[pos], maxx + l - pos - 1));
}
int main() {
ios;
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
build(1, 1, n);
solve(1, n, 0);
cout << ans.size() << endl;
sort(ans.begin(), ans.end());
for (int i : ans) cout << i << " ";
}

2020-2021 Winter Petrozavodsk Camp, Belarusian SU Contest (XXI Open Cup, Grand Prix of Belarus) 题解的更多相关文章

  1. GYM 100608G 记忆化搜索+概率 2014-2015 Winter Petrozavodsk Camp, Andrew Stankevich Contest 47 (ASC 47)

    https://codeforces.com/gym/100608 题意: 两个人玩游戏,每个人有一个长为d的b进制数字,两个人轮流摇一个$[0,b-1]$的骰子,并将选出的数字填入自己的d个空位之中 ...

  2. Petrozavodsk Summer-2016. Warsaw U Contest, XVI Open Cup Onsite.

    Petrozavodsk Summer-2016. Warsaw U Contest, XVI Open Cup Onsite. Problem A. Gambling Problem B. Colo ...

  3. 2015-2016 Petrozavodsk Winter Training Camp, Nizhny Novgorod SU Contest (5/9)

    2015-2016 Petrozavodsk Winter Training Camp, Nizhny Novgorod SU Contest B. Forcefield 题意 给你一维平面上n个镜子 ...

  4. 2015 UESTC Winter Training #7【2010-2011 Petrozavodsk Winter Training Camp, Saratov State U Contest】

    2015 UESTC Winter Training #7 2010-2011 Petrozavodsk Winter Training Camp, Saratov State U Contest 据 ...

  5. 2014-2015 Petrozavodsk Winter Training Camp, Contest.58 (Makoto rng_58 Soejima contest)

    2014-2015 Petrozavodsk Winter Training Camp, Contest.58 (Makoto rng_58 Soejima contest) Problem A. M ...

  6. Petrozavodsk Winter Training Camp 2018

    Petrozavodsk Winter Training Camp 2018 Problem A. Mines 题目描述:有\(n\)个炸弹放在\(x\)轴上,第\(i\)个位置为\(p_i\),爆炸 ...

  7. 2020 CCPC-Wannafly Winter Camp Day2

    2020 CCPC-Wannafly Winter Camp Day2 A 托米的字符串 虽然每个子串出现的概率是相同的,但是同一长度的子串个数是不同的,所以要分别处理.计算出某一长度的情况下,元音字 ...

  8. CCPC、Petrozavodsk Camp、OpenCup 题解汇总

    省赛 \([\text{2021.11.30}]\) 2021 Jilin Collegiate Programming Contest 全部完成. \([\text{2021.12.25}]\) 2 ...

  9. 2020 ICPC Universidad Nacional de Colombia Programming Contest

    2020 ICPC Universidad Nacional de Colombia Programming Contest A. Approach 三分 显然答案可以三分,注意\(eps\)还有两条 ...

随机推荐

  1. TFrecord写入与读取

    Protocol buffers are Google's language-neutral, platform-neutral, extensible mechanism for serializi ...

  2. Deployment之滚动更新策略。

    1.Deployment控制器详细信息中包含了其更新策略的相关配置.kubectl describe命令中输出的StrategyType.RollingUpdateStrategy字段等: root@ ...

  3. 四边形不等式优化 dp (doing)

    目录 1. 四边形不等式与决策单调性 2. 决策单调性优化 dp - (i) 关于符号 1. 四边形不等式与决策单调性 定义(四边形不等式) 设 \(w(x,y)\) 是定义在整数集合上的二元函数,若 ...

  4. PHP切割汉字

    <?php /* @UTF-8编码的字符可能由1~3个字节组成. */ /*--------------------------方法一截取中文字符串方法--------------------- ...

  5. vue2与vue3实现响应式的原理区别和提升

    区别: vue2.x: 实现原理: 对象类型:Object.defineProperty()对属性的读取,修改进行拦截(数据劫持): 数组类型:通过重写更新数组的一系列方法来进行拦截(对数组的变更方法 ...

  6. 企业运维实践-Nginx使用geoip2模块并利用MaxMind的GeoIP2数据库实现处理不同国家或城市的访问最佳实践指南

    关注「WeiyiGeek」公众号 设为「特别关注」每天带你玩转网络安全运维.应用开发.物联网IOT学习! 希望各位看友[关注.点赞.评论.收藏.投币],助力每一个梦想. 本章目录 目录 0x00 前言 ...

  7. 使用three.js(webgl)搭建智慧楼宇、设备检测、数字孪生——第十三课

    老子云:有道无术,术尚可求,有术无道,止于术. 咱开篇引用老子的话术,也没其它意思,只是最近学习中忽有感悟,索性就写了上来. 这句话用现代辩证思维理解,这里的"道" 大抵是指方法论 ...

  8. 查看 npm 的全局安装依赖包

    在控制台中输入以下指令可以直接查看 npm 全局安装的依赖包: npm list -g --depth 0

  9. 定时器控制单只LED灯

    点击查看代码 #include <reg51.h> #define uchar unsigned char #define uint unsigned int sbit LED=P0^0; ...

  10. 【c语言简单算法】1-阶乘

    求n的阶乘 算法要求 从键盘输入一个数,求出这个数的阶乘 代码实现 #include main() { double result=1; size_t n; scanf("%d", ...