Preface
作为一名堪堪入门的 A c m e r Acmer Acmer ,谨以此贴记录我算法入门年 2025 2025 2025 的点滴。
英文题面的翻译由 D e e p L DeepL DeepL 完成,样例输入输出较少,以记录向为主,若需查看原题请点击蓝色超链接部分
后续应该会逐渐完善此贴,牛客、cf 等平台也可能慢慢加进来滴,现在关注以后就是老粉啦,更新有动力捏
每三十个赞 or 收藏加更一套ABC!
Atcoder Beginner Contest
AtCoder Beginner Contest(ABC)作为入门级的算法竞赛,以其适中的难度和丰富的经典题型,成为了许多初学者的练兵场。笨人暂时定下了 3 / w e e k s 3/weeks 3/weeks 的 abc 刷题计划,每场坚持补到F,此贴普遍记录EF题和较新颖其他题。
abc 391
D - Gravity
Algorithm
思维,枚举
Problom Statement
有一个网格,网格中有 1 0 9 10^9 109 行和 W W W 列。从左边起第 x x x 列和从底部起第 y y y 行的单元格用 ( x , y ) (x,y) (x,y) 表示。
共有 N N N 个块。每个图块是一个 1 × 1 1 \times 1 1×1 正方形,图块 i i i -th( 1 ≤ i ≤ N 1 \leq i \leq N 1≤i≤N )位于时间 0 0 0 的单元格 ( X i , Y i ) (X_i,Y_i) (Xi,Yi) 。
在时间 t = 1 , 2 , … , 1 0 100 t=1,2,\dots,10^{100} t=1,2,…,10100 时,这些图块按照以下规则移动:
- 如果整个底行都布满了图块,那么底行的所有图块都会被移除。
- 对于剩余的每个区块,按照从下到上的顺序,执行以下操作:
- 如果该图块位于最下面一行,或者它下面的单元格中有一个图块,则不做任何操作。
- 否则,将该图块向下移动一格。
给你 Q Q Q 个查询。对于 j j j -th 查询( 1 ≤ j ≤ Q 1 \leq j \leq Q 1≤j≤Q ),请回答在 T j + 0.5 T_j+0.5 Tj+0.5 时间是否存在图块 A j A_j Aj 。
Constraints
- 1 ≤ N ≤ 2 × 1 0 5 1 \leq N \leq 2 \times 10^5 1≤N≤2×105
- 1 ≤ W ≤ N 1 \leq W \leq N 1≤W≤N
- 1 ≤ X i ≤ W 1 \leq X_i \leq W 1≤Xi≤W
- 1 ≤ Y i ≤ 1 0 9 1 \leq Y_i \leq 10^9 1≤Yi≤109
- ( X i , Y i ) ≠ ( X j , Y j ) (X_i,Y_i) \neq (X_j,Y_j) (Xi,Yi)=(Xj,Yj) 如果 i ≠ j i \neq j i=j 。
- 1 ≤ Q ≤ 2 × 1 0 5 1 \leq Q \leq 2 \times 10^5 1≤Q≤2×105
- 1 ≤ T j ≤ 1 0 9 1 \leq T_j \leq 10^9 1≤Tj≤109
- 1 ≤ A j ≤ N 1 \leq A_j \leq N 1≤Aj≤N
- 所有输入值均为整数。
Solution
用
p
a
i
r
<
i
n
t
,
i
n
t
>
pair<int,int>
pair<int,int> 存储坐标,将
X
X
X 坐标相同(位于同一列)的方块从底向上依次存入数组
v
v
v 中
m
i
n
v
minv
minv 表示所有列中的最底层数(即可以消去的最高层),
m
a
x
v
maxv
maxv 记录所有列中的最高层
直观地想,第
i
i
i 层被消除的时间取决于该层方块位置最高的那个(只要最高的那块到达底部,该层其他方块也一定已经到达,且前置消除层数一定不会影响后续最高方块的下降)
t
i
[
i
]
=
m
a
x
(
t
i
[
i
]
,
v
[
j
]
[
i
]
)
;
ti[i]=max(ti[i], v[j][i]);
ti[i]=max(ti[i],v[j][i]);
m
i
n
v
−
m
a
x
v
minv-maxv
minv−maxv 之间的方块一定不会被消除,
t
[
i
]
t[i]
t[i] 记为
∞
\infty
∞
查询时,
T
j
+
0.5
T_j+0.5
Tj+0.5 时间存在图块
A
j
A_j
Aj 等价于
T
j
T_j
Tj 时刻图块
A
j
A_j
Aj 没被消除,即
A
j
A_j
Aj 消除时刻大于
T
j
T_j
Tj
找到对应层数后比较输出即可
Code
#include <bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false), cin.tie(0);
typedef long long LL;
typedef pair<int, int> PII;
const int N=2e5+10;
PII a[N], b[N];
vector<int> v[N], ti;
bool cmp(PII A, PII B)
{
return A.second<B.second;
}
int main()
{
IOS
int n, w, minv=1e9+10, maxv=-1;
cin>>n>>w;
for(int i=1; i<=n; i++)
{
cin>>a[i].first>>a[i].second;
b[i]=a[i];
}
sort(b+1, b+n+1, cmp);
for(int i=1; i<=n; i++)
v[b[i].first].push_back(b[i].second);
for(int i=1; i<=w; i++)
{
minv=min(minv, (int)v[i].size());
maxv=max(maxv, (int)v[i].size());
}
ti.resize(maxv+5);
for(int j=1; j<=w; j++)
for(int i=0; i<minv; i++)
ti[i]=max(ti[i], v[j][i]);//tu[i]第i层消去的时刻, i下标从0开始
for(int i=minv; i<maxv; i++) ti[i]=1e9+10;
int q;
cin>>q;
while(q--)
{
int t, x;
cin>>t>>x;
//a[x].first--坐标的x .second坐标的y 找第一个大于等于y的一定是目标块,减去begin得到层数
int tower=lower_bound(v[a[x].first].begin(), v[a[x].first].end(), a[x].second) - v[a[x].first].begin();
cout<< ( t<ti[tower]? "Yes\n": "No\n");//查询时刻未到消除时刻
}
return 0;
}
Sample Input 1
5 3
1 1
1 2
2 2
3 2
2 3
6
1 1
1 2
2 3
2 5
3 4
3 5
Sample Output 1
Yes
Yes
No
Yes
No
Yes
E - Hierarchical Majority Vote
Algorithm
DP
Problom Statement
对于长度为 3 n 3^n 3n ( n ≥ 1 n \geq 1 n≥1 ) 的二进制字符串 B = B 1 B 2 … B 3 n B = B_1 B_2 \dots B_{3^n} B=B1B2…B3n ,我们定义了如下操作,以获得长度为 3 n − 1 3^{n-1} 3n−1 的二进制字符串 C = C 1 C 2 … C 3 n − 1 C = C_1 C_2 \dots C_{3^{n-1}} C=C1C2…C3n−1 :
- 将 B B B 中的元素分成 3 3 3 组,并从每组中取多数值。也就是说,对于 i = 1 , 2 , … , 3 n − 1 i=1,2,\dots,3^{n-1} i=1,2,…,3n−1 ,让 C i C_i Ci 成为 B 3 i − 2 B_{3i-2} B3i−2 、 B 3 i − 1 B_{3i-1} B3i−1 和 B 3 i B_{3i} B3i 中出现频率最高的值。
给你一个长度为 3 N 3^N 3N 的二进制字符串 A = A 1 A 2 … A 3 N A = A_1 A_2 \dots A_{3^N} A=A1A2…A3N 。设 A ′ = A 1 ′ A' = A'_1 A′=A1′ 是对 A A A 进行上述运算 N N N 次后得到的长度为 1 1 1 的字符串。
求要改变 A 1 ′ A'_1 A1′ 的值,必须改变 A A A 中最少多少个元素(从 0 0 0 改为 1 1 1 或从 1 1 1 改为 0 0 0 )。
Constraints
- N N N 是包含 1 ≤ N ≤ 13 1 \leq N \leq 13 1≤N≤13 的整数。
- A A A 是长度为 3 N 3^N 3N 的字符串,由 0 0 0 和 1 1 1 组成。
Solution
此问题具有明显的 最优子结构 和 重叠子问题 性质,又是求 xxx 的最小值,考虑进行动态规划。
f
[
i
]
[
j
]
[
0
/
1
]
f[i][j][0/1]
f[i][j][0/1] 表示进行了
i
i
i 轮运算后的第
j
j
j (索引)组的值变成
0
/
1
0/1
0/1 所进行的最少操作次数
题目最终所求就转化为了求
m
a
x
(
f
[
n
]
[
0
]
[
0
]
,
f
[
n
]
[
0
]
[
1
]
)
max(f[n][0][0], f[n][0][1])
max(f[n][0][0],f[n][0][1]),通过取
m
i
n
min
min 进行每一层运算的状态转移
具体实现时,先
l
e
n
/
=
3
len/=3
len/=3 再进行模拟转移,
j
∗
3
+
0
/
1
/
2
j*3 +0/1/2
j∗3+0/1/2 即可找到运算前的索引
Code
//https://www.luogu.com.cn/problem/solution/AT_abc391_e
#include <bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false), cin.tie(0);
typedef long long LL;
const int N1=15, N2=2e6;
int f[N1][N2][2], n;//f[i][j][0/1]表示将前i个字符的值改为0/1所需要的最少操作次数
string s;
int main()
{
IOS
cin>>n>>s;
int len=s.size();
for(int i=0; i<len; i++)
{
if(s[i]^'0') f[0][i][1]=0, f[0][i][0]=1;
else f[0][i][0]=0, f[0][i][1]=1;
}
len/=3;//第一次缩小
for(int i=0; i<n; i++, len/=3)
{
for(int j=0; j<len; j++)
{
int a0=f[i][j*3 + 0][0], a1=f[i][j*3 + 1][0], a2=f[i][j*3 + 2][0];
int b0=f[i][j*3 + 0][1], b1=f[i][j*3 + 1][1], b2=f[i][j*3 + 2][1];
f[i+1][j][0]=min({a0+a1+a2, a0+a1+b2, a0+b1+a2, b0+a1+a2});
f[i+1][j][1]=min({b0+b1+b2, b0+b1+a2, b0+a1+b2, a0+b1+b2});
}
}
cout<<max(f[n][0][0], f[n][0][1]);
return 0;
}
F - K-th Largest Triplet
Algorithm
单调性,枚举
Problom Statement
给你三个长度为 N N N 的整数序列,即 A = ( A 1 , A 2 , … , A N ) A=(A_1,A_2,\ldots,A_N) A=(A1,A2,…,AN) 、 B = ( B 1 , B 2 , … , B N ) B=(B_1,B_2,\ldots,B_N) B=(B1,B2,…,BN) 和 C = ( C 1 , C 2 , … , C N ) C=(C_1,C_2,\ldots,C_N) C=(C1,C2,…,CN) ,以及一个整数 K K K 。
对于 N 3 N^3 N3 中的每个整数 i , j , k i,j,k i,j,k ( 1 ≤ i , j , k ≤ N 1\leq i,j,k\leq N 1≤i,j,k≤N ),计算值 A i B j + B j C k + C k A i A_iB_j + B_jC_k + C_kA_i AiBj+BjCk+CkAi 。在所有这些值中,找出 第 K K K大的值。
Constraints
- 1 ≤ N ≤ 2 × 1 0 5 1\leq N \leq 2\times 10^5 1≤N≤2×105
- 1 ≤ K ≤ min ( N 3 , 5 × 1 0 5 ) 1\leq K \leq \min(N^3,5\times 10^5) 1≤K≤min(N3,5×105)
- 1 ≤ A i , B i , C i ≤ 1 0 9 1\leq A_i,B_i,C_i \leq 10^9 1≤Ai,Bi,Ci≤109
- 所有输入值均为整数。
Solution
注意到将
A
,
B
,
C
A, B, C
A,B,C 元素单调不增排序后,
A
i
B
j
+
B
j
C
k
+
C
k
A
i
A_iB_j + B_jC_k + C_kA_i
AiBj+BjCk+CkAi 中
i
,
j
,
k
i,j,k
i,j,k 任何一个变小,表达式的值也必然单调不增
三重
f
o
r
for
for 循环由
m
(
K
)
m (K)
m(K) 的限制可知时间复杂度为
O
(
K
log
2
K
)
O(K\log^2 K)
O(Klog2K),最坏情况下
O
(
5
e
5
∗
20
∗
20
)
O(5e5*20*20)
O(5e5∗20∗20)这样子
枚举后用
n
t
h
_
e
l
e
m
e
n
t
nth\_element
nth_element 找到第
K
K
K 大的值并输出即可
Code
//优先队列也可以做,数组降序后有单调性,取后面的数一定会使乘积变小
#include <bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false), cin.tie(0);
typedef long long LL;
const int N=2e5+10, M=2e8+10;
int a[N], b[N], c[N];
LL ans[M];
bool cmp(int x, int y)
{
return x>y;
}
int main()
{
IOS
LL n, m;
cin>>n>>m;
for(int i=1; i<=n; i++) cin>>a[i];
for(int i=1; i<=n; i++) cin>>b[i];
for(int i=1; i<=n; i++) cin>>c[i];
sort(a+1, a+n+1, cmp), sort(b+1, b+n+1, cmp), sort(c+1, c+n+1, cmp);//降序
int cnt=0;
for(LL i=1; i<=n && i<=m; i++)
for(LL j=1; j<=n && i*j<=m; j++)
for(LL k=1; k<=n && i*j*k<=m; k++)//跑满k<=n是n(logn)^2的复杂度
ans[++cnt]=(LL)a[i]*b[j]+(LL)b[j]*c[k]+(LL)c[k]*a[i];
nth_element(ans+1, ans+m, ans+cnt+1, greater<LL>());//左闭右开
cout<<ans[m];
return 0;
}
abc 392
E - Cables and Servers
Algorithm
并查集
时间复杂度
O
(
n
+
m
)
O(n+m)
O(n+m)
Problom Statement
有编号为
1
1
1 至
N
N
N 的
N
N
N 台服务器和编号为
1
1
1 至
M
M
M 的
M
M
M 条电缆。
电缆
i
i
i 双向连接服务器
A
i
A_i
Ai 和
B
i
B_i
Bi 。
执行以下操作若干次(可能为零),使所有服务器通过电缆连接。
- 操作:选择一条电缆,将其一端重新连接到不同的服务器。
找出所需的最少操作次数,并输出实现该最少操作次数的操作序列。
Constraints
- 2 ≤ N ≤ 2 × 1 0 5 2 \leq N \leq 2\times 10^5 2≤N≤2×105
- N − 1 ≤ M ≤ 2 × 1 0 5 N-1 \leq M \leq 2\times 10^5 N−1≤M≤2×105
- 1 ≤ A i , B i ≤ N 1 \leq A_i, B_i \leq N 1≤Ai,Bi≤N
- 所有输入值均为整数。
Output
假设最少操作次数为 K K K 。打印 K + 1 K+1 K+1 行。
- 第一行应包含 K K K 。
- 第 ( i + 1 ) (i+1) (i+1) -th行应包含三个空格分隔的整数:在第 i i i -th操作中选择的电缆的编号、最初连接到该端的服务器编号,以及操作后连接到的服务器编号,按此顺序排列。
如果有多个有效解决方案,则接受其中任何一个。
Solution
由 N − 1 ≤ M N-1 \leq M N−1≤M 可知必然可以修改后成为一个连通块。于是检查每条边,如果在一个连通块内即为多余边,标记下来 st[i] 并记录 a[i] = x,否则将其连起来
需要修改的电缆数为 初始连通块块数 - 1
有N台服务器(节点),M条电缆(边),循环中第 i 条不是多余边就跳过,每次找到不在多余边所在连通块的服务器节点 k ,输出 编号 i 、原连接端 a[i] 、新连接端 k
当 k > m 或 k > n 时退出循环
Code
#include <bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false), cin.tie(0);
const int N=2e5+10;
int a[N], p[N];
bool st[N];//标记多余边
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
int main()
{
IOS
int n, m;
cin>>n>>m;
for(int i=1; i<=n; i++) p[i]=i;
for(int i=1; i<=m; i++)
{
int x, y;
cin>>x>>y;
if(find(x)==find(y)) st[i]=true, a[i]=x;//第i条边是多余的,最初连接到x上
else p[find(x)]=find(y);
}
int cnt=0;//连通块个数
for(int i=1; i<=n; i++)
if(p[i]==i) cnt++;
cout<<cnt-1<<'\n';
int i, k;
for(i=k=1; i<=m; i++)
{
if(!st[i]) continue;//没多余边,跳过
while(k<=n && find(k)==find(a[i])) k++;
if(k>n) break;
cout<<i<<" "<<a[i]<<" "<<k<<'\n';
p[find(k)]=find(a[i]);
}
return 0;
}
F - Insert
Algorithm
线段树分治
时间复杂度
O
(
n
log
n
)
O(n\log n)
O(nlogn)
Problom Statement
有一个空数组 A A A 。对于 i = 1 , 2 , … , N i = 1,2,\ldots,N i=1,2,…,N ,依次进行以下操作:
- 将数字
i
i
i 插入
A
A
A ,使其成为从头开始的
P
i
P_i
Pi -th 元素。
- 更准确地说,把 A A A 替换为 A A A 的前 P i − 1 P_{i-1} Pi−1 个元素的连接,然后是 i i i ,接着是 A A A 的其余元素,从 P i P_i Pi -th元素开始,依次替换。
完成所有操作后,输出最终数组 A A A 。
Constraints
- 1 ≤ N ≤ 5 × 1 0 5 1 \leq N \leq 5\times 10^5 1≤N≤5×105
- 1 ≤ P i ≤ i 1 \leq P_i \leq i 1≤Pi≤i
- 所有输入值均为整数。
Sample Input
4
1 1 2 1
Sample Output
4 2 3 1
操作步骤如下
- 插入数字 1 1 1 使其成为 A A A 的第 1 个元素。现在 A = ( 1 ) A = (1) A=(1) 。
- 插入数字 2 2 2 使其成为 A A A 的第 1 个元素。现在 A = ( 2 , 1 ) A = (2, 1) A=(2,1) 。
- 插入数字 3 3 3 使之成为 A A A 的第 2 个元素。现在 A = ( 2 , 3 , 1 ) A = (2, 3, 1) A=(2,3,1) 。
- 插入数字 4 4 4 使之成为 A A A 的第 1 个元素。现在 A = ( 4 , 2 , 3 , 1 ) A = (4, 2, 3, 1) A=(4,2,3,1) 。
Solution
将所有操作倒序进行。每一个操作即为插入目前编号第
a
i
a_i
ai 小的空位
binary函数实现了 query 和 modify 两种功能,先判断左子树的空位数
s
u
m
sum
sum 是否足够,然后分治精确查找放置位置
l
l
l ,将叶子节点的
s
u
m
sum
sum 修改为1 并一路返回答案
l
l
l
数组
b
[
i
]
b[i]
b[i] 表示数字
i
i
i 被填到位置
b
[
i
]
b[i]
b[i] 再用数组
c
c
c 表示每个位置应该填什么数
Code
#include <bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false), cin.tie(0);
typedef long long LL;
typedef pair<int, int> PII;
const int N=5e5+10;
int a[N], b[N], c[N];
struct Node
{
int l, r;
int sum;
}tr[4*N];
void pushup(int u)
{
tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}
void build(int u, int l, int r)//u--节点编号 l--左边界 r--右边界
{
if(l==r) tr[u]={l, r, 1};
else
{
tr[u]={l, r};
int mid=l+r>>1;
build(u<<1, l, mid), build(u<<1|1, mid+1, r);
pushup(u);
}
}
int binary(int u, int x)
{
if(tr[u].l==tr[u].r)
{
tr[u].sum=0;
return tr[u].l;
}
int res=0;
if(tr[u<<1].sum>=x) res=binary(u<<1, x);
else res=binary(u<<1|1, x-tr[u<<1].sum);
pushup(u);
return res;
}
int main()
{
IOS
int n;
cin>>n;
build(1, 1, n);
for(int i=1; i<=n; i++) cin>>a[i];
//将所有操作倒序进行。每一个操作即为插入目前编号第ai小的空位
for(int i=n; i>=1; i--)
{
int t=binary(1, a[i]);
//cout<<t<<" ";
b[i]=t;//数字i被填到位置t
}
for(int i=1; i<=n; i++) c[b[i]]=i;//b[i]位置的数是i
cout<<c[1];
for(int i=2; i<=n; i++) cout<<" "<<c[i];
return 0;
}
abc 393
E - GCD of Subset
Algorithm
桶思想,枚举
时间复杂度
O
(
m
log
m
)
O(m \log m)
O(mlogm),
m
m
m 为
A
i
A_i
Ai 的最大值
Problom Statement
给你一个长度为
N
N
N 的序列
A
=
(
A
1
,
A
2
,
…
,
A
N
)
A = (A_1, A_2, \dots, A_N)
A=(A1,A2,…,AN) 和一个正整数
K
K
K (最多为
N
N
N )。
针对每个
i
=
1
,
2
,
…
,
N
i = 1, 2, \dots, N
i=1,2,…,N 求解下面的问题:
- 从 A A A 中选择包含 A i A_i Ai 的 K K K 个元素,求所选元素的最大公约数 (GCD)。
Constraints
- 1 ≤ K ≤ N ≤ 1.2 × 1 0 6 1 \leq K \leq N \leq 1.2 \times 10^6 1≤K≤N≤1.2×106
- 1 ≤ A i ≤ 1 0 6 1 \leq A_i \leq 10^6 1≤Ai≤106
- 所有输入值均为整数。
Solution
先找到序列中元素最大值,
s
[
i
]
s[i]
s[i] 表示
i
i
i 在序列中出现次数,
c
[
i
]
c[i]
c[i] 表示
i
i
i 的倍数在序列中出现次数
a
n
s
[
i
]
ans[i]
ans[i] 即所选元素为
i
i
i 的答案
枚举并更新值域内的所有答案,
O
(
1
)
O(1)
O(1) 即可查询输出
Code
#include <bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false), cin.tie(0);
int main()
{
IOS
int n, k;
cin>>n>>k;
vector<int> a(n);
for(auto &it: a) cin>>it;
int m=*max_element(a.begin(), a.end());
vector<int> s(m+1), c(m+1), ans(m+1);//s[]--桶 c[i]--i的倍数在序列中的次数
for(auto it:a) s[it]++;
for(int d=1; d<=m; d++)
for(int j=d; j<=m; j+=d)//mlogm的时间复杂度 调和级数可证
c[d]+=s[j];
for(int d=1; d<=m; d++)
{
if(c[d]<k) continue;
for(int j=d; j<=m; j+=d)
ans[j]=max(ans[j], d);
}
for(auto &it:a) cout<<ans[it]<<"\n";
return 0;
}
F - Prefix LIS Query
Algorithm
二分优化求 LIS
时间复杂度
O
(
(
n
+
q
)
log
n
)
O((n+q)\log n)
O((n+q)logn)
Problom Statement
给你一个长度为 N N N 的序列 A = ( A 1 , A 2 , … , A N ) A = (A_1, A_2, \dots, A_N) A=(A1,A2,…,AN) 。
请回答 Q Q Q 个查询。 i i i -th 查询( 1 ≤ i ≤ Q 1 \leq i \leq Q 1≤i≤Q )如下:
- 给定整数 R i R_i Ri 和 X i X_i Xi 。考虑 ( A 1 , A 2 , … , A R i ) (A_1, A_2, \dots, A_{R_i}) (A1,A2,…,ARi) 的一个子序列(不一定连续),它是严格递增的,并且只由最多为 X i X_i Xi 的元素组成。求这样一条子序列的最大可能长度。保证为 X i ≥ min { A 1 , A 2 , … , A R i } X_i \geq \min\lbrace A _1, A_2,\dots,A_{R_i} \rbrace Xi≥min{A1,A2,…,ARi}
Constraints
- 1 ≤ N , Q ≤ 2 × 1 0 5 1 \leq N,Q \leq 2 \times 10^5 1≤N,Q≤2×105
- 1 ≤ A i ≤ 1 0 9 1 \leq A_i \leq 10^9 1≤Ai≤109
- 1 ≤ R i ≤ N 1 \leq R_i \leq N 1≤Ri≤N
- min { A 1 , A 2 , … , A R i } ≤ X i ≤ 1 0 9 \min\lbrace A_1, A_2,\dots,A_{R_i} \rbrace\leq X_i\leq 10^9 min{A1,A2,…,ARi}≤Xi≤109
- 所有输入值均为整数。
Solution
输入所有数据,将查询存储在二维数组 qs 中
第一维存储查询的右端点(.first 是输入时的查询下标,.second 是 LIS 限制的最大值)
DP求LIS的同时 更新 qs中 查询右端点的答案,其中 upper_bound() - begin() 来计算 LIS 中小于等于 x 的元素个数
Code
#include <bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false), cin.tie(0);
typedef long long LL;
typedef pair<int, int> PII;
typedef vector<int> vi;
const int inf=1e9+10;
int main()
{
IOS
int n, q;
cin>>n>>q;
vi a(n);
for(auto &it: a) cin>>it;
vector<vector<PII>> qs(n);
for(int i=0; i<q; i++)
{
int r, x;
cin>>r>>x;
qs[--r].emplace_back(i, x);//下标从0开始 .first--查询下标 .second--最大值x
}
vi ans(q), dp(n, inf);//dp--最长上升子序列
for(int i=0; i<n; i++)
{
auto t=lower_bound(dp.begin(), dp.end(), a[i]);
*t=a[i];
for(auto it:qs[i])
ans[it.first]=upper_bound(dp.begin(), dp.end(), it.second)-dp.begin();
}
for(int i=0; i<q; i++)
cout<<ans[i]<<'\n';
return 0;
}
abc 394
E - Palindromic Shortest Path
Algorithm
BFS,回文
时间复杂度
O
(
n
4
)
O(n^4)
O(n4)
Problom Statement
我们有一个有向图,图中有 N N N 个顶点,编号为 1 , 2 , … , N 1, 2, \ldots, N 1,2,…,N 。
关于边的信息由
N
2
N^2
N2 字符
C
1
,
1
,
C
1
,
2
,
…
,
C
1
,
N
,
C
2
,
1
,
…
,
C
N
,
N
C_{1, 1}, C_{1, 2}, \ldots, C_{1, N}, C_{2, 1}, \ldots, C_{N, N}
C1,1,C1,2,…,C1,N,C2,1,…,CN,N 提供。这里,每个
C
i
,
j
C_{i, j}
Ci,j 都是小写英文字母或 -
。
如果
C
i
,
j
C_{i, j}
Ci,j 是小写英文字母,那么从顶点
i
i
i 到顶点
j
j
j 正好有一条标记为
C
i
,
j
C_{i, j}
Ci,j 的有向边。如果
C
i
,
j
C_{i, j}
Ci,j 是 -
,那么从顶点
i
i
i 到顶点
j
j
j 就没有边。
对于 ( i , j ) (i, j) (i,j) 与 1 ≤ i , j ≤ N 1 \leq i, j \leq N 1≤i,j≤N 的每一对整数,请回答下面的问题:
- 在从顶点 i i i 到顶点 j j j 的所有(不一定是简单的)路径中,如果各条路径边上的标签连起来构成一个回文,那么最短路径的长度是多少?如果没有这样的路径,答案为 − 1 -1 −1 。
Tips:空串也是回文串
Constraints
- 1 ≤ N ≤ 100 1 \leq N \leq 100 1≤N≤100
- N N N 是整数。
- 每个
C
i
,
j
C_{i, j}
Ci,j 都是小写英文字母或
-
。
Solution
i
=
j
i = j
i=j 最短路径长度为
0
0
0,进队
遍历全图,不是
′
−
′
'-'
′−′ 的从 i 到 j 最短路径长度为
1
1
1 ,单字符构成回文,进队
宽搜,每次取出的队头都是队伍中最短的回文串,尝试找从 k 到 i 和从 j 到 l 的最短路径,
a
[
k
]
[
i
]
=
=
a
[
j
]
[
l
]
a[k][i]==a[j][l]
a[k][i]==a[j][l] 表示原回文串两端加上相同字符,另外 k 到 l 的最短路径还未找到(inf)
最后输出答案
Code
#include <bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false), cin.tie(0);
#define rep(i, n) for (int i = 0; i < (n); i++)
typedef pair<int, int> PII;
const int N=110, inf=1e9;
string g[N];
int main()
{
IOS
int n;
cin>>n;
vector<vector<int>> a(n, vector<int>(n, inf));
queue<PII> q;
rep(i, n) cin>>g[i];
rep(i, n) a[i][i]=0, q.push({i, i});
rep(i, n)
rep(j, n)
{
if(i!=j && g[i][j]!='-') a[i][j]=1, q.push({i, j});
}
while(!q.empty())
{
auto t=q.front(); q.pop();
int i=t.first, j=t.second;
rep(k, n)
rep(l, n)
{
if(g[k][i]!='-' && g[j][l]!='-' && g[k][i]==g[j][l] && a[k][l]==inf)
a[k][l]=a[i][j]+2, q.push({k, l});
}
}
rep(i, n)
rep(j, n)
cout<< (a[i][j]==inf? -1: a[i][j]) << " \n"[j==n-1];
return 0;
}
F - Alkane
Algorithm
搜索 (DFS),DP
时间复杂度
O
(
n
2
)
O(n^2)
O(n2)
Problom Statement
给你一棵无向树 T T T ,有 N N N 个顶点,编号为 1 , 2 , … , N 1, 2, \ldots, N 1,2,…,N 。第 i i i 条边是连接顶点 A i A_i Ai 和 B i B_i Bi 的无向边。
当且仅当一个图满足以下条件时,它才被定义为烷烃:
- 图是一棵不定向树
- 每个顶点的度数为 1 1 1 或 4 4 4 ,且至少有一个顶点的度数为 4 4 4 。
请判断 T T T 是否存在一个烷的子图,如果存在,请找出该子图的最大顶点数。
Constraints
- 1 ≤ N ≤ 2 × 1 0 5 1 \leq N \leq 2 \times 10^5 1≤N≤2×105
- 1 ≤ A i , B i ≤ N 1 \leq A_i, B_i \leq N 1≤Ai,Bi≤N
- 给定图形是一棵无向树。
- 所有输入值均为整数。
Solution
从编号
1
1
1 的节点开始 dfs,dp[i] 表示以
i
i
i 为根的满足要求的子树顶点个数
每层dfs进入后先预置
d
p
[
u
]
=
1
dp[u] = 1
dp[u]=1 表示子树中已有一个顶点(本身)
优先队列(大根堆) pq 存储节点
u
u
u 的每个子树的顶点个数
深搜到叶节点后返回
只有 pq 中元素大于等于 3 个时才会更新
d
p
[
u
]
dp[u]
dp[u] ,不是 4 个的原因是还有一个点是父节点(因为是回溯时更新
d
p
[
u
]
dp[u]
dp[u] , 返回下一层才是父节点)
取完最大的三个元素,如果队列中还有子树,那加上那个即可构成一个完整的烷烃,故有ans=max(ans, sum+pq.top()+1);//+1是本身,sum是另外三个子图
由于if(pq.size() && pq.top()>1) ans=max(ans, pq.top()+1);//至少存在一个度为4的 这里
p
q
.
t
o
p
(
)
>
1
pq.top()>1
pq.top()>1 其最小是 3 ,从而保证了答案的正确性
Code
#include <bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false), cin.tie(0);
#define rep(i, n) for (int i = 0; i < (n); i++)
typedef long long LL;
typedef pair<int, int> PII;
typedef vector<int> vi;
const int N=2e5+10;
int n, dp[N], ans;
vi g[N];
void dfs(int u, int fa)
{
dp[u]=1;
priority_queue<int> pq;
for(auto v: g[u])
{
if(v!=fa)//防止回溯
{
dfs(v, u);
pq.push(dp[v]);
}
}
if(pq.size() && pq.top()>1) ans=max(ans, pq.top()+1);//至少存在一个度为4的
int cnt=0, sum=0;
while(pq.size())
{
cnt++;
sum+=pq.top();
pq.pop();
if(cnt==3) break;
}
if(cnt==3) dp[u]=sum+1;//此时的顶点数一定是合法的
if(cnt==3 && pq.size()) ans=max(ans, sum+pq.top()+1);//+1是本身,sum是另外三个子图
}
int main()
{
IOS
cin>>n;
rep(i, n-1)
{
int u, v;
cin>>u>>v;
g[u].push_back(v), g[v].push_back(u);
}
dfs(1, 0);
if(ans==0) ans=-1;
cout<<ans;
return 0;
}
Sample Input
9
1 2
2 3
3 4
4 5
2 6
2 7
3 8
3 9
Sample Output
8
设
(
u
,
v
)
(u, v)
(u,v) 表示顶点
u
u
u 和
v
v
v 之间的无向边。
则由顶点
1
,
2
,
3
,
4
,
6
,
7
,
8
,
9
1,2,3,4,6,7,8,9
1,2,3,4,6,7,8,9 和边
(
1
,
2
)
,
(
2
,
3
)
,
(
3
,
4
)
,
(
2
,
6
)
,
(
2
,
7
)
,
(
3
,
8
)
,
(
3
,
9
)
(1,2),(2,3),(3,4),(2,6),(2,7),(3,8),(3,9)
(1,2),(2,3),(3,4),(2,6),(2,7),(3,8),(3,9) 组成的子图是一个烷烃。
感谢阅读!如果你对我的解题思路或代码有任何疑问,欢迎在评论区留言交流。本文为原创内容,未经授权不得转载。