07-31 題解
A
首先, 如果 \(k \ge m\) , 答案為 \(m\) , 因為每變換一次, 字首中多一個 1
想法
\(k\) 非常的大, 不可能計算出最終狀態
但是 \(m\) 非常小, 如果有一種 \(O(答案)\) 的做法就好了 (類似 NOI 2024 D2T1 的部分分做法)
重要的性質
對於一棵樹, 如果每個父節點都有至少兩個兒子, 那麼總節點個數 \(\le 2*葉子個數\) (以二叉樹為例理解)
根據此性質, 如果我們暴搜只搜到 \(m\) 個葉子, 那麼複雜度接近 \(O(m)\)
為保證每個父節點至少兩個兒子, 需要把所有狀態為 1 的點剪掉 (''狀態'' 即當前 x)
完結 !
程式碼
#include <bits/stdc++.h>
#define int long long
#pragma optmistic(3)
using namespace std;
const int N = 1e6 + 10;
const int INF = 1e18;
int x, k, m;
int si, ans;
vector <int> temp;
unordered_map <int, bool> vis;
unordered_map <int, vector <int>> rec;
void Corner(){
if(x == 1){
cout << "1\n";
exit(0);
}
if(k >= m){
cout << m << "\n";
exit(0);
}
}
int tar;
unordered_map <int, bool> rpri;
void Get_prii(int i, int num){
if(i == temp.size()){
if(rpri[num]) return;
rpri[num] = 1;
rec[tar].push_back(num);
return;
}
Get_prii(i + 1, num * temp[i]);
Get_prii(i + 1, num);
}
void Get_pri(int x){
temp.clear();
rpri.clear();
int xx = x;
for(int i = 2; i * i <= x; i++){
if(xx % i == 0){
while(xx % i == 0){
temp.push_back(i);
xx /= i;
}
}
}
if(xx != 1){
temp.push_back(xx);
}
tar = x;
Get_prii(0, 1);
sort(rec[x].begin(), rec[x].end());
}
void Dfs(int x, int dep){
if(!si){
cout << ans << "\n";
// printf("%.10lf\n", 1.0 * clock() / CLOCKS_PER_SEC);
exit(0);
}
if(x == 1){
--si;
++ans;
return;
}
if(dep == k - 1){
if(!vis[x]){
Get_pri(x);
vis[x] = 1;
}
for(auto i : rec[x]){
ans += i;
--si;
if(!si) break;
}
return;
}
if(!vis[x]){
Get_pri(x);
vis[x] = 1;
}
for(auto i : rec[x]){
Dfs(i, dep + 1);
}
return;
}
void Calc1(){
si = m;
Dfs(x, 0);
cout << ans << "\n";
// printf("%.10lf\n", 1.0 * clock() / CLOCKS_PER_SEC);
}
signed main(){
cin >> x >> k >> m;
Corner();
Calc1();
}
B
我們喜歡確定的東西, 不喜歡不確定的, 但是這題所有東西都不確定, 這性質非常好
由於都不確定, 所以除了被我們選中的數, 其他都是等價的 !
想出一個狀態, 用來表示一種路徑集合
\(f(l, r, 0/1)\) 表示在這條路徑中, 向右走了 \(l\) 步 (有 \(l\) 個比他小的 \(mid\)) , 向左走了 \(r\) 步 (\(r\) 個比他大的 \(mid\)), 中止位置 否/是 x 的路徑集合的大小
搜尋過程是類似二分的, 所以 \(l, r\) 都是 \(log\) 級別的
\(f(l, r, 0/1) = l! * r!\) 因為所有 大於/小於 當前 \(x\) 的數都是等價的
所以可以仿照題目做一次搜尋, 在搜尋過程中求出 \(f\)
再者, \(f\) 與當前數 \(x\) 的具體值無關, 只和相對大小有關, 這也是 ''序列完全不確定'' 帶來的性質
然後再求出從所有 小於/大於 當前 \(x\) 的數中選出 \(l/ r\) 個數的貢獻, 可以對 前/後 綴做 dp 求出
剩下的數隨便放就行, 對返回值不做貢獻, 有階乘中方案
形式化地
程式碼
#include <bits/stdc++.h>
#define int long long
#pragma optmistic(3)
using namespace std;
const int N = 3e5 + 10, MOD = 1e9 + 7;
int n, m, x;
int num[20][20][2];
struct Node{
int n, v;
Node operator += (const Node &p){
(n += p.n) %= MOD;
(v += p.v) %= MOD;
return {n, v};
}
Node operator += (const int &p){
(v += n * p % MOD) %= MOD;
return {n, v};
}
Node operator * (const Node &p){
int nn = n * p.n % MOD;
int vv = (n * p.v % MOD + v * p.n % MOD) % MOD;
return {nn, vv};
}
}pre[N][20], suf[N][20], mul;
int fac[N], inv[N];
void Init(){
int up = 3e5;
fac[0] = fac[1] = 1;
inv[0] = inv[1] = 1;
for(int i = 2; i <= up; i++){
fac[i] = fac[i - 1] * i % MOD;
inv[i] = (MOD - MOD / i) * inv[MOD % i] % MOD;
}
for(int i = 2; i <= up; i++){
inv[i] = inv[i - 1] * inv[i] % MOD;
}
}
int C(int n, int m){
return fac[n] * inv[m] % MOD * inv[n - m] % MOD;
}
void Dfs(int l, int r, int cl, int cr){
if(l > r){
(num[cl][cr][0] += fac[cl] * fac[cr] % MOD) %= MOD;
return;
}
int mid = (l + r) / 2;
(num[cl][cr][1] += fac[cl] * fac[cr] % MOD) %= MOD;
Dfs(l, mid - 1, cl, cr + 1);
Dfs(mid + 1, r, cl + 1, cr);
}
signed main(){
Init();
cin >> n >> m;
Dfs(1, n, 0, 0);
pre[0][0].n = 1;
for(int i = 1; i <= n; i++){
for(int j = 0; j < 20; j++){
if(j != 0){
pre[i][j] = pre[i - 1][j - 1];
pre[i][j] += i;
}
pre[i][j] += pre[i - 1][j];
// cout << pre[i][j].n << "+" << pre[i][j].v << " ";
}
}
// cout << "\n";
suf[n + 1][0].n = 1;
for(int i = n; i >= 1; i--){
for(int j = 0; j < 20; j++){
if(j != 0){
suf[i][j] = suf[i + 1][j - 1];
suf[i][j] += i;
}
suf[i][j] += suf[i + 1][j];
// cout << suf[i][j].n << "+" << suf[i][j].v << " ";
}
}
// cout << "Ed\n";
for(int i = 1; i <= m; i++){
cin >> x;
int ans = 0;
for(int l = 0; l < 20; l++){
for(int r = 0; r < 20; r++){
if(!num[l][r][0] && !num[l][r][1]){
continue;
}
mul = pre[x - 1][l] * suf[x + 1][r];
// cout << mul.n << "+" << mul.v << " ";
if(num[l][r][0]){
(ans += mul.v * num[l][r][0] % MOD * fac[n - l - r] % MOD) %= MOD;
}
if(num[l][r][1]){
mul += x;
(ans += mul.v * num[l][r][1] % MOD * fac[n - l - r - 1] % MOD) %= MOD;
}
}
}
cout << ans << "\n";
}
}
C
我第一眼的想法是做高維字首和, 該位置的取值在他的補集的子集中取最優的, 但這樣是 \(n^2\) 級別的
性質
這種交換是具有傳遞性的, 即若 a 和 b 可以交換, b 和 c 可以交換, 那麼可以經過 b 中轉, 使得 a, c 互換
所以可以把能互換的點對之間連邊, 處於同一個連通塊的點可以貪心地賦值
但是這樣連邊也是 \(n^2\) 級別的
考慮用類似高維字首和的思路連邊, 即建立一些 ''源點'' 減少連邊數量
每個點向他的補集連邊, 每個點所在集合向他連邊, 每個集合向比他在二進位制上少一個 1 的子集連邊
這樣, 原本處於同一連通塊中的點現在就處在同一個強連通分量中, 用 Tarjan 找一下強連通分量即可
程式碼
#include <bits/stdc++.h>
#define int long long
#pragma optmistic(3)
using namespace std;
const int N = 3e6 + 10, ST = (1 << 20) + 1;
int n, a[N];
vector <int> e[N];
int dfn[N], low[N], cdfn;
int col[N], ccol;
bool ins[N];
vector <int> st;
priority_queue <int, vector <int>, greater <int>> q[N];
void Tarjan(int x){
dfn[x] = low[x] = ++cdfn;
ins[x] = 1;
st.push_back(x);
for(auto i : e[x]){
if(!dfn[i]){
Tarjan(i);
low[x] = min(low[x], low[i]);
}else if(ins[i]){
low[x] = min(low[x], dfn[i]);
}
}
// cout << x << " " << low[x] << "+" << dfn[x] << "\n";
if(dfn[x] == low[x]){
++ccol;
int tp = st.back();
do{
// cout << tp << "tp";
tp = st.back(), st.pop_back();
ins[tp] = 0;
col[tp] = ccol;
}while(tp != x);
// cout << "\n";
}
}
signed main(){
// freopen("1.in", "r", stdin);
// freopen("1.out", "w", stdout);
freopen("seq.in", "r", stdin);
freopen("seq.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i];
}
int up = (1 << 20) - 1, upp = 20;
// cout << ST << "ST\n";
for(int i = 1; i <= n; i++){
int ed = (up ^ a[i]);
e[i + ST].push_back(ed);
e[a[i]].push_back(i + ST);
// cout << i + ST << " " << ed << "\n";
// cout << a[i] << " " << i + ST << "\n";
}
for(int i = 0; i <= up; i++){
for(int j = 0; j < upp; j++){
if((i >> j) & 1){
e[i].push_back(i ^ (1 << j));
// cout << i << " " << (i ^ (1ll << j)) << "\n";
}
}
}
for(int i = 0; i <= ST + n; i++){
if(!dfn[i]){
Tarjan(i);
}
}
// for(int i = 0; i <= ST + n; i++){
// cout << col[i] << " ";
// }
// cout << "\n";
for(int i = ST + 1; i <= ST + n; i++){
// cout << col[i] << " ";
q[col[i]].push(a[i - ST]);
}
// cout << "\n";
for(int i = ST + 1; i <= ST + n; i++){
cout << q[col[i]].top() << " ";
q[col[i]].pop();
}
cout << "\n";
}
D
有個人寫了快一百行的 點分治, 就是為了拿 15 pts 的部分分, 我不說是誰
鴿了