算法进阶课first

本文深入探讨了多种基础算法,包括启发式合并、Manacher算法、最小表示法、构造和打表等。通过实例解析ACWing上的编程题目,如树上启发式合并、最长回文子串、项链表示问题和幻方构造,展示了如何运用这些算法解决复杂问题。此外,还介绍了搜索方法如模拟退火和爬山法在问题求解中的应用。
摘要由CSDN通过智能技术生成

第七章 基础算法

包含启发式合并、manacher算法、最小表示法、构造、打表等内容

梦幻布丁
n个布丁,有m个颜色
操作:
op1:要把一个颜色的布丁全部变为另一种颜色
op2:询问连续的颜色段个数

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010, M = 1000010;

int n, m;
int h[M], e[N], ne[N], idx;
int color[N], sz[M], p[M];
int ans;

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
    sz[a] ++ ;
}

void merge(int& x, int& y)
{
    if (x == y) return;
    if (sz[x] > sz[y]) swap(x, y);
    for (int i = h[x]; ~i; i = ne[i])
    {
        int j = e[i];
        ans -= (color[j - 1] == y) + (color[j + 1] == y);
    }
    for (int i = h[x]; ~i; i = ne[i])
    {
        int j = e[i];
        color[j] = y;
        if (ne[i] == -1)
        {
            ne[i] = h[y], h[y] = h[x];
            break;
        }
    }
    h[x] = -1;
    sz[y] += sz[x], sz[x] = 0;
}

int main()
{
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%d", &color[i]);
        if (color[i] != color[i - 1]) ans ++ ;
        add(color[i], i);
    }

    for (int i = 0; i < M; i ++ ) p[i] = i;

    while (m -- )
    {
        int op;
        scanf("%d", &op);
        if (op == 2) printf("%d\n", ans);
        else
        {
            int x, y;
            scanf("%d%d", &x, &y);
            merge(p[x], p[y]);
        }
    }

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/700202/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

AcWing 3189. Lomsat gelral(树上启发式合并, dsu on tree, 静态链分治)

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;
const int N = 100010, M =  N * 2;

int n;
int h[N], e[M], ne[M], idx;
int color[N], cnt[N], sz[N], son[N];
LL ans[N], sum;
int mx;

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

int dfs_son(int u, int father)
{
    sz[u] = 1;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j == father) continue;
        sz[u] += dfs_son(j, u);
        if (sz[j] > sz[son[u]]) son[u] = j;
    }
    return sz[u];
}

void update(int u, int father, int sign, int pson)
{
    int c = color[u];
    cnt[c] += sign;
    if (cnt[c] > mx) mx = cnt[c], sum = c;
    else if (cnt[c] == mx) sum += c;

    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j == father || j == pson) continue;
        update(j, u, sign, pson);
    }
}

void dfs(int u, int father, int op)
{
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j == father || j == son[u]) continue;
        dfs(j, u, 0);
    }

    if (son[u]) dfs(son[u], u, 1);
    update(u, father, 1, son[u]);

    ans[u] = sum;

    if (!op) update(u, father, -1, 0), sum = mx = 0;
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &color[i]);
    memset(h, -1, sizeof h);
    for (int i = 0; i < n - 1; i ++ )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b), add(b, a);
    }

    dfs_son(1, -1);
    dfs(1, -1, 1);

    for (int i = 1; i <= n; i ++ ) printf("%lld ", ans[i]);
    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/700269/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

给定一个长度为 n 的由小写字母构成的字符串,求它的最长回文子串的长度是多少。

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 2e7 + 10;

int n;
char a[N], b[N];
int p[N];

void init()
{
    int k = 0;
    b[k ++ ] = '$', b[k ++ ] = '#';
    for (int i = 0; i < n; i ++ ) b[k ++ ] = a[i], b[k ++ ] = '#';
    b[k ++ ] = '^';
    n = k;
}

void manacher()
{
    int mr = 0, mid;
    for (int i = 1; i < n; i ++ )
    {
        if (i < mr) p[i] = min(p[mid * 2 - i], mr - i);
        else p[i] = 1;
        while (b[i - p[i]] == b[i + p[i]]) p[i] ++ ;
        if (i + p[i] > mr)
        {
            mr = i + p[i];
            mid = i;
        }
    }
}

int main()
{
    scanf("%s", a);
    n = strlen(a);

    init();
    manacher();

    int res = 0;
    for (int i = 0; i < n; i ++ ) res = max(res, p[i]);
    printf("%d\n", res - 1);

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/700383/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

有一天,达达捡了一条价值连城的宝石项链,但是,一个严重的问题是,他并不知道项链的主人是谁!

在得知此事后,很多人向达达发来了很多邮件,都说项链是自己的,要求他归还(显然其中最多只有一个人说了真话)。

达达要求每个人都写了一段关于自己项链的描述: 项链上的宝石用数字 0 至 9 来标示。

一个对于项链的表示就是从项链的某个宝石开始,顺指针绕一圈,沿途记下经过的宝石,比如项链: 0−1−2−3,它的可能的四种表示是 0123、1230、2301、3012。

达达现在心急如焚,于是他找到了你,希望你能够编写一个程序,判断两个给定的描述是否代表同一个项链(注意,项链是不会翻转的)。

也就是说给定两个项链的表示,判断他们是否可能是一条项链。

输入格式
输入文件只有两行,每行一个由字符 0 至 9 构成的字符串,描述一个项链的表示(保证项链的长度是相等的)。

输出格式
如果两个对项链的描述不可能代表同一个项链,那么输出 No,否则的话,第一行输出一个 Yes,第二行输出该项链的字典序最小的表示。

数据范围
设项链的长度为 L,1≤L≤1000000

a b a b c
b a b c a
a b c a b
b c a b a
c a b a b
字典序最小的串-原串的最小表示法
暴力-O(n^2)-O(nlogn)
破环成链
a b a b c a b a b c
|       |
  |       |
    |       |
双指针O(n)
对比
  s1 = s.substr(i,n)
  s2 = s.substr(j,n)
  i    i+k   i+n
     j   j+k   j+n
  i+k和j+k是第一个不同的字符
1 s[i+k]>s[j+k]:
    i开头的不是最小表示
    并且 [i,i+k]中开头的长度为n的串也不是
    证:此时可以找到i' in [i,i+k] ,j' in [j,j+k]
  i  i'  i+k   i+n
     j  j'  j+k   j+n
    因为以i'开头的[i',i'+n]一定<[j',j'+n]

    所以i = i+k+1
2 s[i+k]<s[j+k]:
    同理可推
        j = j+k+1
3 s1 == s2:break
  此时出现循环串 且覆盖了[i,i+n] [j,j+n],那么原串 = 循环串
  且j-i刚好是一个循环节,则说明j从i走到j都没找到一个比i更小的
  即把一个循环节枚举完了,相当于把所有可能串都枚举完了
   i
  ab ab ab ab
    \| \| \|
     ab ab ab
     j

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 2000010;

int n;
char a[N], b[N];

int get_min(char s[])
{
    int i = 0, j = 1;
    while (i < n && j < n)
    {
        int k = 0;
        while (k < n && s[i + k] == s[j + k]) k ++ ;
        if (k == n) break;//如果k都已经走了n了 s[i+n] == s[j+n] 则说明是循环串了
        if (s[i + k] > s[j + k]) i += k + 1;
        else j += k + 1;
        if (i == j) j ++ ;//没必要比较相同起点,所以让j走到下一个位置
    }
    int ans = min(i, j);//结束时靠前的就是答案,因为字典序大的我们都让它+k+1了
    // 给最小表示法的终止位置标0表示字符串结尾 方便strcmp()比较
    s[ans + n] = 0;
    return ans;
}

int main()
{
    scanf("%s%s", a, b);
    n = strlen(a);
    memcpy(a + n, a, n);
    memcpy(b + n, b, n);

    int x = get_min(a), y = get_min(b);
    if (strcmp(a + x, b + y)) puts("No");
    else
    {
        puts("Yes");
        puts(a + x);
    }

    return 0;
}

作者:仅存老实人
链接:https://www.acwing.com/solution/content/50880/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

幻方(模拟构造
yxc的直接模拟


题意:

  • 给出一颗n个点的树(1e5),每条边有一个权值c,
  • 每次操作可以令某个权值+1,
  • 求最少操作次数令根节点到每个叶节点路径上的权值和相等

思路
思路(构造):

  • 容易发现,越靠近根节点的,调整代价越小。
  • 我们可以把节点深度类比成距离,题目即为求把所有叶子节点调整到同一高度,每次优先调整靠近根部的。
  • 先dfs一遍更新到最远叶节点的距离dis[x],再循环一遍更新调整其余子节点的距离跟它一样。
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5+10,M=2*N;
int h[N],e[M],w[M],ne[M],idx;
int n,root;
long long d[N],ans;
void add(int a,int b,int c){
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

void dfs(int u,int father){
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        if(j==father)continue;
        dfs(j,u);
        d[u]=max(d[u],d[j]+w[i]);
    }
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        if(j==father)continue;
        ans+=d[u]-d[j]-w[i];
    }
    
}
signed main(){
    memset(h,-1,sizeof h);
    cin>>n>>root;
    for(int i=1;i<n;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c),add(b,a,c);
    }
    dfs(root,-1);
    cout<<ans;
}

把插头dp学了再回来看
在这里插入图片描述

通过连接得到6种状态之间的转移矩阵w[6][6]
在这里插入图片描述
在这里插入图片描述

f[i][j]=sum5k=0f[i−1][k]∗w[k][j]f[i][j]=sum(k=0~5)f[i−1][k]∗w[k][j]
初始化
f[1][1]=f[1][4]=1f[1][1]=f[1][4]=1
终止
f[n−1][0]+f[n−1][4]
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010, M = 1010;

int n;
int w[6][6] = {
    {1, 0, 1, 1, 0, 0},
    {0, 1, 0, 0, 1, 0},
    {0, 1, 0, 0, 1, 0},
    {0, 1, 0, 0, 1, 0},
    {1, 0, 1, 1, 0, 1},
    {0, 0, 0, 0, 1, 0},
};
int f[N][6][M];

void add(int a[], int b[])
{
    for (int i = 0, t = 0; i < M; i ++ )
    {
        t += a[i] + b[i];
        a[i] = t % 10;
        t /= 10;
    }
}

int main()
{
    cin >> n;
    f[1][1][0] = f[1][4][0] = 1;
    for (int i = 2; i < n; i ++ )
        for (int j = 0; j < 6; j ++ )
            for (int k = 0; k < 6; k ++ )
                if (w[k][j])
                    add(f[i][j], f[i - 1][k]);
    int res[M] = {0};
    add(res, f[n - 1][0]), add(res,f[n - 1][4]);
    add(res, res);

    int k = M - 1;
    while (k > 0 && !res[k]) k -- ;
    for (int i = k; i >= 0; i -- ) cout << res[i];
    cout << endl;

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/703067/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

第六章 搜索

包括模拟退火、爬山法等内容
















  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值