代码源每日一题 div1 (301-307)

代码源每日一题 div1 (301-307)

连续子序列

[Link](连续子序列 - 题目 - Daimayuan Online Judge)

思路

  • d p dp dp

​ 暴力的来设 f i : 以 i 结 尾 的 最 长 子 序 列 f_i:以i结尾的最长子序列 fi:i,对于一个 a i a_i ai我们想看 f a i − 1 f_{a_i-1} fai1是否存在,存在就接到后面,设最长为 m x mx mx,我们 d p dp dp完从前往后遍历 f f f,对于第一个 f i = m x f_i=mx fi=mx的位置一定是字典序最小的。由于值域太大我们不可能开一个数组来存,因此开个 m a p map map来搞一下即可。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
map<int, int> f;
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n;
    int mx = 0;
    for (int i = 1; i <= n; i ++) {
        int x; cin >> x;
        f[x] = f[x - 1] + 1;
        mx = max(mx, f[x]);
    }    
    for (auto it : f) 
        if (it.second == mx) {
            cout << mx << '\n';
            for (int i = it.first - mx + 1; i <= it.first; i ++)
                cout << i << ' ';
            cout << '\n';
            return 0;
        }
    return 0;
}

工作安排

[Link](工作安排 - 题目 - Daimayuan Online Judge)

思路

  • 贪心

​ 将任务按时间排序,对于每个任务,如果当前时间不够,若前面某个点的收益小于当前点的收益,我们就将用当前点把那个点替换掉即可,维护前面选的权值最小的值可以用一个优先队列来做。

小根堆要重载大于号。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
PII a[N];
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i].y >> a[i].x;
    sort(a + 1, a + 1 + n,[&](PII a, PII b) {
        return a.y < b.y;
    });
    priority_queue<PII, vector<PII>, greater<PII>> pq;
    for (int i = 1; i <= n; i ++) {
        if (a[i].y <= 0) continue ;
        if (pq.size() < a[i].y) pq.push(a[i]);
        else if (pq.top().x < a[i].x) {
            pq.pop();
            pq.push(a[i]);
        }        
    }
    LL res = 0;
    while (pq.size()) {
        res += pq.top().first;
        pq.pop();
    }

    cout << res << '\n';
    return 0;
}

三角果计数

[Link](三角果计数 - 题目 - Daimayuan Online Judge)

思路

  • 树形 d p dp dp

​ 发现当三个点在树的一条路径上无解,其他情况均有解,由于不在一条路径上因此必然存在一个中间结点分别与这些点相连,假设这个点和其他三个点相连的边权分别为 a , b , c a,b,c a,b,c,则 a + b + b + c > a + c a+b+b+c>a+c a+b+b+c>a+c,证明与权值路径权值无关了。

​ 接下来考虑如何计数,我们可以这样来划分方案以点 u u u为中间点的方案数,这样的方案数由两部分构成:

  1. u u u的两个不同的子树各选一个,非 u u u的子树里选一个这样是不会构成一条路径的
  2. u u u的三个不同的子树各选一个,这样也不会构成一条路径

​ 设 s z [ u ] : 以 u 为 根 的 子 树 的 节 点 数 sz[u]:以u为根的子树的节点数 sz[u]:u,对于方案一我们要求 非 u 子 树 节 点 数 × ∑ i = 1 n ∑ j = i + 1 n s z [ i ] × s z [ j ] 非u子树节点数\times\sum_{i=1}^n\sum_{j=i+1}^nsz[i]\times sz[j] u×i=1nj=i+1nsz[i]×sz[j],后面的连成可以优化成 O ( n ) O(n) O(n),因为 ( a + b ) 2 = a 2 + b 2 + 2 a b → a b = ( a + b ) 2 − ( a 2 + b 2 ) 2 (a+b)^2=a^2+b^2+2ab\to ab = \frac{(a+b)^2-(a^2+b^2)}{2} (a+b)2=a2+b2+2abab=2(a+b)2(a2+b2),推广到 n n n个变量,我们可以 O ( n ) O(n) O(n)的求 ( a + b ) 2 (a+b)^2 (a+b)2 a 2 + b 2 a^2+b^2 a2+b2,同理方案二我们要求 ∑ i = 1 i = n ∑ j = i + 1 j = n ∑ k = j + 1 k = n s z [ i ] × s z [ j ] × s z [ k ] \sum_{i=1}^{i=n}\sum_{j=i+1}^{j=n}\sum_{k=j+1}^{k=n}sz[i]\times sz[j]\times sz[k] i=1i=nj=i+1j=nk=j+1k=nsz[i]×sz[j]×sz[k],这个通过式子也可以变形到 O ( n ) O(n) O(n),因此直接一个 d f s dfs dfs即可。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 2e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
LL sz[N];
LL res;
LL clac1(vector<int>&ve, int u) {
    if (ve.size() < 2) return 0;
    LL sum1 = 0, sum2 = 0;
    for (auto v : ve) sum1 += sz[v], sum2 += sz[v] * sz[v];
    return (LL)(n - sz[u]) * (sum1 * sum1 - sum2) / 2;
}
LL clac2(vector<int>&ve) {
    if (ve.size() < 3) return 0;
    LL sum1 = 0, sum2 = 0, sum = 0;
    for (auto v : ve) sum += sz[v];
    for (auto v : ve) sum1 += sz[v] * sz[v] * sz[v], sum2 += 3 * sz[v] * sz[v] * (sum - sz[v]);

    return (sum * sum * sum - sum1 - sum2) / 6;
}
void dfs(int u, int fa) {
    vector<int> ve;
    sz[u] = 1;
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa) continue ;
        dfs(j, u);
        sz[u] += sz[j];
        ve.push_back(j);
    }
    res += clac1(ve, u);
    res += clac2(ve);
}
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n;
    memset(h, -1, sizeof h);
    for (int i = 1; i < n; i ++) {
        int x, y, z; cin >> x >> y >> z;
        add(x, y, z), add(y, x, z);
    }
    dfs(1, -1);
    cout << res << '\n';
    return 0;
} 
  • 数学

​ 如果将当前点 u u u去除掉我们可以得到好几颗子树,问题转化为从这些树中选三棵树每个每棵树选一个结点,设每个树的节点数为 s z [ i ] sz[i] sz[i],等价于要求 ∑ i = 1 i = n ∑ j = i + 1 j = n ∑ k = j + 1 k = n s z [ i ] × s z [ j ] × s z [ k ] \sum_{i=1}^{i=n}\sum_{j=i+1}^{j=n}\sum_{k=j+1}^{k=n}sz[i]\times sz[j]\times sz[k] i=1i=nj=i+1j=nk=j+1k=nsz[i]×sz[j]×sz[k],优化一下这个式子,我们可以枚举中间点 j j j对于每一个 j j j我们等价于求 ( ∑ i = 1 i = j − 1 s z [ i ] ) × s z [ j ] × ( ∑ k = j + 1 k = n s z [ k ] ) (\sum_{i=1}^{i=j-1}sz[i])\times sz[j]\times (\sum_{k=j+1}^{k=n}sz[k]) (i=1i=j1sz[i])×sz[j]×(k=j+1k=nsz[k]),也就是我们可以枚举其中一个点,然后保证这个点前面的递增,后面的递减即可,也就是等价于我们求到了 u u u这个点对于已经出现的点作为左半部分,当前枚举的作为 j j j,未枚举到的作为右半部分,因此就可以 O ( n ) O(n) O(n)的求了。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
LL sz[N];
LL res;
void dfs(int u, int fa) {
    sz[u] = 1;    
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa) continue ;
        dfs(j, u);
        res += (sz[u] - 1) * sz[j] * (n - sz[u] - sz[j]);
        sz[u] += sz[j];
    }
}
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    memset(h, -1, sizeof h);
    cin >> n;
    for (int i = 1; i < n; i ++) {
        int x, y, z; cin >> x >> y >> z;
        add(x, y), add(y, x);
    }
    dfs(1, -1);
    cout << res << '\n';
    return 0;
}

整齐的数组2

[Link](整齐的数组2 - 题目 - Daimayuan Online Judge)

思路

  • 枚举,数论

​ 首先给原数组排个序,如果有大于等于一般的数都相同就是无解。

​ 首先如果给你一个 k k k,怎么判断是否和法,对于两个数 a i + x k = a j + y k → a i ≡ a j ( m o d   k ) a_i+xk=a_j+yk\to a_i\equiv a_j(mod\ k) ai+xk=aj+ykaiaj(mod k),因此可以开一个 m a p map map,统计出这 n n n个数 m o d   k mod\ k mod k的值然后遍历 m a p map map看是否存在 ≥ n / 2 \ge n/2 n/2的值。

​ 那么怎么枚举我们的 k k k呢,由于我们要让一半的数相同,假设 a i > a j a_i>a_j ai>aj那么当 a i a_i ai减到等于 a j a_j aj的时候就不需要再减了,设 d = a i − a j d=a_i-a_j d=aiaj,我们可以枚举 d d d的所有约数,对于大于 d d d的数不可能让他俩相同所以没必要枚举,对于非 d d d的约数 a i a_i ai一定不可能减到 a j a_j aj我们也不需要枚举了。假设最优解为 k 1 k_1 k1,那么一定是某个 a i a_i ai变到了 a j a_j aj因此一定可以枚举到。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
int gcd(int a, int b) {
    return b ? gcd(b, a % b) : a;
}
bool check(int x) {
    map<int, int> mp;
    for (int i = 1; i <= n; i ++) mp[(a[i] % x + x) % x] ++;
    for (auto it : mp)
        if (it.second >= n / 2) return true;
    return false;
}
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    int T;
    cin >> T;
    while (T -- ) {
        cin >> n;
        for (int i = 1; i <= n; i ++) cin >> a[i];
        sort(a + 1, a + 1 + n);
        bool ok = false;
        for(int i = 1; i <= n; i ++) {
            int j = i;
            while (j <= n && a[j] == a[i]) j ++;
            if (j - i >= n / 2) {
                ok = true;
                break;
            }
            i = j - 1;
        }

        if (ok) {
            cout << -1 << '\n';
            continue ;
        }

        int res = 1;
        for (int i = 1; i <= n; i ++)
            for (int j = i + 1; j <= n; j ++) {
                int d = abs(a[j] - a[i]);
                for (int k = 1; k <= d / k; k ++) {
                    if (d % k == 0) {
                        if (check(k)) res = max(res, k);
                        if (check(d / k)) res = max(res, d / k);
                    }
                }
            }

        cout << res << '\n';
    }
    return 0;
}

三进制循环

思路

  • 树形 d p dp dp

​ 处理出来以每个点为子树的顺序和逆序的最长长度,然后 d p dp dp判断当前点和父节点的关系,找到当前点出去和进来的最长长度相加即可。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 5e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
int d[N], p[N];
void dfs(int u, int fa) {
    d[u] = p[u] = 1;    
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa) continue ;
        dfs(j, u);
        if ((a[j] + 1) % 3 == a[u]) p[u] = max(p[u], p[j] + 1);
        if ((a[u] + 1) % 3 == a[j]) d[u] = max(d[u], d[j] + 1);
    }
}
int res;
void dp(int u, int fa) {
    if (fa == -1) res = max(res, d[u] + p[u] - 1);
    else if ((a[u] + 1) % 3 == a[fa]) res = max(res, p[u] + max(d[u], d[fa] + 1) - 1);
    else if ((a[fa] + 1) % 3 == a[u]) res = max(res, d[u] + max(p[u], p[fa] + 1) - 1);
    else res = max(res, d[u] + p[u] - 1);
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa) continue ;
        dp(j, u);
    }
}
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n;
    memset(h, -1, sizeof h);
    for (int i = 1; i < n; i ++) {
        int x, y; cin >> x >> y;
        add(x, y), add(y, x);
    }
    for (int i = 1; i <= n; i ++) cin >> a[i];
    dfs(1, -1);
    dp(1, -1);

    cout << res << '\n';
    return 0;
}

树上逆序对

[Link](树上逆序对 - 题目 - Daimayuan Online Judge)

思路

  • 离线,树状数组

​ 很显然模拟是行不通的,我们可以找出 k k k叉树的时候每个结点的情况对于结点 i i i它的子树对应的结点区间为 [ k ( i − 1 ) , k i + 1 ] [k(i-1),ki+1] [k(i1),ki+1],那么这个点的贡献就是这些区间里值小于它的数的个数,我们可以将所有数的区间预处理出来,对于每个区间我们要求这个区间小于某个数的个数,这个可以用我们前边数数这道题的方法,离线树状数组来搞。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 2e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
PII b[N];
int res[N];
struct Node {
    int l, r, h, id;
    bool operator<(Node t)const {
        return h < t.h;
    }
}q[4000000];
int tr[N];
int lowbit(int x) {
    return x & -x;
}
void add(int x) {
    for (; x <= n; x += lowbit(x)) tr[x] ++;
}
int sum(int x) {
    int res = 0;
    for (; x; x -= lowbit(x)) res += tr[x];
    return res;
}
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i], b[i] = {a[i], i};
    sort(b + 1, b + 1 + n);
    for (int k = 1; k < n; k ++) {
        for (int i = 1; ; i ++) {
            int l = (i - 1) * k + 2, r = min(n, i * k + 1);
            if (l > n) break;
            q[++m] = {l, r, a[i], k};
        }
    }

    sort(q + 1, q + m + 1);
    int pos = 1;
    for (int i = 1; i <= m; i ++) {
        while (pos <= n && q[i].h > b[pos].x) add(b[pos ++].y);
        res[q[i].id] += sum(q[i].r) - sum(q[i].l - 1);
    }

    for (int i = 1; i < n; i ++)
        cout << res[i] << ' ';
    
    
    return 0;
}

约分

[link](约分 - 题目 - Daimayuan Online Judge)

思路

​ 二进制暴力枚举分子删除哪些位,直接根据最简比搞出对应的分母,判断当前分母是否合法,即是否符合删除次数,是否可以由原数删除得到。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
LL a, b;
vector<int> get(LL x) {
    vector<int> res;
    while (x) {
        res.push_back(x % 10);
        x /= 10;
    }
    return res;
}
LL gcd(LL a, LL b) {
    return b ? gcd(b, a % b) : a;
}
int cnt[10];
int main() {
    ios::sync_with_stdio(false), cin.tie(0);   
    cin >> a >> b;
    vector<int> numa = get(a), numb = get(b);
    LL d =gcd(a, b), p = a / d, q = b / d;

    for (int j = 1; j < 1 << numa.size(); j ++) {
        for (int i = 0; i < 10; i ++) cnt[i] = 0;
        LL fz = 0, fm;
        for (int i = numa.size() - 1; i >= 0; i --)
            if (j >> i & 1) fz = fz * 10 + numa[i];
            else cnt[numa[i]] ++;
        
        if (!fz || fz % p) continue ;
        fm = fz / p * q;        
        
        for (int i = 0; i < numb.size(); i ++)
            if (fm % 10 == numb[i]) fm /= 10;
            else cnt[numb[i]] --;
        bool ok = false;
        for (int i = 0; i < 10; i ++)
            if (cnt[i]) {
                ok = true;
                break;
            }
        if (ok) continue ;        
        if (a > fz) a = fz, b = fz / p * q;
    }

    cout << a << ' ' << b << '\n';
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值