BFIALDGH
B
本题试图证明:对于任意一对 (不妨认为 ) 有:
先来看对于 能否 找到一个z使得
我们知道 因此 令 ,自然有
也就是说 问题转化为 对于任意的 证明
当 时, 无法构造合法解 此时 直接输出 "NO"
当 时:
- 的等号取得的条件是 or 都不符合题意
- 那我们只需要尝试证明 即可。
不妨对两个数字进行 按位异或 分以下四种情况:
- 此时异或的结果还是0 相比于j来说 大小不变
- 此时异或的结果是1 相比于j来说 大小不变
- 此时异或的结果是1 相比于j来说 变大
- 此时异或的结果是0 相比于j来说 变小
需要注意的是,高位对低位有绝对的压制作用。 也就是说 在从高到低的遍历过程中,如果发现已经满足 那就不用看了 ;而如果发现 那数字一定会变小 就是不合法的了。
所以问题转化为:如何快速判断 对于一个数字x的最高位 是否存在 使得y的这一位是1
如果出现了哪怕一个1 那么就一定能构造出不符合条件的解 返回"NO"
如果在遍历的过程中完全没有找到,那就说明完全合法 返回 "YES"
那我们只需要开一个61大小的辅助数组 用来记录当前位置1的个数
先对数字排个序 从大到小枚举
对每一个数字 都按位枚举 把自己的贡献记录进去
然后判断对于当前数字最高位 是否存在1
ac code:
void solve()
{
int n;
cin >> n;
vector<int> v(n);
for (int i = 0; i < n; i++)
{
cin >> v[i];
}
/*
i + j = i ^ j + 2 * (i & j)
什么时候 i ^ j > i and i ^ j > j
如果有两个相等的 那就不可能
如果没有 那么
不妨设 i < j 那么 要让战斗力不会降低 也就是 i^j 比 j 大 即可
如果要达成这个目的 就要让i的1刚好对应上j的0
否则都会比j小
对于一个j 我们想找所有比他小的i => 对于每一个i 我们找比他大的j是不是全部都是0
*/
vector<int> bits(62, 0);
sort(all(v), greater<int>());
for (auto num : v)
{
auto cur = num;
int maxnum = 0;
int idx = 0;
while (cur)
{
bits[idx] += cur & 1;
if (cur & 1)
{
maxnum = idx;
}
cur >>= 1;
idx++;
}
if (bits[maxnum] > 1)
{
return void(cout << "NO");
}
}
cout << "YES";
}F
首先破环成链,然后往右边找第一次出现的火和第 次出现的火
如果不铺设可控火势,那么对于任何一个 大小为 的空地 在t时间过去后,剩下的空地会变为
现在我们只能铺设一次可控火势,那么就要找出最大的空地 在其中一侧扑火 这样火势只能从另一侧蔓延了 t时间后剩下的空地为
最后累加答案并输出即可
ac code:
void solve()
{
int n, k;
cin >> n >> k;
string s;
cin >> s;
s += s;
int idx1 = -1, idx2 = -1;
for (int i = 0; i < n; i++)
{
if (s[i] == '1')
{
idx1 = i;
break;
}
}
for (int i = n; i < s.size(); i++)
{
if (s[i] == '1')
{
idx2 = i;
break;
}
}
// 找出里面最多的0 他可以被挡住一边 然后蔓延
/*
蔓延的代价是
假设一共有m个0 每秒消耗1 一共t秒 那就消耗了 m - 2t
特别的 最多的应该是 (m - 1) - t
*/
vector<int> vv;
int cur = 0;
for (int i = idx1; i <= idx2; i++)
{
if (s[i] == '0')
{
cur++;
}
else
{
vv.emplace_back(cur);
cur = 0;
}
}
int ans = 0;
sort(all(vv));
for (int i = 0; i + 1 < vv.size(); i++)
{
ans += max(0ll, vv[i] - 2 * k);
}
ans += max(0ll, vv.back() - 1 - k);
cout << ans;
}I
猜猜题,想要找到一个k 使得对于给定的 ,有 如果不存在就输出-1
对于任意一个数字 总存在 使得
因此 如果 那么等式就变为 这是显然成立的
特别的 时, 因此 or 时候需要输出-1
ac code:
void solve()
{
int a, b;
cin >> a >> b;
if (a == 1 or b == 1)
{
cout << -1;
}
else
{
cout << 1;
}
}A
第一时间想到了dp, 但是细节处理上需要仔细思考。
如果我们设 是 进行到第i天,以j结尾 一共有几段连续的1
那么自然有:
比方说 i位置最后是0 有k种构造方案,一共有m段连续的1
那么到了i+1位置 最后是1 所以每一个构造方案都会增加新的一段
那么i+1位置现在有多少段呢?就是原有的段数(m) + 新增加的段数(k) 因此 我们还需要额外维护新的量 也就是:
到了第i天 以j结尾 一共有多少种合法组成方案
有:
最后要输出段数取模 参考代码如下:
constexpr int MOD = 998244353;
void solve()
{
int n;
cin >> n;
vector<int> v(n + 1);
for (int i = 1; i <= n; i++)
{
cin >> v[i];
}
auto add = [&](int i, int j) -> int
{
return (i + j) % MOD;
};
/*
只有0 -> 1 会让段数增加额外的一段 我们要知道最后的段数之和
昨天是0 今天是1 段数如何增加?
昨天0结尾的总方案数 每一个方案都要增加1
那段数增加了多少呢 是0段+0方案
*/
struct datas
{
int fangan, duan;
};
vector dp(n + 1, vector<datas>(2, {0, 0}));
if (v[1] != 0)
{
dp[1][1].fangan = 1;
dp[1][1].duan = 1;
}
if (v[1] != 1)
{
dp[1][0].fangan = 1;
dp[1][0].duan = 0;
}
for (int i = 2; i <= n; i++)
{
auto pre = v[i - 1], cur = v[i];
// 如果当前是0 那就不可能新增段数和方案数
if (cur != 1)
{
dp[i][0].fangan = add(dp[i - 1][0].fangan, dp[i - 1][1].fangan);
dp[i][0].duan = add(dp[i - 1][0].duan, dp[i - 1][1].duan);
}
if (cur != 0)
{
// 有可能产生新的一段 取决于前一天有没有0方案
dp[i][1].fangan = add(dp[i - 1][1].fangan, dp[i - 1][0].fangan);
dp[i][1].duan = add(dp[i - 1][1].duan, add(dp[i - 1][0].duan, dp[i - 1][0].fangan));
}
}
cout << add(dp[n][0].duan, dp[n][1].duan);
}L
一个节点要么跟自己指向的节点组合,要么跟指向自己的节点组合;同时由题目得知,每个节点的出度入度均为1 因此 这个图一定会形成一片环的森林
接下来 我们要删除其中两个节点 问能构造多少种合法匹配 使得所有节点都被配对
我们可以先不删除点 而是看一下环的大小对这个匹配数量的影响
- 如果环是奇数,那么一定没有办法两两配对
- 如果环是偶数:
- 如果环大小为2 那么只能配成一对
- 如果环大小大于2 譬如 a → b → c → d → e → f → a 可以有
ab cd ef和fa bc de两种构造方案
也就是说 对于奇环 我们要尝试删去成偶链;对于大小为2的偶环 贡献是1;对于大小 的偶环 贡献是2
由于点的数目是偶数,因此奇环的数量一定是偶数;而我们只能删除两个点 意味着只有奇环数量为2时候能把他们全部变成偶链;
因此: 当奇环数量 >2 时,可以直接输出0
当奇环数量为2时候 这两个奇环都会变成偶链 偶链的贡献恒为1 那么一个奇环能创造多少贡献呢?应该是 删除点的方案 乘以 偶链的贡献
在本题中 这个结果就是 奇环上点的数量 乘以 1
对于剩下的偶环 小于4的乘以1 大于等于4的乘以2
记录这两条奇环的大小分别为 ,偶环中 大小 大于等于4的数量为 等于2的数量为
也就是
当奇环数量为0的时候 我们要从偶环中删除两个点 是否可以选择两个不同的偶环呢?如果这么做 会产生两条奇链,这是我们不愿看到的
因此 我们只能枚举每一条偶环 从中删除两个节点 这两个节点的删去 不能使得这个环变为两条奇链
手玩一下可以发现 对于 a → b → c → d → e → f → a 这条偶环 我们可以从ace 中删除一个,从bdf 中删除一个 记录这条环的大小为 那么总共删除的方案数为
这条环的匹配数量应该是 方案数 乘以 偶链贡献 而偶链的贡献(正如前文提到的那样)是1 因此 这个偶环的贡献就是
而剩下的环 贡献应当是
最后要枚举 答案为
用快速幂加速运算,注意取模
ac code:
constexpr int MOD = 998244353;
// -9.2e18 ~ 9.2e18
int add(int i, int j)
{
return (i + j) % MOD;
}
int mul(int i, int j)
{
return (i % MOD * j % MOD) % MOD;
}
int ksm(int base, int exp)
{
int ans = 1;
while (exp)
{
if (exp & 1)
{
ans = mul(ans, base);
}
base = mul(base, base);
exp >>= 1;
}
return ans;
}
void solve()
{
int n;
cin >> n;
vector<int> v(n + 1);
for (int i = 1; i <= n; i++)
{
cin >> v[i];
}
vector<int> vis(n + 1);
vector<int> odd, even2, even4;
for (int i = 1; i <= n; i++)
{
if (vis[i])
{
continue;
}
auto cur = i;
int tot = 0;
while (!vis[cur])
{
tot++;
vis[cur] = 1;
cur = v[cur];
}
if (tot & 1)
{
odd.emplace_back(tot);
}
else
{
if (tot == 2)
{
even2.emplace_back(tot);
}
else
{
even4.emplace_back(tot);
}
}
}
if (odd.size() > 2)
{
return void(cout << 0);
}
if (odd.size() == 2)
{
cout << mul(mul(odd[0], odd[1]), ksm(2, even4.size()));
return;
}
// 现在全都是偶数 要求和
int ans = 0;
// 如果把2删掉 那就删没了 所以对于每一个2 把他删掉的结果都是 ksm(2, even4.size())
// 所以这样的贡献一共有 even2.size() 个 要乘起来
ans = add(ans, mul(ksm(2, even4.size()), even2.size()));
// 如果把4删掉 对于每一个大于等于4的 删掉会导致 他的贡献是 ksm(num / 2, 2);
// 剩下even4.size() - 1 个 4 贡献是ksm(2, even4.size() - 1)
// 2的贡献都不用管的
for (auto num : even4)
{
ans = add(ans, mul(ksm(num / 2, 2), ksm(2, even4.size() - 1)));
}
cout << ans;
}G
H
n个点,m条有向边,使用第i条公路时,车辆耗时 分钟 从 前往 每一次升级可以让路段通行时间减少 分钟 可以进行多次升级
现在有q次询问,每次给出参数k,代表总共有k次升级次数,保证对于任意的 ,
求从1到n的最短用时
先学习单次询问: k是固定的 假如我们要对 三条边升级 那么一定不如 对 最大的一条边进行多次升级 因为 最大就可以尽可能的减少用时
问题转化为 选择一条边 让边权由 变为 求1到n的最短路
设 为 从i到j的最短路,那么枚举 i ,
这两个最短路可以通过正反dij来求,参考 dijk
对于多次询问 需要掌握凸包 这个再补吧…