2023寒假第二阶段测试

B CodeForces - 691D

题解:并查集、贪心

​ 经过模拟后发现可以交换的位置形成了一个个连通块,我们只要对每个连通块中的元素排序即可

那么排序的话可以利用大根堆优先队列实现

#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 1e6 + 10;

int n, m;
int a[N];
int fa[N];
priority_queue<int> q[N];

int find(int x)
{
    return x == fa[x] ? x : fa[x] = find(fa[x]);
}

void merge(int u, int v)
{
    u = find(u);
    v = find(v);
    if (u != v)
        fa[u] = v;
}

void solve()
{
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
        fa[i] = i;
    for (int i = 1; i <= n; ++i)
        cin >> a[i];
    for (int i = 1, u, v; i <= m; ++i)
    {
        cin >> u >> v;
        merge(u, v);
    }
    for (int i = 1; i <= n; ++i)
        q[find(i)].push(a[i]);		//将连通块的根作为队列下标
    for (int i = 1; i <= n; ++i)
    {
        if (q[find(i)].size())
        {
            cout << q[find(i)].top() << " ";
            q[find(i)].pop();
        }
    }
    cout << endl;
}
signed main(void)
{
    Zeoy;
    int T = 1;
    // cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

C LibreOJ - 2452

题解:二分、字符串哈希

首先对于一个反对称串来说,有以下几个结论:

1.长度一定是偶数;

2.和反对称串共对称轴的字串一定也是反对称串,也就是说一个反对称串长度的一半是这个反对称串中子串也是反对称串的数量

3.在对一个反对称串对称轴左边进行取反反转后一定和对称轴右边相同

有了以上结论,我们可以直接从\([1,n-1]\)枚举对称轴的位置,然后对反对称串的长度的一半进行二分,然后\(check\)我们可以利用字符串哈希\(O(1)\)得出

所以时间复杂度为:\(O(nlogn)\)

#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 5e5 + 10;

int n;
ULL h1[N], h2[N], p[N];
int base = 131;

ULL get_h1(int l, int r)
{
    return h1[r] - h1[l - 1] * p[r - l + 1];
}
ULL get_h2(int l, int r)
{
    return h2[l] - h2[r + 1] * p[r - l + 1];
}

int check(int x)
{
    int l = 1, r = min(x, n - x);		//二分反对称串长度的一半
    while (l <= r)		
    {
        int mid = l + r >> 1;
        if (get_h1(x + 1, x + mid) == get_h2(x - mid + 1, x))
            l = mid + 1;
        else
            r = mid - 1;
    }
    return r;
}

void solve()
{
    cin >> n;
    string s;
    cin >> s;
    s = " " + s;
    int ans = 0;
    p[0] = 1;
    for (int i = 1; i <= n; ++i)
        p[i] = p[i - 1] * base;
    for (int i = 1; i <= n; ++i)
        h1[i] = h1[i - 1] * base + s[i] - '0';
    for (int i = n; i >= 1; --i)			//相当于对原来的字符串进行取反后反转的操作
        h2[i] = h2[i + 1] * base + ((s[i] - '0') ^ 1);
    for (int i = 1; i < n; ++i)
        ans += check(i);
    cout << ans << endl;
}
signed main(void)
{
    Zeoy;
    int T = 1;
    // cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

D AtCoder - abc235_e

题解:离线查询、最小生成树

对于该问题我们可以进行离线查询,先将询问边和原有的边一起进行排序,而后模拟\(kruscal\)算法,如果\(u,v\)不在同一集合中,并且该边是询问边,说明如果加入这条边后,最小生成树中一定会有该边,所以该条边的询问为"YES"

#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 2e5 + 10, M = 4e5 + 10;

int n, m, q;
int fa[N];
int ans[N];
struct node
{
    int w, u, v, qid;
    bool operator<(const node &t) const
    {
        return w < t.w;
    }
} ed[M];

int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }

void merge(int u, int v)
{
    u = find(u);
    v = find(v);
    if (u != v)
        fa[u] = v;
}

void solve()
{
    cin >> n >> m >> q;
    for (int i = 1; i <= n; ++i)
        fa[i] = i;
    for (int i = 1, u, v, w; i <= m; ++i)
    {
        cin >> u >> v >> w;
        ed[i] = {w, u, v, 0};
    }
    for (int i = 1, u, v, w; i <= q; ++i)
    {
        cin >> u >> v >> w;
        ed[i + m] = {w, u, v, i};
    }
    sort(ed + 1, ed + m + q + 1);
    for (int i = 1; i <= m + q; ++i)
    {
        auto &[w,u,v,qid] = ed[i];
        if (find(u) != find(v))
        {
            if (qid)
                ans[qid] = 1;
            else
                merge(u, v);
        }
    }
    for (int i = 1; i <= q; ++i)
    {
        if (ans[i])
            cout << "Yes" << endl;
        else
            cout << "No" << endl;
    }
}
signed main(void)
{
    Zeoy;
    int T = 1;
    // cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

E CodeForces - 320D

题解:单调栈优化DP

对于\(u\)来说,如果前面有比他大的数,那么他最终一定会被杀死,那么被杀死的情况有两种:

\(1.u\)前面只有一个比他大的数\(v\),那么\(u\)被杀死的时间就是\(v->u\)这一段时间

\(2.u\)前面有多个比他大的数,...v1...v2...v...u...,那么假设\(v_2>v\),那么很有可能\(v\)会先被\(v_2\)杀死,然后\(v_2\)再杀死\(u\),但是我们会发现不管\(v\)会不会被先杀死,\(u\)被杀死的时间是一定等于\(v->u\)这一段时间,因为\(v_2\)杀死\(v\)后依旧需要走完\(v\)原来的需要走的时间去杀死\(u\)

那么我们知道了,一个数被杀死的时间等于左边第一个比它大的数杀死它的时间,这就符合了单调栈的性质,我们维护单调递增的栈

\(dp[i]\)代表第\(i\)个数被杀死所需要的时间

如果当前准备压入栈的数大于栈顶,那么我们需要在弹出的\(dp[j]\)中找到最大值,因为当前数一定晚于这个最大值被杀死,如果在弹出元素后栈不为空,说明我们会被栈顶的元素杀死,否则该数永远不会被杀死

\(dp[i] = max(dp[j])+1,dp[j]是被弹出的数\)

#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 1e5 + 10, M = 4e5 + 10;

int n;
int maxl[N], stk[N], tt;
int a[N];
int dp[N];

void solve()
{
    cin >> n;
    for (int i = 1; i <= n; ++i)
        cin >> a[i];
    int ans = 0;
    for (int i = 1; i <= n; ++i)
    {
        int tmp = 0;						//一开始回合数为0
        while (tt && a[stk[tt]] <= a[i])
        {
            tmp = max(dp[stk[tt]], tmp);
            tt--;
        }
        if (tt)
            dp[i] = tmp + 1;
        stk[++tt] = i;
    }
    for (int i = 1; i <= n; ++i)
        ans = max(dp[i], ans);
    cout << ans << endl;
}
signed main(void)
{
    Zeoy;
    int T = 1;
    // cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

F CodeForces - 1619D

题解:二分答案+抽屉原理 \(O(nmlog1e9)\)

首先题目给出“最小值的最大值”,很明显就是二分答案

题目又说明最多只能打开\(n-1\)个水龙头,根据抽屉原理,也就是\(n\)个花盆一定至少有两个花盆被同一水管浇到水

那么我们只要对\(>=mid\)的数置为1,\(<mid\)的数置为0

所以我们\(check\)某个答案的合法性需要注意:

1.至少有两个花盆被同一根水管浇到水,即一行上至少出现两个1

2.所有花盆都要被浇到水,也就是说每一列上都要有1

那么满足上述两条的\(mid\)就是合法的

#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 1e5 + 10, M = 4e5 + 10;

int n, m;
int a[N];
int st[N];
bool check(int mid)
{
    vector<int> vis(n + 2);
    for (int i = 1; i <= m; ++i)
        for (int j = 1; j <= n; ++j)
        {
            if (a[(i - 1) * n + j] < mid)
                st[(i - 1) * n + j] = 0;
            else
            {
                st[(i - 1) * n + j] = 1;
                vis[j] = 1;
            }
        }
    for (int i = 1; i <= n; ++i)
        if (!vis[i])
            return 0;
    int cnt = 0;
    for (int i = 1; i <= m; ++i)
    {
        cnt = 0;
        for (int j = 1; j <= n; ++j)
        {
            if (st[(i - 1) * n + j] == 1)
                cnt++;
        }
        if (cnt >= 2)
            return 1;
    }
    return 0;
}

void solve()
{
    cin >> m >> n;
    for (int i = 1; i <= m; ++i)
        for (int j = 1; j <= n; ++j)
            cin >> a[(i - 1) * n + j];

    int l = 0, r = 1e9;
    while (l <= r)
    {
        int mid = l + r >> 1;
        if (check(mid))
            l = mid + 1;
        else
            r = mid - 1;
    }
    cout << r << endl;
}
signed main(void)
{
    Zeoy;
    int T = 1;
    cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

G CodeForces - 980E

题解:树上倍增+贪心

对于一个地区\(i\)\(2_i\)个士兵,所以我们每次一定会贪心选择编号大的地区,所以我们可以将编号最大的地区作为根

我们从大到小遍历地区,暴力向上找到每个节点到根节点路径第一个尚未选的点,那么我们选的话一定是选一条从节点到根的完完整整的路径,所以我们需要判断这条路径上还有多少个点未选,判断多少个点没有选我们可以直接利用深度判断;我们再分析一下复杂度,很显然每个点只会被遍历到一次,并且每次进行树上倍增,所以复杂度可以保证为\(O(nlogn)\)

#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 1e6 + 10, M = 4e5 + 10;

int n, k;
vector<int> g[N];
int fa[N][22];
int dep[N];

void dfs(int u, int par)
{
    dep[u] = dep[par] + 1;
    fa[u][0] = par;
    for (int i = 1; i <= 20; ++i)
        fa[u][i] = fa[fa[u][i - 1]][i - 1];
    for (auto &v : g[u])
    {
        if (v == par)
            continue;
        dfs(v, u);
    }
}

void solve()
{
    cin >> n >> k;
    k = n - k;
    for (int i = 1, u, v; i < n; ++i)
    {
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs(n, 0);

    vector<bool> vis(n + 2);
    for (int i = n; i >= 1 && k > 0; --i)
    {
        int u = i;
        if (vis[u])
            continue;
        for (int j = 20; j >= 0; j--)
        {
            if (!vis[fa[u][j]] && dep[fa[u][j]] >= 1)
                u = fa[u][j];
        }
        if (k - (dep[i] - dep[u] + 1) >= 0)
        {
            k -= (dep[i] - dep[u] + 1);
            for (int v = i; v != fa[u][0]; v = fa[v][0])
                vis[v] = true;
        }
    }
    for (int i = 1; i <= n; ++i)
        if (vis[i] == false)
            cout << i << " ";
    cout << endl;
}
signed main(void)
{
    Zeoy;
    int T = 1;
    // cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

I 洛谷 - P1270

题解:树上背包

读入部分二叉树递归读入

状态表示:\(dp[u][i]\):以\(u\)节点为根的子树中\(i\)秒能偷到的画的数量

状态属性:\(MAX\)

首先只能在叶子节点才能偷到画,并且每幅画的时间为\(5s\),所以在叶子节点的状态转移方程:\(dp[u][i]=dp[u][i-5]+1\)

如果不是在叶子节点,那么我们只要把\(i\)秒时间分给其左儿子和右儿子即可

\(dp[u][i]=max(dp[u][i],dp[lson][j]+dp[rson][i-j])\)

那么时间这里的\(i\)指的是剩余的时间,我们要记得路程时间花费是两倍,然后m需要-1,因为我们需要在警察到来之前离开

#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 2e3 + 10, M = 4e5 + 10;

struct node
{
    int w, num;
} seg[N << 2];
int m;
int dp[N][N];

void build(int id)
{
    int a, b;
    cin >> a >> b;
    seg[id].w = 2 * a, seg[id].num = b;
    if (!b)
    {
        build(id << 1);
        build(id << 1 | 1);
    }
}

void dfs(int id, int w)
{
    int lson = id << 1, rson = id << 1 | 1;
    if (seg[id].num == 0)
    {
        dfs(lson, w + seg[lson].w);
        dfs(rson, w + seg[rson].w);
        for (int i = 1; i <= m - w; ++i)		//m-w是现在剩余的总时间,i代表左儿子分配的时间
            for (int j = 1; j <= m - w - i; ++j)//j代表右儿子分配的时间
                dp[id][w + i + j] = max(dp[id][w + i + j], dp[lson][w + i] + dp[rson][w + j]);
    }
    else
    {
        for (int i = 1; i <= seg[id].num; ++i)
        {
            int now = w + (i * 5);
            if (now > m)
                break;
            dp[id][now] = dp[id][now - 5] + 1;
        }
    }
}

void solve()
{
    cin >> m;
    m--;
    build(1);
    dfs(1, seg[1].w);
    cout << dp[1][m] << endl;
}
signed main(void)
{
    Zeoy;
    int T = 1;
    // cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

J Gym - 102832A

题解:混合背包

01背包和完全背包的混合

一开始首次额外赠送是01背包,后面是完全背包

我们只需要每次先做一次01背包:\(dp[i][j] = max(dp[i-1][j],dp[i-1][j-v[i]]+w[i])\)

再做完全背包:\(dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]+w[i])\)

#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 2e3 + 10;

int n;
int dp[N];
int a[8] = {0, 1, 6, 28, 88, 198, 328, 648};
int b[8] = {0, 8, 18, 28, 58, 128, 198, 388};

void solve()
{
    cin >> n;
    int ans = 0;
    for (int i = 1; i <= 7; ++i)
    {
        for (int j = n; j >= a[i]; --j)
            dp[j] = max(dp[j], dp[j - a[i]] + a[i] * 10 + b[i]);

        for (int j = a[i]; j <= n; ++j)
            dp[j] = max(dp[j], dp[j - a[i]] + a[i] * 10);
    }
    cout << dp[n] << endl;
}
signed main(void)
{
    Zeoy;
    int T = 1;
    // cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}
posted @ 2023-03-17 20:45  Zeoy_kkk  阅读(12)  评论(0编辑  收藏  举报