2025基础算法集(abc好题周更ing

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 1iN )位于时间 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 1jQ ),请回答在 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 1N2×105
  • 1 ≤ W ≤ N 1 \leq W \leq N 1WN
  • 1 ≤ X i ≤ W 1 \leq X_i \leq W 1XiW
  • 1 ≤ Y i ≤ 1 0 9 1 \leq Y_i \leq 10^9 1Yi109
  • ( 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 1Q2×105
  • 1 ≤ T j ≤ 1 0 9 1 \leq T_j \leq 10^9 1Tj109
  • 1 ≤ A j ≤ N 1 \leq A_j \leq N 1AjN
  • 所有输入值均为整数。
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 minvmaxv 之间的方块一定不会被消除, 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 n1 ) 的二进制字符串 B = B 1 B 2 … B 3 n B = B_1 B_2 \dots B_{3^n} B=B1B2B3n ,我们定义了如下操作,以获得长度为 3 n − 1 3^{n-1} 3n1 的二进制字符串 C = C 1 C 2 … C 3 n − 1 C = C_1 C_2 \dots C_{3^{n-1}} C=C1C2C3n1

  • B B B 中的元素分成 3 3 3 组,并从每组中取多数值。也就是说,对于 i = 1 , 2 , … , 3 n − 1 i=1,2,\dots,3^{n-1} i=1,2,,3n1 ,让 C i C_i Ci 成为 B 3 i − 2 B_{3i-2} B3i2 B 3 i − 1 B_{3i-1} B3i1 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=A1A2A3N 。设 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 1N13 的整数。
  • 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 j3+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 1i,j,kN ),计算值 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 1N2×105
  • 1 ≤ K ≤ min ⁡ ( N 3 , 5 × 1 0 5 ) 1\leq K \leq \min(N^3,5\times 10^5) 1Kmin(N3,5×105)
  • 1 ≤ A i , B i , C i ≤ 1 0 9 1\leq A_i,B_i,C_i \leq 10^9 1Ai,Bi,Ci109
  • 所有输入值均为整数。
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(5e52020)这样子
枚举后用 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 2N2×105
  • N − 1 ≤ M ≤ 2 × 1 0 5 N-1 \leq M \leq 2\times 10^5 N1M2×105
  • 1 ≤ A i , B i ≤ N 1 \leq A_i, B_i \leq N 1Ai,BiN
  • 所有输入值均为整数。
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 N1M 可知必然可以修改后成为一个连通块。于是检查每条边,如果在一个连通块内即为多余边,标记下来 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} Pi1 个元素的连接,然后是 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 1N5×105
  • 1 ≤ P i ≤ i 1 \leq P_i \leq i 1Pii
  • 所有输入值均为整数。
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 1KN1.2×106
  • 1 ≤ A i ≤ 1 0 6 1 \leq A_i \leq 10^6 1Ai106
  • 所有输入值均为整数。
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 1iQ )如下:

  • 给定整数 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 Ximin{A1,A2,,ARi}
Constraints
  • 1 ≤ N , Q ≤ 2 × 1 0 5 1 \leq N,Q \leq 2 \times 10^5 1N,Q2×105
  • 1 ≤ A i ≤ 1 0 9 1 \leq A_i \leq 10^9 1Ai109
  • 1 ≤ R i ≤ N 1 \leq R_i \leq N 1RiN
  • 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}Xi109
  • 所有输入值均为整数。
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 1i,jN 的每一对整数,请回答下面的问题:

  • 在从顶点 i i i 到顶点 j j j 的所有(不一定是简单的)路径中,如果各条路径边上的标签连起来构成一个回文,那么最短路径的长度是多少?如果没有这样的路径,答案为 − 1 -1 1

Tips:空串也是回文串

Constraints
  • 1 ≤ N ≤ 100 1 \leq N \leq 100 1N100
  • 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 1N2×105
  • 1 ≤ A i , B i ≤ N 1 \leq A_i, B_i \leq N 1Ai,BiN
  • 给定图形是一棵无向树。
  • 所有输入值均为整数。
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) 组成的子图是一个烷烃。


感谢阅读!如果你对我的解题思路或代码有任何疑问,欢迎在评论区留言交流。本文为原创内容,未经授权不得转载。

未完待续……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值