Codeforces Round 1083 (Div. 2) 解题报告
Dashboard - Codeforces Round 1083 (Div. 2) - Codeforces
将第一个值与最大值交换,此时只有之后一个丑位置。
点击展开代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define i128 __int128
#define vi vector<int>
#define pii pair<int, int>
#define pll pair<ll, ll>
#define siz(a) ((int)((a).size()))
#define endl '\n'
// #define fi first
// #define se second
// #define double long double
// #define int ll
void solve()
{
int n;
cin >> n;
vector<int> a(n);
for(int i = 0; i < n; ++i) cin >> a[i];
int pos = 0;
for(int i = 1; i < n; ++i) if(a[i] == n) pos = i;
swap(a[0], a[pos]);
for(auto x : a) cout << x << ' ';
cout << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while(T--) solve();
return 0;
}
由于 \(p^p\) 总可以被 \(p\) 整除。因此只要保证 \(k\) 包含 \(n\) 的所有质因子即可,即 \(k\) 是 \(n\) 的所有质因子相乘。
点击展开代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define i128 __int128
#define vi vector<int>
#define pii pair<int, int>
#define pll pair<ll, ll>
#define siz(a) ((int)((a).size()))
#define endl '\n'
// #define fi first
// #define se second
// #define double long double
// #define int ll
void solve()
{
int n;
cin >> n;
ll ans = 1;
for(int i = 2; i * i <= n; ++i)
{
if(n % i == 0)
{
ans *= i;
while(n % i == 0) n /= i;
}
}
if(n > 1) ans *= n;
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while(T--) solve();
return 0;
}
倒序考虑操作,相当于每次选一个序列放在已有序列的末尾,要求字典序最小。
由于一个元素只有第一次出现的位置有用,因此直接将剩余序列排序后,选最小的序列拼接上即可。再模拟删除剩余未选的序列中,已经被选上的元素。
点击展开代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define i128 __int128
#define vi vector<int>
#define pii pair<int, int>
#define pll pair<ll, ll>
#define siz(a) ((int)((a).size()))
#define endl '\n'
// #define fi first
// #define se second
// #define double long double
// #define int ll
void solve()
{
int n;
cin >> n;
vector<vector<int>> a(n + 1);
vector<int> b;
for(int i = 1; i <= n; ++i)
{
int siz, x;
cin >> siz;
a[i].resize(siz);
for(int j = 0; j < siz; ++j)
{
cin >> x;
a[i][j] = x;
b.emplace_back(x);
}
}
sort(b.begin(), b.end());
b.resize(unique(b.begin(), b.end()) - b.begin());
for(int i = 1; i <= n; ++i)
{
for(auto &x : a[i]) x = lower_bound(b.begin(), b.end(), x) - b.begin();
reverse(a[i].begin(), a[i].end());
}
vector<int> v1(n + 1, 0), v2(siz(b) + 1, 0);
vector<int> ans;
for(int i = 1; i <= n; ++i)
{
vi temp;
for(auto x : a[i])
{
if(!v2[x]) temp.emplace_back(x), v2[x] = 1;
}
a[i] = temp;
for(auto x : a[i]) v2[x] = 0;
}
for(int i = 1; i <= n; ++i)
{
vector<int> id;
for(int j = 1; j <= n; ++j)
{
if(v1[j]) continue;
id.emplace_back(j);
}
sort(id.begin(), id.end(), [&](int x, int y){ return a[x] < a[y]; });
for(auto x : a[id[0]]) if(!v2[x]) ans.emplace_back(x), v2[x] = 1;
v1[id[0]] = 1;
// printf("id[0] = %d\n", id[0]);
// for(auto x : a[id[0]]) printf("%d ", x);
// printf("\n");
for(int j = 1; j < siz(id); ++j)
{
vector<int> temp;
for(auto x : a[id[j]]) if(!v2[x]) temp.emplace_back(x);
a[id[j]] = temp;
}
}
// reverse(ans.begin(), ans.end());
for(auto x : ans) cout << b[x] << ' ';
cout << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while(T--) solve();
return 0;
}
注意到一开始会选择最大值,然后清空它的左侧或右侧,对于另一侧,最大值已经没有限制,于是变成一个相同的子问题。
于是求出序列的笛卡尔树,问题转化成在笛卡尔树上DP,设 \(dp_i\) 表示处理完 \(i\) 子树的最小操作数,转移有
点击展开代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define i128 __int128
#define vi vector<int>
#define pii pair<int, int>
#define pll pair<ll, ll>
#define siz(a) ((int)((a).size()))
#define endl '\n'
// #define fi first
// #define se second
// #define double long double
// #define int ll
const int N = 5e5 + 5;
int n, a[N];
int son[N][2];
int sta[N], top;
#define ls(x) son[x][0]
#define rs(x) son[x][1]
int dp[N], Siz[N];
void dfs(int k)
{
Siz[k] = 1;
if(ls(k)) dfs(ls(k)), Siz[k] += Siz[ls(k)];
if(rs(k)) dfs(rs(k)), Siz[k] += Siz[rs(k)];
if(!ls(k) || !rs(k)){ dp[k] = dp[ls(k)] + dp[rs(k)]; return ; }
dp[k] = min(Siz[ls(k)] + dp[rs(k)], Siz[rs(k)] + dp[ls(k)]);
}
void solve()
{
cin >> n;
for(int i = 1; i <= n; ++i) cin >> a[i], ls(i) = rs(i) = 0;
top = 0;
for(int i = 1; i <= n; ++i)
{
while(top && a[sta[top]] < a[i]) ls(i) = sta[top], --top;
rs(sta[top]) = i;
sta[++top] = i;
}
while(top) rs(sta[top - 1]) = sta[top], --top;
dfs(rs(0));
cout << dp[rs(0)] << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while(T--) solve();
return 0;
}
问题在于同一个 \(S\) 可能有很多种翻转方式到达 \(T\)。
注意到一个子串若含有border,设为 \(s=twt\),那么翻转 \(s\) 等价于分别翻转 \(t,w,t\)。而这是最小分割的方式,以此来做DP。
设 \(dp_{i}\) 表示考虑前 \(i\) 个字符时,有多少字符串 \(S\) 满足可以翻转得到 \(T\),转移时枚举上一个断点 \(j\),满足 \(T[i...j]\) 没有border。
可以对于 \(T\) 的每一个后缀做一遍KMP,用bool数组存储下是否有border。
点击展开代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define i128 __int128
#define vi vector<int>
#define pii pair<int, int>
#define pll pair<ll, ll>
#define siz(a) ((int)((a).size()))
#define endl '\n'
// #define fi first
// #define se second
// #define double long double
// #define int ll
const int N = 8005;
int n, a[N], b[N];
int nxt[N];
bool f[N][N];
const ll mod = 998244353;
int dp[N];
void add(int &x, int y){ x = (x + y >= mod) ? (x + y - mod) : (x + y); }
void del(int &x, int y){ x = (x - y < 0) ? (x - y + mod) : (x - y); }
void solve()
{
cin >> n;
for(int i = 1; i <= n; ++i) cin >> a[i];
for(int i = 1; i <= n; ++i)
{
for(int j = i; j <= n; ++j) f[i][j] = 0;
int len = n - i + 1;
for(int j = 1; j <= len; ++j) b[j] = a[i + j - 1];
nxt[1] = 0;
for(int j = 2, k = 0; j <= len; ++j)
{
while(k && b[k + 1] != b[j]) k = nxt[k];
if(b[k + 1] == b[j]) ++k;
nxt[j] = k;
}
for(int j = 1; j <= len; ++j) f[i][j + i - 1] = (nxt[j] == 0);
}
dp[0] = 1;
for(int i = 1; i <= n; ++i)
{
dp[i] = 0;
for(int j = 1; j <= i; ++j)
if(f[j][i]) add(dp[i], dp[j - 1]);
}
cout << dp[n] << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while(T--) solve();
return 0;
}
先不考虑不可重建的边。由于每个点度数都为偶数,那么最终有一条欧拉回路。
考虑只有一条简单欧拉回路,贡献是左边+上边-右边-下边,如果每个格子的价值是它的左边+上边-右边-下边,那么整个欧拉回路的价值恰好是被包围在内的格子的价值之和。
再考虑有多条欧拉回路,发现一个格子被奇数个欧拉回路包含时才有贡献,再进一步发现,每个格子是否选择时相互独立的。
此时考虑不可重建的边,若该边位于边界,那么边界那个格子不能选择;若位于两个格子中间,那么这两个格子必须一起选或者一起不选,使用并查集维护一下。
点击展开代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define i128 __int128
#define vi vector<int>
#define pii pair<int, int>
#define pll pair<ll, ll>
#define siz(a) ((int)((a).size()))
#define endl '\n'
// #define fi first
// #define se second
// #define double long double
// #define int ll
const int N = 2e5 + 5;
int n, m;
vector<int> id[N];
vector<int> h[N], l[N];
int f[N * 2];
ll val[N * 2];
const ll inf = 0x3f3f3f3f3f3f3f3f;
int find(int x){ return (x == f[x]) ? x : (f[x] = find(f[x])); }
void merge(int x, int y)
{
x = find(x), y = find(y);
if(x == y) return ;
f[y] = x, val[x] = max(val[x] + val[y], -inf);
}
void solve()
{
cin >> n >> m;
for(int i = 1; i <= n; ++i)
{
id[i].clear();
h[i].clear();
id[i].resize(m + 1);
h[i].resize(m + 1);
}
for(int i = 1; i <= n; ++i)
{
l[i].clear();
l[i].resize(m + 1);
}
int tot = 0;
for(int i = 1; i < n; ++i)
for(int j = 1; j < m; ++j)
{
id[i][j] = ++tot;
f[tot] = tot;
val[tot] = 0;
}
for(int i = 1; i < n; ++i)
{
for(int j = 1; j <= m; ++j)
{
cin >> h[i][j];
}
}
for(int i = 1; i <= n; ++i)
for(int j = 1; j < m; ++j)
cin >> l[i][j];
for(int i = 1; i < n; ++i)
for(int j = 1; j < m; ++j)
{
val[id[i][j]] = 1ll * h[i][j] - h[i][j + 1] + l[i][j] - l[i + 1][j];
// printf("i = %d, j = %d, %d %d %d %d %lld\n", i, j, h[i][j], h[i][j + 1], l[i][j], l[i + 1][j], val[id[i][j]]);
}
for(int i = 1; i < n; ++i)
{
string s;
cin >> s;
for(int j = 1; j <= m; ++j)
{
int p;
p = s[j - 1] - '0';
if(!p)
{
if(j == 1 || j == m) val[find(id[i][j - (j == m)])] = -inf;
else merge(id[i][j], id[i][j - 1]);
}
}
}
for(int i = 1; i <= n; ++i)
{
string s;
cin >> s;
for(int j = 1; j < m; ++j)
{
int p;
p = s[j - 1] - '0';
if(!p)
{
if(i == 1 || i == n) val[find(id[i - (i == n)][j])] = -inf;
else merge(id[i][j], id[i - 1][j]);
}
}
}
ll ans = 0;
for(int i = 1; i < n; ++i)
for(int j = 1; j < m; ++j)
if(find(id[i][j]) == id[i][j] && val[id[i][j]] > 0)
{
// printf("id[%d][%d] = %d, val = %lld\n", i, j, id[i][j], val[id[i][j]]);
ans += val[id[i][j]];
}
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while(T--) solve();
return 0;
}
首先 \(x,y\) 没什么用,转化为 \(\gcd(i\oplus j,j\oplus k)|n\)。
先不考虑 \(i=j\) 或者 \(j=k\),设 \(p=i\oplus j,q=j\oplus k\)。则 \(p,q\) 都是正整数,设 \(c(p,q)\) 表示 \(i\oplus j=p,j\oplus k=q\) 的方案数。
设 \(f(T)=\sum_{p,q}c(Tp,Tq),g(T)=\sum_{d|\gcd(n,T)}u(\frac{T}{d})\)。
对于 \(g(T)\) 是好求的。考虑求 \(f(T)\):
要求 \(i\oplus j=Tp,j\oplus k=Tq\),这里 \(Tp\le 2m\),而 \(p,q\) 任意,枚举 \(p\),求有多少个 \(j\) 满足 \(0\le Tp\oplus j\le m\)。满足条件的 \(j\) 在 \(\log m\) 个区间上,进行区间+1;而 \(p,q\) 等价,则 \(f(T)=\sum ((cnt_j)^2)\)。
可以先枚举 \(p\) 进行区间+1求出所有的 \(cnt_j\),再枚举 \(q\) (实际上一样的过程),此时统计答案,由于操作时区间+1,那么对答案的贡献就是 \(\sum cnt\times 1=\sum cnt\),即区间求和。
但是如果使用树状数组或者线段树,复杂度是 \(O(m\log^3 m)\),不可接收。
注意到满足条件的区间在 \(trie\) 树上恰好是一颗子树,可以通过记录子树和以及标记永久化,在递归时求得子树的 \(\sum cnt\),复杂度为 \(O(m\log^2 m)\)。
而对于 \(i=j\) 或者 \(j=k\),实际上是 \(f(T)=\sum cnt_j\)。
点击展开代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define i128 __int128
#define vi vector<int>
#define pii pair<int, int>
#define pll pair<ll, ll>
#define siz(a) ((int)((a).size()))
#define endl '\n'
// #define fi first
// #define se second
// #define double long double
// #define int ll
mt19937 rng((unsigned)chrono::steady_clock::now().time_since_epoch().count());
int rnd(int l, int r) {
return uniform_int_distribution<int>(l, r)(rng);
}
const int N = 6e5 + 5;
int n, m;
int u[N], prime[N / 8], cnt;
bool vis[N];
vector<int> D[N];
ll g[N], f[N];
int ch[1 << 21][2], Size[1 << 21];
ll sum[1 << 21], tag[1 << 21];
int tot, root;
#define ls(x) ch[x][0]
#define rs(x) ch[x][1]
void dfs(int k, int dep, int val)
{
if(dep == 0)
{
Size[k] = 1;
return ;
}
ls(k) = ++tot;
dfs(ls(k), dep - 1, val);
if(val + (1 << (dep - 1)) <= m)
{
rs(k) = ++tot;
dfs(rs(k), dep - 1, val + (1 << (dep - 1)));
}
Size[k] = Size[ls(k)] + Size[rs(k)];
}
void Init()
{
u[1] = 1;
for(int i = 2; i <= N - 5; ++i)
{
if(!vis[i]) prime[++cnt] = i, u[i] = -1;
for(int j = 1; j <= cnt && i * prime[j] <= N - 5; ++j)
{
vis[i * prime[j]] = 1;
if(i % prime[j] == 0){ u[i * prime[j]] = 0; break; }
else u[i * prime[j]] = -u[i];
}
}
for(int i = 1; i <= N - 5; ++i)
for(int j = i; j <= N - 5; j += i)
D[j].emplace_back(i);
}
void getg(int lim)
{
for(int i = 1; i <= m + m; ++i) g[i] = 0;
for(int i = 1; i <= m + m; ++i)
{
int x = gcd(i, n);
for(auto d : D[x]) g[i] += u[i / d];
}
}
int fa[23], num;
void dfs1(int k, int dep, int temp)
{
num = 0;
fa[++num] = k;
for(int i = dep - 1; i >= 0; --i)
{
int d = (temp >> i) & 1;
int t = (m >> i) & 1;
if(t)
{
if(ch[k][d])
{
++tag[ch[k][d]];
sum[ch[k][d]] += Size[ch[k][d]];
}
k = ch[k][d ^ 1];
}else k = ch[k][d];
if(!k) break;
fa[++num] = k;
}
if(k)
{
sum[k] += Size[k];
++tag[k];
}
for(int i = num; i >= 1; --i)
sum[fa[i]] = sum[ls(fa[i])] + sum[rs(fa[i])] + tag[fa[i]] * Size[fa[i]];
}
void dfs3(int k, int dep, int temp)
{
sum[k] = tag[k] = 0;
for(int i = dep - 1; i >= 0; --i)
{
int d = (temp >> i) & 1;
int t = (m >> i) & 1;
if(t)
{
tag[ch[k][d]] = 0;
sum[ch[k][d]] = 0;
k = ch[k][d ^ 1];
}else k = ch[k][d];
sum[k] = tag[k] = 0;
}
}
ll ans;
void dfs2(int k, int dep, int temp, int stag)
{
stag += tag[k];
for(int i = dep - 1; i >= 0; --i)
{
int d = (temp >> i) & 1;
int t = (m >> i) & 1;
if(t)
{
ans += sum[ch[k][d]] + 1ll * stag * Size[ch[k][d]];
k = ch[k][d ^ 1];
}else k = ch[k][d];
stag += tag[k];
}
stag -= tag[k];
ans += sum[k] + 1ll * stag * Size[k];
}
void getf(int lim)
{
for(int T = 1; T <= m + m; ++T)
{
for(int k = 1; k * T <= m + m; ++k)
{
dfs1(root, lim, k * T);
}
ans = sum[root] * 2;
for(int k = 1; k * T <= m + m; ++k)
{
dfs2(root, lim, k * T, 0);
}
f[T] = ans;
for(int k = 1; k * T <= m + m; ++k)
{
dfs3(root, lim, k * T);
}
}
}
void solve()
{
cin >> n >> m;
int lim = 0;
while((1 << lim) <= m + m) ++lim;
root = ++tot;
// cerr << "lim = " << lim << endl;
dfs(root, lim, 0);
getg(lim);
getf(lim);
for(int i = 1; i <= tot; ++i)
{
sum[i] = tag[i] = Size[i] = 0;
ls(i) = rs(i) = 0;
}
tot = 0;
i128 ans = 0;
for(int T = 1; T <= m + m; ++T) ans += (i128)f[T] * g[T];
cout << (ll)ans << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
Init();
int T = 1;
cin >> T;
while(T--) solve();
return 0;
}

浙公网安备 33010602011771号