第七章 基础算法
包含启发式合并、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
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
题意:
- 给出一颗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
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
第六章 搜索
包括模拟退火、爬山法等内容