前记:最近vp好几场cf感觉手感越来越差,div2的D题是怎么也开不了,人麻了已经。
Codeforces Round 879 (Div. 2)
D. Survey in Class
题意:有 个同学,
个主题,第
个人准备了主题编号区间是
,你可以选择问一些主题,对于你问的这些主题,若学生
准备了,则他的手往上举
,否则往下放
(可以小于
),问你选择哪些问题,可以使得举得最高的人和举的最低的人差值最大。
题解:
不难发现对于两个不同的同学 ,他们之间的最大差值就是他们不相交的区间长度的最大值乘
,例:对于区间
和
来说,它们不相交的区间分别是
和
,因此最大区间长度是
,那么它们之间的最大差值是
。
于是我们的思路是依次遍历所有区间,求可以和当前区间 相交使得区间
不相交的区间长度最大的区间长度值。
继续我们发现对于两个区间 有四种关系:
- 区间
从左端点与区间
有交集
- 区间
从右端点与区间
有交集
- 区间
完全包含区间
- 区间
和区间
不相交
对于第一种情况,我们可以贪心地发现,区间 如果和具有区间右端点最小的区间相交,对于区间
一定可以得到不相交的区间长度最大。
对于第二种情况,我们可以贪心地发现,区间 如果和具有区间左端点最大的区间相交,对于区间
一定可以得到不相交的区间长度最大。
对于第三种情况,我们可以认定长度最小的那条区间被区间 完全包含,假设这区间真的被区间
包含,那答案一定是这样。如果这区间并没有被区间
包含,那这条区间与区间
的交集一定不大于任何被区间
包含的区间与区间
的交集。(因为这条线段的长度本身就最小了),这种情况说明区间
最优匹配的区间
一定不包含在区间
内部,所以我们不需要真的判断长度最小的区间是否被区间
包含,只要假设它被区间
包含即可,即使不包含,对答案也没有影响(因为答案是在 情况一二四中出现)。
对于第四种情况,在情况一二中已经算出来了。
具体代码如下:
#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <cmath>
#include <set>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#define int long long
#define pi acos(-1)
#define quick_cin() cin.tie(0),ios::sync_with_stdio(false)
#define endl "\n"
#define pb push_back
#define mkp make_pair
#define all(x) x.begin(), x.end()
#define ul (u << 1)
#define ur (u << 1 | 1)
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
const int N = 500010, M = 110, INF = 1e9;
const int mod = 1e9 + 7;
int T = 1;
int res = 0;
int n, m, k;
struct Line
{
int l, r;
int len;
}s[N];
void solve()
{
cin >> n >> m;
int minr = INF, maxl = 0;
int minlen = INF;
for (int i = 1; i <= n; i ++ )
{
int l, r;
cin >> l >> r;
int len = r - l + 1;
minr = min(minr, r), maxl = max(maxl, l);
minlen = min(minlen, len);
s[i] = {l, r, len};
}
res = 0;
for (int i = 1; i <= n; i ++ )
{
res = max(res, min(s[i].len, s[i].r - minr));
res = max(res, min(s[i].len, maxl - s[i].l));
res = max(res, min(s[i].len, s[i].len - minlen));
}
cout << res * 2 << endl;
}
signed main()
{
quick_cin();
cin >> T;
while (T -- )
{
solve();
}
}
E. MEX of LCM
题意:给定一个长度为 的序列
,求它的所有连续子序列不可能构造出的最小的最小公倍数
。
题解:
考虑已知一个区间的 ,此时将区间往两边扩大
,
是不降的。
于是如果我们想要知道所有区间的 中最小的未出现过数,因此我们不需要一开始就知道所有的
,考虑按从小到大构造出
,又知道区间
在区间变大的过程中是一直增大的。
想到使用小根堆,最开始将每个位置上的数存进去表示以这个位置作为区间 的起点,用
表示当前考虑
是否是答案,每次取出最小的数出来看是否等于
,如果等于
那么
就不能作为答案,
需要加
,然后将堆顶的元素取出来让它的区间往右走一步再重新插进堆中,这样就能当堆顶元素不等于
时就找到了答案。
但此时若遇到 这种特殊情况时间复杂度会到
,于是考虑优化。
考虑什么样的情况是不需要重复计算的,当有若干个区间右端点相同,左端点不同的区间的 相同时,只需要保留一个区间即可,例如:
的
等于
的
时,
便不需要继续往右走了,因为答案会完全和
相同。
因此考虑记忆每个位置曾经出现过哪些值,当某个区间的右端点扩大后发现这个位置曾经有过区间的 和自己相同,那么就不用将这个区间加入堆中了,用
记忆这些值即可。
具体代码如下:
#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <cmath>
#include <set>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#define int long long
#define pi acos(-1)
#define quick_cin() cin.tie(0),ios::sync_with_stdio(false)
#define endl "\n"
#define pb push_back
#define mkp make_pair
#define all(x) x.begin(), x.end()
#define ul (u << 1)
#define ur (u << 1 | 1)
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
const int N = 500010, M = 110, INF = 1e9;
const int mod = 1e9 + 7;
int T = 1;
int res = 0;
int n, m, k;
int a[N];
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
int lcm(int a, int b)
{
return a * b / gcd(a, b);
}
set<int> S[N];
priority_queue<PII, vector<PII>, greater<PII>> heap;
void solve()
{
res = 1;
cin >> n;
for (int i = 1; i <= n; i ++ )
{
cin >> a[i];
S[i].clear();
S[i].insert(a[i]);
heap.push({a[i], i});
}
while (heap.size() && heap.top().first == res)
{
while (heap.size() && heap.top().first == res)
{
PII t = heap.top();
heap.pop();
int x = t.first, idx = t.second;
if ( ++ idx <= n)
{
x = lcm(x, a[idx]);
if (!S[idx].count(x))
{
heap.push({x, idx});
S[idx].insert(x);
}
}
}
res ++ ;
}
while (!heap.empty()) heap.pop();
cout << res << endl;
}
signed main()
{
quick_cin();
cin >> T;
while (T -- )
{
solve();
}
}
F. Typewriter
题意:有一个只有一个缓冲区的打字机,即每次最多只能搬运一个数字。
打字机有以下五种操作:
- 如果当前单元格中的整数不为空,则从该单元格中提取该整数,如果为空,请将其放入回车缓冲区(该缓冲区最多可包含一个整数)。
- 如果整数不为空,则将其从回车缓冲区放入当前单元格(如果为空)。
- 如果缓冲区和单元格都包含整数,则将回车缓冲区中的数字与当前单元格中的数字交换。
- 将托架从当前单元格中移出
到单元格
(如果
), 而缓冲器中的整数被保留。
- 重置支架,即将其移动到单元格编号
,同时保留缓冲区中的整数。
现有三种修改:
- 当
,表示将序列向左移动
位。
- 当
,表示将序列向右移动
位。
- 当
,表示将序列整体翻转。
求让序列变成升序序列,操作5需要的最小操作次数。
题解:
不难发现操作数量等于 的点的数量。
我们发现无论如何修改。可能的结果只有 种,我们可以把这些结果都预处理出来,然后维护操作后到达了哪个状态,
查询即可。
具体代码如下:
#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <cmath>
#include <set>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#define int long long
#define pi acos(-1)
#define quick_cin() cin.tie(0),ios::sync_with_stdio(false)
#define endl "\n"
#define pb push_back
#define mkp make_pair
#define all(x) x.begin(), x.end()
#define ul (u << 1)
#define ur (u << 1 | 1)
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
const int N = 500010, M = 110, INF = 1e9;
const int mod = 1e9 + 7;
int T = 1;
int res = 0;
int n, m, k;
int a[N];
int ans[2][N];
vector<int> p;
void cal(int type)
{
vector<int> c(n);//表示向右走i步后总体贡献会加c[i-1]
int sum = 0;
for (int i = 0; i < n; i ++ )
{
sum += (i > p[i]);
c[(p[i] - i + n) % n] += 1;
}
ans[type][0] = sum;
for (int i = 1; i < n; i ++ )
{
sum += c[i - 1] - 1;
ans[type][i] = sum;
}
}
void solve()
{
cin >> n;
for (int i = 1; i <= n; i ++ )
{
int x;
cin >> x;
p.pb(x - 1);
}
cal(0);
reverse(all(p));
cal(1);
int cnt = 0, type = 0;
cout << ans[type][cnt] << endl;
cin >> m;
while (m -- )
{
int op;
cin >> op;
if (op == 1)
{
int x;
cin >> x;
cnt = (cnt - x + n) % n;
}
else if (op == 2)
{
int x;
cin >> x;
cnt = (cnt + x) % n;
}
else
{
type ^= 1;
cnt = (n - cnt) % n;
}
cout << ans[type][cnt] << endl;
}
}
signed main()
{
quick_cin();
// cin >> T;
while (T -- )
{
solve();
}
}
Codeforces Round 875 (Div. 2)
D. The BOSS Can Count Pairs
题意:给定两个序列 ,问满足
的数量,其中
。
题解:遍历所有的 的可能值,遍历所有的
,得到理想值
,加上产生的个数。
具体代码如下:
#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <cmath>
#include <set>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#define int long long
#define pi acos(-1)
#define quick_cin() cin.tie(0),ios::sync_with_stdio(false)
#define endl "\n"
#define pb push_back
#define mkp make_pair
#define all(x) x.begin(), x.end()
#define ul (u << 1)
#define ur (u << 1 | 1)
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
const int N = 200010, M = 110, INF = 1e18;
const int mod = 1e9 + 7;
int T = 1;
int res = 0;
int ans = 0;
int n, m, k;
int idx;
int a[N], b[N];
PII c[N];
void solve()
{
res = 0;
cin >> n;
for (int i = 0; i < n; i ++ ) cin >> a[i];
for (int i = 0; i < n; i ++ ) cin >> b[i];
for (int i = 0; i < n; i ++ ) c[i] = {a[i], b[i]};
sort(c, c + n);
for (int val = 1; val * val <= 2 * n; val ++ )
{
vector<int> cnt(n + 1, 0);
for (int i = 0; i < n; i ++ )
{
int A = c[i].first, B = c[i].second;
if (A < val) continue;
int t = val * A - B;
if (t >= 1 && t <= n) res += cnt[t];
if (A == val) cnt[B] ++ ;
}
}
cout << res << endl;
}
signed main()
{
quick_cin();
cin >> T;
while (T -- )
{
solve();
}
}
E. Hyperregular Bracket Strings
题意:给出 和
,有
组区间
,要求满足每个区间都是匹配的括号序列,且自己整体也是一个匹配的括号序列,长度为
,求满足条件的括号序列的数量,答案对
取模。
题解:匹配的括号序列的数量即卡特兰数,即对于 个括号合法数量有
个。
然后我们来分析两种不同区间类型:
1、对于区间 ,其中
。
不难发现,我们可以把它分成两个整体: 和
。
2、对于区间 ,其中
。
不难发现,我们可以把它分成三个整体: 、
和
。
每个整体之间互相独立,于是我们可以通过用异或一个随机数来标记区间,然后利用异或前缀和来记录每种整体的总长度,并算出它的贡献。
注意的是本题需要的随机数范围较大,不能使用 函数,需使用
随机数,使用函数定义如下:
mt19937_64 rnd(chrono::steady_clock::now().time_since_epoch().count());
具体代码如下:
#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <cmath>
#include <set>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <random>
#include <chrono>
#define int long long
#define pi acos(-1)
#define quick_cin() cin.tie(0),ios::sync_with_stdio(false)
#define endl "\n"
#define pb push_back
#define mkp make_pair
#define all(x) x.begin(), x.end()
#define ul (u << 1)
#define ur (u << 1 | 1)
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
const int N = 500010, M = 110, INF = 1e18;
const int mod = 998244353;
int T = 1;
int res = 0;
int ans = 0;
int n, m, k;
int idx;
int a[N];
int inv[N];
int fact[N], infact[N];
int f[N];
mt19937_64 rnd(chrono::steady_clock::now().time_since_epoch().count());
int qmi(int a, int b, int p)
{
int res = 1;
while (b)
{
if (b & 1) res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res;
}
void init(int n)
{
fact[0] = infact[0] = inv[0] = inv[1] = 1;
for (int i = 2; i <= n; i ++ )
inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
for (int i = 1; i <= n; i ++ )
{
fact[i] = 1ll * fact[i - 1] * i % mod;
infact[i] = 1ll * infact[i - 1] * inv[i] % mod;
}
}
int C(int n, int m)
{
if (n < m) return 0;
if (m == 0 || n == m) return 1;
return 1ll * fact[n] * infact[m] % mod * infact[n - m] % mod;
}
void add(int l, int r)
{
int x = rnd();
a[l] ^= x, a[r + 1] ^= x;
}
void solve()
{
res = 1;
map<int, int> cnt;
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) a[i] = 0;
while (m -- )
{
int l, r;
cin >> l >> r;
add(l, r);
}
for (int i = 1; i <= n; i ++ )
{
a[i] ^= a[i - 1];
cnt[a[i]] ++ ;
}
f[0] = 1;
for (int i = 1; i <= n; i ++ )
if (i % 2 == 0)
f[i] = C(i, i / 2) * qmi(i / 2 + 1, mod - 2, mod) % mod;
for (auto i : cnt)
res = res * f[i.second] % mod;
cout << res << endl;
}
signed main()
{
init(N - 2);
quick_cin();
cin >> T;
while (T -- )
{
solve();
}
}