CF1581 - A~F

source

A - CQXYM Count Permutations(思维)

对于排列\(p\),如果它的对数是\(k\),那么可以构造\(\{2n-p_i\}\),使得它的对数变为\(2n-1-k\)。如果\(k\ge n\),就有\(2n-k-1 < n\)。即每个对数大于等于\(n\)的排列都可以构造一个小于\(n\)的排列,一共有\((2n)!\)。因此答案为\(\frac{(2n)!}{2}\)

// pass

B - Diameter of Graph

分类讨论

// pass

C - Portal (前缀和)

枚举地狱门的宽度的左右边界,然后中间部分可以枚举下边界,用一些前缀和计算代价。设当前下边界在第\(i\)行,有最小代价\(\min\limits_{j=0}^{i-1}(g_i-f_j)\),即\(g_i-\max\limits_{j=0}^{i-1}(f_j)\)。维护\(f\)的前缀最大值即可。至于\(f\)\(g\)的计算,用行列前缀和维护。

注意地狱门缺四角也是合法的。时间复杂度\(O(m^2n)\)

#include <bits/stdc++.h>

#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mp make_pair
#define seteps(N) fixed << setprecision(N) 
typedef long long ll;

using namespace std;
/*-----------------------------------------------------------------*/

ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f

const int N = 5e2 + 10;
const double eps = 1e-5;

int costr[N][N];
int costc[N][N];
char s[N][N];
int mx[N];

int main() {
    IOS;
    int t;
    cin >> t;
    while(t--) {
        int n, m;
        cin >> n >> m;
        for(int i = 1; i <= n; i++) cin >> s[i] + 1;
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= m; j++) {
                costr[i][j] = costr[i][j - 1] + s[i][j] - '0';
            }
        }
        for(int j = 1; j <= m; j++) {
            for(int i = 1; i <= n; i++) {
                costc[j][i] = costc[j][i - 1] + ((s[i][j] - '0') ^ 1);
            }
        }
        int ans = INF;
        for(int len = 4; len <= m; len++) {
            for(int l = 1; l + len - 1 <= m; l++) {
                mx[0] = -INF;
                int r = l + len - 1;
                int rsum = 0;
                for(int i = 1; i <= n; i++) {
                    int d = costr[i][r - 1] - costr[i][l];
                    int rd = r - l - 1 - d;
                    mx[i] = costc[l][i] + costc[r][i] + rsum + d - rd;
                    mx[i] = max(mx[i], mx[i - 1]);
                    if(i - 4 > 0) {
                        ans = min(ans, rsum + costc[l][i - 1] + costc[r][i - 1] + rd - mx[i - 4]);
                    } 
                    rsum += d;
                }
            }
        }
        cout << ans << endl;
    }
}

D - Mathematics Curriculum(动态规划)

见:CF1581D - Mathematics Curriculum(动态规划)

E - Train Maintenance(暴力)

  • 如果\(x_i+y_i > \sqrt{m}\),那么其中正在维护的时间段不超过\(\sqrt{m}\)段。直接在差分数组上对每一段区间加。由于询问时间是递增的,直接边修改边累加即可。稍微注意一下删除的情况就可以。

  • 如果\(x_i+y_i \le \sqrt{m}\),如果\(i\)号车是第\(s_i\)天进入的,当前是第\(t\)天。令\(a=x_i+y_i\)那么\(i\)号车维护的条件就是\((t-s_i) \mod a =k\ge x_i\),即\(t \mod a =(k+s_i) \mod a, k \in [x_i, a)\)。所以创建\(\sqrt{m}\)个数组,对\((k+s_i) \mod a, k \in [x_i, a)\)暴力修改即可,查询累加每个数组的\(t \mod a\)项即可。

注意删除操作要根据其加入那天来修改,而不是当前天数。

#include <bits/stdc++.h>

#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mp make_pair
#define seteps(N) fixed << setprecision(N) 
typedef long long ll;

using namespace std;
/*-----------------------------------------------------------------*/

ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f

const int N = 3e5 + 10;
const int M = 1e3 + 10;
const double eps = 1e-5;

typedef pair<int, int> PII;
PII ts[N];
int cnt1[M][M];
int cnt2[N];
int sqn;
int n, m;
int si[N];

void upd1(int x, int y, int s, int val) {
    int a = x + y;
    assert(a <= sqn);
    for(int i = x + 1; i <= a; i++) {
        cnt1[a][(i - 1 + s) % a] += val;
    }
}

int que1(int p) {
    int res = 0;
    for(int i = 1; i <= sqn; i++) {
        res += cnt1[i][p % i];
    }
    return res;
}

void ad(int x, int y, int id, int s) {
    si[id] = s;
    for(int i = s + x; i <= m; i += x + y) {
        cnt2[i] += 1;
        if(i + y <= m) cnt2[i + y] -= 1;
    }
}

void mi(int x, int y, int id, int ts, int &res) {
    int s = si[id];
    for(int i = s + x; i <= m; i += x + y) {
        cnt2[i] -= 1;
        if(i + y <= m) cnt2[i + y] += 1;
        if(ts >= i && ts < i + y) res--;
    }
}


int main() {
    IOS;
    cin >> n >> m;
    for(int i = 1; i <= n; i++) {
        cin >> ts[i].first >> ts[i].second;
    }
    sqn = sqrt(n);
    int ans = 0;
    for(int i = 1; i <= m; i++) {
        ans += cnt2[i];
        int op, k;
        cin >> op >> k; 
        int x = ts[k].first, y = ts[k].second;
        if(op == 1) {
            if(x + y <= sqn) {
                si[k] = i;
                upd1(x, y, i, 1);
            } else ad(x, y, k, i);
        } else {
            if(x + y <= sqn) upd1(x, y, si[k], -1);
            else mi(x, y, k, i, ans);
        }
        cout << ans + que1(i) << endl;
    }
}

F - Subsequence(st表,dp,笛卡尔树)

化一下式子可得

\[(m-1)\sum_{i=1}^{m}{a_{b_i}}-\sum_{i=1}^{m}\sum^{m}_{j=i+1}{2f(b_i,b_j)} \]

可以发现,\(f\)和区间最小值有有关,假设一个序列涉及到的区间为\([l,r]\),这个序列最小值所在的位置是\(p\)\(p\)的左边有序列的\(k_1\)个数,右边有\(k_2\)个,那么序列中横跨\(p\)的贡献就是\(-a_p\cdot k_1 \cdot k_2\),就是上面式子中第二项的一部分。然后剩余的贡献就会被分为左右两部分,可以分治下去,最后求得序列的值。

由此可以提出一个dp,设\(f(l,r,k)\)为区间\([l,r]\)中长度为\(k\)的序列的最大的值是多少,容易得到转移方程

\[f(l,r,k)=\max_{i=0}^{k}{[f(l,p-1,i)+f(p+1,r,k-i)-2i\cdot (k-i)a_p]} \]

以及

\[f(l,r,k)=\max_{i=0}^{k}{[f(l,p-1,i-1)+f(p+1,r,k-i) + f(p,p,1)-2(i\cdot (k-i+1)-1)a_p]} \]

\(f(l,r,k)\)取两者中的较大值,上下两式分别代表\(a_p\)不在序列中和\(a_p\)在序列中,\(p\)代表区间\([l,r]\)最小值的编号。

看起来会超时,实则不然。不同的\((l,r)\)只有\(O(n)\)个,而且因为有\(k\le r-l+1\)的限制,所以枚举\(i\)的个数可以不是\(k\),可以优化到区间的长度。因此直接记忆化搜索,时间复杂度为\(O(n^2)\)

#include <bits/stdc++.h>

#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mp make_pair
#define seteps(N) fixed << setprecision(N) 
typedef long long ll;

using namespace std;
/*-----------------------------------------------------------------*/

ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f3f3f3f3f
const int M = 5e3 + 10;
const int N = 4e3 + 10;
const double eps = 1e-5;

ll arr[N];
int mipos[20][N];
ll dp[M][N];
bool vis[M][N];
int id[N][N];

int n, m, si;

int que(int l, int r) {
    int k = log2(r - l + 1);
    int p1 = mipos[k][l], p2 = mipos[k][r - (1 << k) + 1];
    return arr[p1] < arr[p2] ? p1 : p2;
}

void init(int l, int r) {
    if(l > r) return ;
    if(id[l][r]) return ;
    if(l == r) return ;
    id[l][r] = ++si;
    int mid = que(l, r);
    init(mid + 1, r);
    init(l, mid - 1);
}



ll solve(int l, int r, int k) {
    if(k > r - l + 1) return 0;
    if(l > r) return 0;
    if(k == 0) return 0;
    if(l == r) {
        return (m - 1) * arr[l]; 
    }
    int ind = id[l][r];
    if(vis[ind][k]) return dp[ind][k];
    ll res = 0;
    int mid = que(l, r);
    for(int i = max(0, k + mid - r); i <= min(k, mid - l + 1); i++) {
        res = max(res, solve(l, mid - 1, i) + solve(mid + 1, r, k - i) - 1ll * 2 * i * (k - i) * arr[mid]);
        if(i) res = max(res, solve(l, mid - 1, i - 1) + solve(mid + 1, r, k - i) + (m - 1) * arr[mid] - 1ll * 2 * (i * (k - i + 1) - 1) * arr[mid]);
    }
    vis[ind][k] = 1;
    return dp[ind][k] = res;
}

int main() {
    IOS;
    cin >> n >> m;
    for(int i = 1; i <= n; i++) {
        cin >> arr[i];
        mipos[0][i] = i;
    }
    for(int i = 1; i < 20; i++) {
        for(int j = 1; j <= n; j++) {
            int p1 = mipos[i - 1][j], p2 = mipos[i - 1][min(n, j + (1 << (i - 1)))];
            mipos[i][j] = arr[p1] < arr[p2] ? p1 : p2;
        }
    }
    init(1, n);
    cout << solve(1, n, m) << endl;
}

根据题解,序列的值可以化为类似树上若干个点两点间距离和的形式。因此可以直接建立笛卡尔树,在树上跑\(k\)个点距离和最大的dp,本质上和上面的做法一模一样,殊途同归了属于是。

笛卡尔树就是treap中随机数换成点的权值,关键字换成点的下标,即treap是笛卡尔树的一种。笛卡尔树中取若干点之间两两距离和,就是本题中相应序列的值。

posted @ 2021-10-12 00:03  limil  阅读(52)  评论(0编辑  收藏  举报