CF1562 Codeforces Round 741
Codeforces Round #741 (Div. 2)
C - Rings
题意
给出一个二进制串 ,长度为 ,你可以在上面做 的截断,函数 表示:将 中 的截断取出,然后转换为十进制的数。
要求找出两对不同的 满足以下条件:
-
-
-
和 至少有一个满足。
-
存在一个非负整数 ,使得 。
题目保证有解,任意给出一组解即可。
数据范围:。
思路
不要把 想的很复杂,可以就考虑几个简单的,如 即可。
这个题就是构造出一种解就完事了,所以进行分类讨论。
- 如果 中所有字符都相等,则直接输出 即可,因为他们直接相等。
由于题目要求取出的串长度大于等于 ,所以考虑从 串的 位置作为截断,分左右进行讨论。
-
如果 的左半部分位置 处为 ,则输出 ,因为前导零去掉后,他们直接相等。
-
如果 的右半部分位置 处为 ,则输出 ,因为后置零,可以通过 得到,。
以上就已经讨论完了所有可能情况了,按情况输出即可。
时间复杂度 。
#include <bits/stdc++.h>
#define db double
#define ll long long
#define int ll
#define vi vector<int>
#define vii vector<vi >
#define pii pair<int, int>
#define vp vector<pii >
#define vip vector<vp >
#define mkp make_pair
#define pb push_back
#define Case(x) cout << "Case #" << x << ": "
using namespace std;
const int INF = 0x3f3f3f3f;
const int P = 998244353;
signed main(){
#ifdef _DEBUG
// FILE *file = freopen("out", "w", stdout);
#endif
ios::sync_with_stdio(0);
cin.tie(0);
int T;
cin >> T;
while (T--) {
int n;
string s;
cin >> n >> s;
bool fg = 0;
for (int i = 1; i <= n; i++) {
if (s[i-1] == '0') {
if (i <= n/2) {
cout << i << ' ' << n << ' ' << i + 1 << ' ' << n << '\n';
} else {
cout << 1 << ' ' << i << ' ' << 1 << ' ' << i - 1 << '\n';
}
fg = 1;
break;
}
}
if (!fg) cout << 1 << ' ' << n-1 << ' ' << 2 << ' ' << n << '\n';
}
return 0;
}
D - Two Hundred Twenty One
题意
给出一个长度为 的字符串 和 次询问,每次询问一个区间 。
对于数列 ,定义“不同符号和”(sign-variable):
现在数列 只有 和 构成,通过只含有正负号的字符串 给出
如果 ,
每次询问给出区间 ,设字符串 ,求至少要从 中删除多少个字符才能使得 ,并给出每次删除的字符位置。(第一个问题是D1所要求的,第二个问题是D2所要求的)。
数据范围:。
思路
我们先不考虑 的子串问题,先考虑整个 串。
设 ,表示 中 这一段的不同符号和。
设 为去掉 串中第 个字符后变为 ,整个 的不同符号和,即 。
命题1:
证明:
不难发现:
则:
-
当 时,。
-
当 时,。
QED
下面这个结论不难证明:
命题2: 与 同奇偶性,也就是字符串长度和字符串的不同符号和的奇偶性相同。
因为 和 之和会发生抵消,抵消以后字符总数 不影响奇偶性,所以最终的不同符号和奇偶性与开始时的字符总长奇偶性相同。
所以,如果 则一定有 为偶数。
命题3:当 时,。
证明:
命题等价于证明 和 异号,或者至少有一个为 。
由于 ,所以 和 的符号只能取决于 的符号, 对它们的影响太小,而 在 中的符号正好是一正一负,所以 只能异号,或者至少一个为 。
QED
下面给出删除方法:
设 为奇数,则 为偶数,因为它从 中删除了一个字符。
又通过命题1知,相邻的 之间差值为 或 ,由命题3知, 异号,则一定存在 ,使得 ,可以通过反证法证明。(类似于连续函数的零点存在定理)
则说明,对于长度为奇数的串,我们一定可以找到 ,即将 从 删除即可满足题意。
如果长度为偶数的串,可以先删除 使得它的长度变为奇数且 不会发生变化,然后再按照奇数的删除方法删除。
综上,最大的删除字符个数不会超过2个。
拓展到任意 的子串 方法很简单,只需要求出 即可,所以可以通过维护函数 的前缀和完成。
于是对于长度为 的字符串 :
-
输出 。
-
为奇数,输出 。
-
为偶数,输出 。
时间复杂度 。
下面给出 D1(easy version) 的代码:
#include <bits/stdc++.h>
#define db double
#define ll long long
#define int ll
#define vi vector<int>
#define vii vector<vi >
#define pii pair<int, int>
#define vp vector<pii >
#define vip vector<vp >
#define mkp make_pair
#define pb push_back
#define Case(x) cout << "Case #" << x << ": "
using namespace std;
const int INF = 0x3f3f3f3f;
const int P = 998244353;
signed main(){
#ifdef _DEBUG
// FILE *file = freopen("out", "w", stdout);
#endif
ios::sync_with_stdio(0);
cin.tie(0);
int T;
cin >> T;
while (T--) {
int n, m;
string s;
cin >> n >> m >> s;
vi sum(n+1);
for (int i = 1; i <= n; i++) {
sum[i] = sum[i-1] + (s[i-1] == '+' ? 1 : -1) * (i % 2 == 1 ? 1 : -1);
}
while (m--) {
int l, r;
cin >> l >> r;
if (sum[r] - sum[l-1] == 0) cout << 0 << '\n';
else if ((r - l + 1) % 2 == 1) cout << 1 << '\n';
else cout << 2 << '\n';
}
}
return 0;
}
对于 D2 (hard version) 只需要求出每次奇数长度时删除的位置 即可。
我们知道计算函数零点,可以通过二分完成,所以直接二分零点即可。
时间复杂度 。
#include <bits/stdc++.h>
#define db double
#define ll long long
#define int ll
#define vi vector<int>
#define vii vector<vi >
#define pii pair<int, int>
#define vp vector<pii >
#define vip vector<vp >
#define mkp make_pair
#define pb push_back
#define Case(x) cout << "Case #" << x << ": "
using namespace std;
const int INF = 0x3f3f3f3f;
const int P = 998244353;
signed main(){
#ifdef _DEBUG
// FILE *file = freopen("out", "w", stdout);
#endif
ios::sync_with_stdio(0);
cin.tie(0);
int T;
cin >> T;
while (T--) {
int n, m;
string s;
cin >> n >> m >> s;
vi sum(n+1);
for (int i = 1; i <= n; i++) {
sum[i] = sum[i-1] + (s[i-1] == '+' ? 1 : -1) * (i % 2 == 1 ? 1 : -1);
}
while (m--) {
int l, r;
cin >> l >> r;
if (sum[r] - sum[l-1] == 0) cout << 0 << '\n';
else {
if ((r - l + 1) % 2 == 0) {
cout << 2 << '\n' << r << ' ';
r--;
}
else cout << 1 << '\n';
int L = l, R = r;
while (l <= r) {
int mid = (l + r) >> 1, x = mid;
if ((sum[x-1] - sum[L-1] - sum[R] + sum[x]) * (-sum[R] +sum[L]) <= 0) r = mid - 1;
else l = mid + 1;
}
cout << l << '\n';
}
}
}
return 0;
}
E - Rescue Niwen!
题意
给出一个长度为 的字符串 ,以 的子串:,构成的字符串序列中,最长单调递增子序列长度为多少?(字符串排序方法按照字典序排序,单调在这里是指严格单调)
数据范围:。
思路
最开始想先预处理出来所有子串的字典序大小,用字典序大小代替原来的字符串序列,然后直接通过 lower_bound
求最长上升子序列,这样复杂度是 理论上过不了,最终优化到了 3s,实在写不进 2s 了,最后放弃了这种写法,后来看题解发现这道题有很好的性质。
如果字符串 在最长上升子序列中,则 也一定在其中。
所以如果 考虑从前一个转移,就只用考虑从 进行转移,保证 。
这里可以通过求每个后缀 的 lcp(最长公共前缀)完成,设它们的lcp长度为 。
设 表示左端点从 的最长上升子序列长度,用贪心的思路进行转移。
则 ,且保证 。
至于两个后缀的lcp可以直接暴力dp得到,这里复杂度也只是 。
时间复杂度 。
正确代码:
#include <bits/stdc++.h>
#define db double
#define ll long long
#define int ll
#define vi vector<int>
#define vii vector<vi >
#define pii pair<int, int>
#define vp vector<pii >
#define vip vector<vp >
#define mkp make_pair
#define pb push_back
#define Case(x) cout << "Case #" << x << ": "
using namespace std;
const int INF = 0x3f3f3f3f;
const int P = 998244353;
const int N = 5000 + 10;
signed main(){
#ifdef _DEBUG
// FILE *file = freopen("out", "w", stdout);
#endif
ios::sync_with_stdio(0);
cin.tie(0);
int T;
cin >> T;
while (T--) {
int n;
string s;
cin >> n >> s;
vii lcp(n+1, vi(n+1));
vi dp(n+1);
for (int i = n-1; i >= 0; i--) {
for (int j = n-1; j >= 0; j--) {
if (i == j) lcp[i][j] = n-i;
else if (s[i] != s[j]) lcp[i][j] = 0;
else lcp[i][j] = lcp[i+1][j+1] + 1;
}
}
int ans = 0;
for (int i = 0; i < n; i++) {
dp[i] = n-i;
for (int j = 0; j < i; j++) {
if (lcp[i][j] < n-i && s[i+lcp[i][j]] > s[j+lcp[i][j]]) {
dp[i] = max(dp[i], dp[j] + n - i - lcp[i][j]);
}
}
ans = max(ans, dp[i]);
}
cout << ans << '\n';
}
return 0;
}
尝试后的错误代码,使用SAM求字典序,然后lower_bound
求解最长上升子序列:
#include <bits/stdc++.h>
#define db double
#define ll long long
#define vi vector<int>
#define vii vector<vi >
#define pii pair<int, int>
#define vp vector<pii >
#define vip vector<vp >
#define mkp make_pair
#define pb push_back
#define Case(x) cout << "Case #" << x << ": "
#define ls (p<<1)
#define rs (p<<1|1)
using namespace std;
const int INF = 0x3f3f3f3f;
const int P = 998244353;
const int N = 5000 + 10;
struct SAM {
int tot, last, cnt, mx[N*2], prt[N*2];
vp rk[N*2];
map<int, int> ch[N*2];
SAM() {tot = last = cnt = 1;}
void clear() {
for (int i = 1; i <= cnt; i++) {
mx[i] = prt[i] = 0;
ch[i].clear();
rk[i].clear();
}
last = cnt = tot = 1;
}
void add(int c) {
int p = last, np = ++cnt;
last = np;
mx[np] = mx[p] + 1;
for (; p && !ch[p][c]; p = prt[p]) ch[p][c] = np;
if (!p) prt[np] = 1;
else {
int q = ch[p][c];
if (mx[q] == mx[p] + 1) prt[np] = q;
else {
int nq = ++cnt;
mx[nq] = mx[p] + 1;
prt[nq] = prt[q];
prt[q] = prt[np] = nq;
ch[nq] = ch[q];
//memcpy(ch[nq], ch[q], sizeof(ch[q]));
for (; ch[p][c] == q; p = prt[p]) ch[p][c] = nq;
}
}
}
void dfs(int u, int len) {
rk[u].pb({len, tot++});
for (auto i : ch[u]) {
dfs(i.second, len+1);
}
}
void solve() {
clear();
int n;
string s;
cin >> n >> s;
for (auto i : s) add(i - 'a');
dfs(1, 0);
vi dp;
for (int i = 1; i <= cnt; i++) sort(rk[i].begin(), rk[i].end());
for (int i = 0; i < n; i++) {
int p = 1;
for (int j = 1; j + i - 1 < n; j++) {
p = ch[p][s[i+j-1] - 'a'];
int r = rk[p][j - mx[prt[p]] - 1].second;
auto it = lower_bound(dp.begin(), dp.end(), r);
if (it == dp.end()) dp.pb(r);
else *it = r;
}
}
cout << dp.size() << '\n';
}
}sam;
signed main(){
#ifdef _DEBUG
FILE *file = freopen("E.in", "r", stdin);
#endif
ios::sync_with_stdio(0);
cin.tie(0);
int T;
cin >> T;
while (T--) {
sam.solve();
}
return 0;
}