c语言算法

文章详细解释了二分查找、一次和二次计算前缀和以及使用差分方法在数组操作中的实现,包括构造新数组、插入操作和最终结果计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

动态规划

背包问题

01背包问题

思路

动态规划:原问题分解为相对简单的子问题的方式求解复杂问题的方法
1状态表示(集合,属性)
2状态转移方程(状态计算,集合划分过程)
3边界确定

i:前i个物品,j:背包目前总体积。所有从前i个物品中选,且总体积不超或j的选法(集合)
f[i][j]:总价值

1.不放入第i个物品:f[i][j]=f[i-1][j]
2.放入第i个物品:f[i][j]=f[i-1][j-v[i]]+V[i]
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+V[i])   

边界:j>=v[i]
i=1;i<=N;i++
j=0;j<=V;j++


if(j>=v[i])
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+V[i]);
else f[i][j]=f[i-1][j];
或
f[i][j]=f[i-1][j];
if(j>=v[i])
f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);



int res=0;
for(int i=0;i<=V;i++)
res=max(res,f[N][i]);
printf("%d\n",res);
或
printf("%d\n",f[N][V]);

滚动数组优化:

让j从大到小遍历 

完整代码
//暴力
#include<stdio.h>

int N,V;
const int n=1010;
int v[n],w[n];
int f[n][n];

int max(int a,int b)
{
    if(a>=b)
    return a;
    else return b;
}

int main()
{
    int max(int a,int b);
    scanf("%d %d",&N,&V);
    
    for(int i=1;i<=N;i++)
    {
        scanf("%d %d",&v[i],&w[i]);
    }
    
    int res=0;
    
    for(int i=1;i<=N;i++)
        for(int j=0;j<=V;j++)
        {
            
 //           if(j>=v[i])
   //          f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
     //        else f[i][j]=f[i-1][j];
            f[i][j]=f[i-1][j];
            if(j>=v[i])
            f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
        }
        
//   for(int i=0;i<=V;i++)
//   res=max(res,f[N][i]);
   
//   printf("%d\n",res);
    printf("%d\n",f[N][V]);
   return 0;
    
}
//优化

#include<stdio.h>

int N,V;
const int n=1010;
int f[n];
int v[n],w[n];

int max(int a,int b)
{
    if(a>=b)
    return a;
    else return b;
}

int main()
{
    scanf("%d %d",&N,&V);
    for(int i=1;i<=N;i++)
    {
        scanf("%d %d",&v[i],&w[i]);
    }
    for(int i=1;i<=N;i++)
    for(int j=V;j>0;j--)
    {
        
        if(j>=v[i])
        f[j]=max(f[j],f[j-v[i]]+w[i]);
    }
    // int res=0;
    // for(int j=0;j<=V;j++)
    // res=max(res,f[j]);
    // printf("%d\n",res);
    printf("%d\n",f[V]);
    return 0;
}

完全背包问题

思路
完全背包:每个物品可放入多次

第i个不放入:f[i][j]=f[i-1][j]
第i个放入1次:f[i][j]=f[i-1][j-v[i]]+w[i]
第i个放入2次:f[i][j]=f[i-1][j-2*v[i]]+2*w[i]
……
第i个放入k次:f[i][j]=f[i-1][j-k*v[i]]+k*w[i]

f[i][j]     =max(f[i-1][j],     f[i-1][j-v[i]]+w[i],  f[i-1][j-2*v[i]]+2*w[i],……,f[i-1][j-k*v[i]]+k*w[i])
f[i][j-v[i]]=max(f[i-1][j-v[i]],f[i-1][j-2*v[i]]+w[i],f[i-1][j-3*v[i]]+2*w[i],……,f[i][j]=f[i-1][j-(k+1)*v[i]]+k*w[i])
so
f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i])

优化:
j=v[i];j<=V;j++ //边界:j>=v[i]
f[j]=max(f[j],f[j-v[i]]+w[i])

01背包:  f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i])   //优化从大到小
完全背包:f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i])     //优化从小到大
完整代码
//暴力
#include<stdio.h>

int N,V;
const int n=1010;
int v[n],w[n];
int f[n][n];

int max(int a,int b)
{
    if(a>=b)
    return a;
    else return b;
}

int main()
{
    scanf("%d %d",&N,&V);
    for(int i=1;i<=N;i++)
    scanf("%d %d",&v[i],&w[i]);
    for(int i=1;i<=N;i++)
        for(int j=0;j<=V;j++)
        {
            f[i][j]=f[i-1][j];
            if(j>=v[i]) f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
            // if(j>=v[i]) f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i]); //
            // else  f[i][j]=f[i-1][j];
        }
    printf("%d\n",f[N][V]);
    return 0;
}
//优化
#include<stdio.h>

int N,V;
const int n=1010;
int v[n],w[n];
int f[n];

int max(int a,int b)
{
    if(a>=b) return a;
    else return b;
}

int main()
{
    scanf("%d %d",&N,&V);
    for(int i=1;i<=N;i++) scanf("%d %d",&v[i],&w[i]);
    for(int i=1;i<=N;i++)
        for(int j=v[i];j<=V;j++) f[j]=max(f[j],f[j-v[i]]+w[i]);
    printf("%d\n",f[V]);
    return 0;
}

多重背包问题

思路
多重背包:第i个物品最多用s[i]个
01背包的扩展

状态表示:第i个物品可放入0,1,2...s[i]个
k:0-s[i]
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i],f[i-1][j-2*v[i]]+2*w[i]...f[i-1][j-k*v[i]]+k*w[i])
k从0开始循环,f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i])

优化
j从大到小,因为上式都是f[i-1]
f[j]=max(f[j],f[j-k*v[i]]+k*w[i])

边界:k=0;k<=s[i]&&k*v[i]<=j;k++
完整代码
//暴力
#include<stdio.h>

int N,V;
const int n=110;
int v[n],w[n],s[n];
int f[n][n];

int max(int a,int b)
{
    if(a>=b) return a;
    else return b;
}

int main()
{
    scanf("%d %d",&N,&V);
    for(int i=1;i<=N;i++) scanf("%d %d %d",&v[i],&w[i],&s[i]);
    for(int i=1;i<=N;i++)
        for(int j=0;j<=V;j++)
            for(int k=0;k<=s[i]&&k*v[i]<=j;k++)
           {
               
            // f[i][j]=f[i-1][j];
            
               f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
           }
    printf("%d\n",f[N][V]);
    return 0;
}
//优化
#include<stdio.h>

const int n=110;
int N,V;
int v[n],w[n],s[n];
int f[n];

int max(int a,int b)
{
    if(a>=b) return a;
    else return b;
}

int main()
{
    scanf("%d %d",&N,&V);
    for(int i=1;i<=N;i++) scanf("%d %d %d",&v[i],&w[i],&s[i]);
    for(int i=1;i<=N;i++)
        for(int j=V;j>=0;j--)
            for(int k=0;k<=s[i]&&k*v[i]<=j;k++)
            f[j]=max(f[j],f[j-k*v[i]]+k*w[i]);
    printf("%d\n",f[V]);
    return 0;
}
二进制优化思路

这二进制优化把人看傻了,发明二进制的人是个天才

任何数都能用二进制表示
8:1,2,4                        8  >=2^3(8)   取2^2(4) ,c(2)
10:1,2,4                       10 >=2^3(8)   取2^2(4) ,c(4)
200:1,2,4,8,16,32,64,73       200>=2^7(128) 取2^6(68),c(73)

将s个第i个物品 拆分为 n组(二进制表示)个第i个物品,将前n-1个i物品 变为第i个物品之前的单个物品
所以就转化为01背包问题

        while(k<=s)
        {
            cnt++;
            v[cnt]=a*k;
            w[cnt]=b*k;
            s=s-k;
            k=k*2;
        }
        if(s>0)
        {
            cnt++;
            v[cnt]=a*s;
            w[cnt]=b*s;
        }
优化代码
#include<stdio.h>

int N,V;
const int n1=25000,n2=2010;
int v[n1],w[n1];
int f[n2];

int max(int a,int b)
{
    if(a>=b) return a;
    else return b;
}

int main()
{
    int cnt=0;
    scanf("%d %d",&N,&V);
    for(int i=1;i<=N;i++)
    {
        int a,b,s;
        scanf("%d %d %d",&a,&b,&s);
        int k=1;
        
        while(k<=s)
        {
            cnt++;
            v[cnt]=a*k;
            w[cnt]=b*k;
            s=s-k;
            k=k*2;
        }
        if(s>0)
        {
            cnt++;
            v[cnt]=a*s;
            w[cnt]=b*s;
        }
    }
    
    for(int i=1;i<=cnt;i++)
        for(int j=V;j>=v[i];j--) f[j]=max(f[j],f[j-v[i]]+w[i]);
    printf("%d",f[V]);
    return 0;
}

分组背包问题

思路
分组背包:有N组物品,每组只能选一个

状态计算:
第i组选第一个:f[i][j]=f[i-1][j]
第i组选第k个:f[i][j]=f[i-1][j-v[i,k]]+w[i,k]

s[i]:每组个数(表示个数,注意边界)
完整代码
#include<stdio.h>

int N,V;
const int n=110;
int s[n];
int v[n][n],w[n][n];
int f[n];

int max(int a,int b)
{
    if(a>=b) return a;
    else return b;
}

int main()
{
    scanf("%d %d",&N,&V);
    for(int i=1;i<=N;i++) 
    {
        scanf("%d",&s[i]);
        for(int j=0;j<s[i];j++) scanf("%d %d",&v[i][j],&w[i][j]);
    }
    for(int i=1;i<=N;i++)
        for(int j=V;j>=0;j--)
            for(int k=0;k<s[i];k++) 
            {
                if(j>=v[i][k])
                f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
            }
    printf("%d\n",f[V]);
    return 0;
}

线性DP

数字三角形

最长上升子序列

 思路
基础版
f[i]:以第i个数结尾的子序列的集合,f[i]表示子序列的长度
f[i]=f[i-1]+1

i=1;i<=N;i++
f[i]=1
j=1;j<=i;j++
if(a[j]<a[i])
f[i]=max(f[i],f[j]+1)


优化版
优化版得先看一遍二分,整数二分边界问题真受够了

子序列长度从1开始,长度为1,长度为2...每个长度下以数字i结尾的子序列,只存i最小的子序列,存在q[i]中
//因为如果一个数能排在3后面,就一定能排在1后面。当长度一定时,子序列结尾的数字 ,只要记录一个数字最小的子序列
a[i]:数字
遍历a[i],二分法找a[i]可以跟在哪个数字(q[i])后面
int len=0,l=0,r=len;
if(q[mid]<a[i]) l=mid //答案在左边<a[i]的部分,但是不包含a[i]
else r=mid+1
len=max(len,r+1)  //r是二分答案,是最大<a[i]的数,a[i]应该接在r后面,a[i]一旦接在r后面,子序列长度+1,以a[i]为结尾的最长子序列应属于r+1,len是右边界

完整代码
//基础版
#include<stdio.h>

int N;
const int n=1010;
int a[n],f[n];

int max(int a,int b)
{
    if(a>=b) return a;
    else return b;
}

int main()
{
    scanf("%d",&N);
    for(int i=1;i<=N;i++) scanf("%d",&a[i]);
    for(int i=1;i<=N;i++)
    {
        f[i]=1;
        for(int j=1;j<i;j++)
        {
            if(a[j]<a[i])
            f[i]=max(f[i],f[j]+1);  //卧草好聪明的办法
        }
    }
        
            
    int res=0;
    for(int i=1;i<=N;i++)
    res=max(res,f[i]);
    printf("%d\n",res);
    return 0;
}

//优化版
#include<stdio.h>

int N;
const int n=100010;
int a[n],q[n];

int max(int a,int b)
{
    if(a>=b) return a;
    else return b;
}

int main()
{
    scanf("%d",&N);
    for(int i=0;i<N;i++) scanf("%d",&a[i]);  //二分法只能从0开始循环
    for(int i=0;i<N;i++) q[i]=a[i];
    int len=0;
	q[0]=-2e9;  //先给q[0]赋值,第一次for只会用到q[0],
    for(int i=0;i<N;i++)
    {
        int l=0,r=len;
        
        while(r-l>0)
        {
            int mid=(l+r+1)/2;
            if(q[mid]<a[i]) l=mid;    //第一次循环q[mid]=q[0],已经赋值过了
            else r=mid-1;
        }
        len=max(len,r+1);
        q[r+1]=a[i];  //这一步就相当于给q[]赋值了
    }
    printf("%d\n",len);
    return 0;
}

最长公共子序列

思路
状态表示
f[i][j]:第一个序列a[]前i个数字的子序列、第二个子序列b[]前j个数字的子序列、构成的所有公共子序列(集合)的长度最大值

状态计算
用01表示最长公共子序列是否包含a[i]、b[j]
00:f[i][j]=f[i-1][j-1]
01:f[i-1][j]
//01:子序列不包含a[i],包含b[j]
//f[i-1][j]:a[]中前i-1个数字的子序列,b[]中前j个数字的子序列,构成的公共子序列(集合)的长度最大值
/*所以f[i-1][j]一定不包含a[i],有包含b[j]也有不包含b[j]
  所以f[i-1][j]>01*/
//max(a,b,c)=max(max(a,b),max(b,c))
10:f[i][j-1]  //同理
11:f[i-1][j-1]+1
//+1而不是+2,因为只有a[i]==b[j]才会出现11

i=1;i<=N
j=1;j<=M
{
f[i][j]=max(f[i][j-1],f[i-1][j])
if(a[i]==b[j]) f[i][j]=max(f[i][j],f[i-1][j-1]+1)

!!!注意
scanf("%c",&a[i])会读取空格,换行符
只能用scanf("%s",a+1)
完整代码
#include<stdio.h>

int M,N;
const int n=1010;
char a[n],b[n];
int f[n][n];

int max(int a,int b)
{
    if(a>=b) return a;
    else return b;
}

int main()
{
    scanf("%d %d",&N,&M);
    // for(int i=1;i<=n;i++) scanf("%c",&a[i]);
    // for(int i=1;i<=M;i++) scanf("%c",&b[i]);
    // scanf%c会在每次读取字符时保留换行符或空白字符,应该直接读取整个字符串,而不是逐个字符读取
    scanf("%s",a+1);
    scanf("%s",b+1);
    
    for(int i=1;i<=N;i++)
    {
        for(int j=1;j<=M;j++)
        {
            f[i][j]=max(f[i-1][j],f[i][j-1]);
            if(a[i]==b[j])
            f[i][j]=max(f[i][j],f[i-1][j-1]+1);
        }
    }
    printf("%d\n",f[N][M]);
    return 0;
}

最短编辑距离

思路
 
状态表示:集合:方法。把A[i]序列变为B[j]序列需要的所有方法的集合
状态计算:1.删:f[i-1][j]+1
        2.增:f[i][j-1]+1
        3.改:f[i-1][j-1]+1

初始化
for (int i = 0; i <= n; i++) f[i][0] = i;
for (int i = 0; i <= n; i++) f[0][i] = i;
3.if(a[i] == b[i]) f[i][j] = min(f[i][j], f[i - 1][j - 1]);
  else f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);
完整代码
#include<stdio.h>

const int N = 1010;  //这行必须放在char A[N]前,,我记得以前不用啊
int n, m;
char A[N], B[N];  //注意char类型
int f[N][N];

int min(int a, int b)
{
    if(a <= b) return a;
    else return b;
}

int main()
{
    scanf("%d %s", &n, A + 1);
    scanf("%d %s", &m, B + 1);
    for (int i = 0; i <= n; i++) f[i][0] = i;
    for (int j = 0; j <= m; j++) f[0][j] = j;
    for (int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
        {
            f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1);
            if(A[i] == B[j]) f[i][j] = min(f[i][j], f[i - 1][j - 1]);
            else f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);
        }
    printf("%d\n", f[n][m]);
    return 0;
}

基础算法

快速排序

y总说二分法的边界让人蛋疼,我觉得快排也是,所有的边界问题都很蛋疼

思路

快排用的是分治的思想
问题分解,解决,合并

数组放入q[n]中,边界随意
分解:
设x等于任意数最好是中间值 int x = q[(a + b)/2]
解决:
指针i,j指向数组一头一尾(如果打算先do再while,i = a - 1, j = b + 1)
while(i <= j) i++,j--后直接交换,注意交换前先if(i <= j)判断
合并:
递归  sort(a,j,q);sort(i,b,q);  



边界问题,两个模板:
1.int i = 0; i < n; i++
  do i++; while(i < j)
  sort(a,j,q);sort(j + 1, b, q)
2.while(i <= j) if(i <= j) sort(a,j,q);sort(i,b,q)

完整代码

#include<stdio.h>

const int N = 100010;
int n,q[N];


void sort(int a,int b, int q[])
{      //判定数组有数   if(a > b) return;
    if(a >= b) return;
    int x = q[(a + b) / 2];    //注意x
    int i = a, j = b;  
 
    while(i <= j)  //边界
    {     
        while(q[i] < x) i++;
        while(q[j] > x) j--;
        if(i <= j)      
        {
            int temp = q[i];
            q[i] = q[j];
            q[j] = temp;
             i ++,j--;   //
        }
    }
    sort(a,j,q);   //先while(i <= j)后++,--,递归只能是j,i 
    sort(i,b,q);  //y总先do,用while(i < j),递归只能用j,j + 1
}

int main()
{
    scanf("%d",&n);
    for(int i = 1; i <= n; i ++)   //从0开始也可
    {
        scanf("%d",&q[i]);
    }
    sort(1,n,q);
    for(int i = 1; i <= n; i++)
    printf("%d ",q[i]);
    printf("\n");
    return 0;
}

 

#include<iostream>
using namespace std;

#define N 100010
int n,q[N];

void quick_sort(int q[],int l, int r)  
{
    if(l>=r) return;  //防止数组没有数
    //int x =q[l + r >> 1];   
    int x = q[(l + r)/2];
    /*
    如果这里是x = l,底下递归是j,j + 1
    如果这里是x = r,底下递归是i - 1,i
    如果是别的数无所谓
    */
    int i = l - 1, j = r + 1;  //先do后判断,所以多一点
    int swap;
    while(i < j) 
    {
        do i ++; while(q[i] < x);
        do j --; while(q[j] > x);
        if(i < j) 
        {
            swap = q[i];
            q[i] = q[j];
            q[j] = swap;
        }
    }
    quick_sort(q, l, j);  //边界问题
    quick_sort(q, j + 1, r); 
}

int main()
{
    scanf("%d",&n);
    for(int i = 0; i < n; i ++)  //从0开始
    {
        scanf("%d",&q[i]);
    }
   
    quick_sort(q,0,n-1);  
    

    for(int i = 0; i < n; i ++)
    printf("%d ",q[i]);
    return 0;
}

 

        24.11.9.这道题我写了三个多小时,它是一种最基础最简单的算法,但是让人蛋疼的区间问题直到最后也没能完全理解清楚,只总结出了两个不同区间的代码模板、

        有点怀疑学习算法的正确方法,对我这样只想有个基础能力目标蓝桥杯省一的人来说,钻研每一个细小的问题(没有老师同伴解惑)或许太耗时耗力。

        y总已经帮我们总结好了模板,避开了坑避开了初学者的难点只有最基础的算法思路,听课,理解思路,背模板,刷题才是我应该做的。

y总的课真的没有一句废话

第k个数

四个半小时

#include <iostream>

using namespace std;

int n,k;
const int N = 100010;
int q[N];

void sort(int a,int b,int q[])  //int sort(int a, int b, int q[], int k)
{
    if(a >= b) return ;   //return q[a];
    int  x = q[(a + b)/2];
    int i = a - 1,j = b + 1;
    while(i < j)
    {
        do i++; while(q[i] < x);
        do j--; while(q[j] > x);
        if(i < j)
        {
            int temp = q[i];
            q[i] = q[j];
            q[j] = temp;
        }
    }
    sort(a,j,q);
    sort(j + 1, b, q);
    // if(j - a + 1 >= k) return sort(a,j,q,k);   
    // else return sort(j + 1, b, q, k -(j - a + 1));
}

int main()
{
    scanf("%d %d",&n,&k);
    for(int i = 1; i <= n ; i++) scanf("%d",&q[i]);
// cout << sort(0, n - 1, q, k) << endl;
     sort(1, n, q);

    printf("%d\n", q[k]);
    return 0;
}

归并排序

24.11.10两个小时

思路

归并排序本质还是分治
但是先递归后解决

找中间点:
快排找的是数组中的一个数,这个数一般取数组中的中间值 int x = q[(l + r)/2];
归并排序找的是位于数组中间位置的一个中间位置  int mid = (l + r)/2;
递归:
sort(q, l, mid); sort(q, mid + 1, r);
解决:
两个指针分别指向两段递归好的有序数列的最小值  int k = 0, i = l, j = mid + 1;
最小值存放在空数组tmp[],i++或j++,k++
存放两段递归好的有序数列的最小值

模板:
while(i <= mid && j <= r)
{
    if(q[i] <= q[j]) tmp[k++] = q[i++];
    else tmp[k++] = q[j++];
}
while(i <= mid) tmp[k++] = q[i++];
while(j <= r) tmp[k++] = q[j++];

for(int i = l, k = 0; i <= r; i++, k++) q[i] = tmp[k];

完整代码

#include<stdio.h>

int n;
const int N = 100010;
int q[N],tmp[N];

void sort(int q[], int l, int r)
{
    if(l >= r) return;
    int mid = (l + r)/2;
    int i = l, j = mid + 1, k = 0;
    sort(q, l, mid);                    //先递归后解决
    sort(q, mid + 1, r);
    
    while(i <= mid && j <= r)           //模板从这里开始背
    {
        if(q[i] <= q[j]) tmp[k++] = q[i++];
        else tmp[k++] = q[j++];
    }
    while(i <= mid) tmp[k++] = q[i++];
    while(j <= r) tmp[k++] = q[j++];
    
    for(int i = l, k = 0; i <= r; i++, k++) q[i] = tmp[k];   //注意l,r
}

int main()
{
    scanf("%d",&n);
    for(int i = 0; i < n; i++) scanf("%d", &q[i]);
    sort(q, 0, n - 1);
    for(int i = 0; i < n; i++) printf("%d ", q[i]);
    return 0;
}

逆序对的数量 

 

//wocao好聪明的办法
#include<iostream>
using namespace std;

const int N = 100010;
int q[N],tmp[N];

long long sort(int q[], int l, int r)
{
    if(l >= r) return 0;   //0
    int mid = (l + r)/2;
    
    long long res = sort(q, l, mid) + sort(q, mid + 1, r); //
    int i = l, j = mid + 1,k = 0;
    while(i <= mid && j <= r)
    {
        if(q[i] <= q[j]) tmp[k++] = q[i ++];
        else {
             res += mid - i + 1;    //
            tmp[k ++] = q[j ++];
           
        }
    }
    while(i <= mid) tmp[k ++] = q[i ++];
    while(j <= r) tmp[k ++] = q[j ++];
    for(int i = l, k = 0; i <= r; i ++, k ++) q[i] = tmp[k];   //<=
    return res;
}

int main()
{
    int n;
    scanf("%d", &n);
    for(int i = 0; i < n; i ++) scanf("%d", &q[i]);
    // int m = sort(q, 0, n - 1);
    // printf("%d ", m);    不能这么写
    cout << sort(q, 0, n - 1) << endl;
    return 0;
}
//这代码没问题能提交但是输出结果不对让我找了半天,,,,,

二分法

用y总的话来说,整数二分的边界问题,是真TM蛋疼

吸取上面教训,不要深究边界,直接背模板

24.11.18,1小时,之前写过,今天还算容易

思路

//整数
我觉得什么都别说了直接上模板吧
i=0;i<n;i++
q[i]
int l,r;
l=0,r=n-1
//左边界
while(l<r)  //注意循环
mid=(l+r)/2
if(a[mid]>=k) r=mid  //每次选答案所在的区间,区间一定要将答案覆盖掉
else l=mid+1

printf()
if()
//右边界
else {
int l=0,r=n-1
while()
mid=(l+r+1)/2
if(a[mid]<=k) l=mid;
r=mid-1

printf()

//浮点数
取后六位:while(r-l>1e-8)
浮点数二分比整数二分舒服多了

完整代码

//整数
#include<stdio.h>

int n,q;
const int N=100010;
int a[N];

int main()
{
    scanf("%d %d",&n,&q);
    // for(int i=1;i<=n;i++)
    for(int i=0;i<n;i++) 
    scanf("%d",&a[i]);
    while(q--)
    {
        int k;
        scanf("%d",&k);
        int l,r;
        // l=1,r=n;
        l=0,r=n-1;
        while(l<r)
        {
            int mid=(l+r)/2;
            if(a[mid]>=k) r=mid;
            //每次选答案所在的区间,区间一定要将答案覆盖掉
          
            else l=mid+1;
        }
        
        if(a[l]!=k) printf("-1 -1\n");
        else{
            printf("%d ",l);
            // int l=1,r=n;
            l=0,r=n-1;
            while(l<r)
            {
                int mid=(l+r+1)/2;
                if(a[mid]<=k) l=mid;
                else r=mid-1;
            }
         printf("%d\n",l);
        }
    }
    return 0;
}

//浮点数
//求一个数的三次方根
#include<iostream>
using namespace std;

double n,k;

int main()
{
    scanf("%lf",&n);
    double l = -100, r = 100;   //n取值-10000到+10000 
    //0-max(1,x),r一定大于1(0.01 = 0.1*0.1)
    while(r - l > 1e-8)    //还有这里,多两位数
    {
        double mid = (l + r)/2;
        double k = mid;
        if(k * k * k < n) l = k;
        else r = k;
    }
    printf("%.6lf",l);
    return 0;
}

前缀和

思路

2024.11.23,模板很简单,1.5小时,二维数组稍微费点心思

数组a[n],第l到r个数的和
= s[r] - s[l - 1]

求s[n]
s[i] = s[i - 1] + a[i]


二维
a[x1][y1]到a[x2][y2]的和  //a[n][m] n行m列
画图
s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]

求s[i][j]
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i-1][j-1] + a[i][j]

完整代码

//挺简单但是容易粗心
#include<iostream>
using namespace std;

const int N = 100010;
int a[N], s[N];
int l, r;

int main()
{
    int n, m;
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
    for(int i = 1; i <= n; i ++) s[i] = s[i - 1] + a[i];
    while(m --)
    {
        scanf("%d %d", &l, &r);
        printf("%d\n", s[r] - s[l - 1]);
    }
    return 0;
}

//y总模板真niub
#include<iostream>
using namespace std;

const int N = 1010;
int a[N][N], s[N][N];

int main()
{
    int n, m, q;
    scanf("%d %d %d", &n, &m, &q);
    
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= m; j++)
            scanf("%d", &a[i][j]);   //读入s[i][j]也行,底下相对改
            
    for(int i = 1; i <= n; i ++)   //注意这一对循环放while外面,不然会超时
        for(int j = 1; j <= m; j ++)
            s[i][j] = s[i][j - 1] + s[i - 1][j] - s[i - 1][j - 1] + a[i][j];   //相对改成s[i][j] += 后面去掉a[i][j]
        
            
    while(q--)
    {
        int x1, y1, x2, y2;
        scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
        printf("%d\n", s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]);  //注意-1边界问题
    }
    return 0;
}

//自己写的,不对,找不到错,输出结果仍然是0,不管了背模板
// #include<iostream>
// using namespace std;

// const int N = 1010;
// int a[N][N], s[N][N];

// int main()
// {
//     int m, n, q;
//     scanf("%d %d %d", &n, &m, &q);
//     while(n --)
//     {
//         int j = 1;
//         for(int i = 1; i <= m; i ++) scanf("%d", &a[j][i]);
//         j ++;
//     }
    
//     for(int i = 1; i <= n; i ++)
//         for(int j = 1; j <= m; j ++)
//             {
//                 s[i][j] = s[i - 1][j - 1] + a[i - 1][j] + s[i][j - 1] + a[i][j];
//             }
            
//     int x1, y1, x2, y2;
//     while(q --)
//     {
//         scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
//         s[x2][y2] = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 -  1][y1 - 1];
//         printf("%d\n", s[x2][y2]);
//     }
//     return 0;
// }

差分

2024.11.24.2h

一维的还好,二维头疼

是谁闲的没事能想出差分这个概念,又是谁能想出差分解这道题太聪明了

思路

原数组a[n]
构造数组b[n]
a[n]是b[n]的前缀和
b[n]叫做a[n]的差分

模板
void insert(int l, int r, int c)  //l,r位置,c需要加的数
{
b[l] += c;
b[r + 1] -=c;
}

//读入a[n]两个for

for(int i = 1; i <= n; i ++)
insert(i, i, a)   //构造b[n],想不出来草稿纸过一遍

while(q --)
insert(l, r, c)  //给b[n]插入c

//输出b[n]前缀和



二维 
//右下角部分
void insert(int x1, int y1, int x2, int y2, int c)
{
b[x1][y1] += c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y1] -= c;
b[x2 + 1][y2 + 1] +=c;
}

//读入a[n]

//构造b[n]
for(int i = 1; i <= n; i ++)
    for(int j = 1; j <= m; j ++)
        insert(i, j, i ,j ,a)

while
insert(x1, y1, x2, y2, c);  //插入c

//输出 前缀和
b[i][j] += b[i - 1][j] + b[i][j - 1] -b[i - 1][j - 1];

二维是真头疼

完整代码

//这道题用差分太聪明了
//是谁能闲的没事能想到差分这个概念
//当我觉得能想到用差分已经很牛逼的时候,这个构造差分的insert更让我觉得我们不是同一个物种

#include<iostream>
using namespace std;

const int N = 100010;
int a[N], b[N];


void insert (int l, int r, int c)
{
    b[l] += c;
    b[r + 1] -= c;
}

int main()
{
    int n, m;
    scanf("%d %d",&n ,&m);
    for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
    for(int i = 1; i <= n; i ++) insert(i, i, a[i]);   //构造b[i]
    while(m --)
    {
        int l, r, c;
        scanf("%d %d %d", &l, &r, &c);
        
        insert(l, r, c);   //
    }
    for(int i = 1; i <= n; i ++) b[i] += b[i - 1];  //b[i]的前缀和是a[i]
    for(int i = 1; i <= n; i ++) printf("%d ",b[i]);
    return 0;
}

//一维都很简单,二维脑子很疼

/*
主要是思路乱了
下次先在纸上写整体思路
a[i][j]读入A
构造b[i][j]
b[i][j]插入,这里重点
算b[i][j]前缀和就是
*/

#include<iostream>
using namespace std;

const int N = 1010;
int a[N][N], b[N][N];

void insert(int x1, int y1, int x2, int y2, int c)
{
    b[x1][y1] += c;
    b[x1][y2 + 1] -= c;   // 右下所有-c
    b[x2 + 1][y1] -=c;
    b[x2 + 1][y2 + 1] += c;
}

int main()
{
    int n, m, q;
    scanf("%d %d %d", &n, &m, &q);
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= m; j ++)
            scanf("%d", &a[i][j]);  //读入
    
    for(int i = 1; i <= n; i ++) 
        for(int j = 1; j <= m; j ++)
            insert(i, j, i, j, a[i][j]);   //构造

    while(q --)
    {
        int x1, y1, x2, y2, c;
        scanf("%d %d %d %d %d",&x1, &y1, &x2, &y2, &c);
        insert(x1, y1, x2, y2, c);   //插入
    }
    
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= m; j ++)
            b[i][j] += b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1];   //前缀和
            
    for(int i = 1; i <= n; i ++){
        for(int j = 1; j <= m; j++)
            printf("%d ",b[i][j]);
            printf("\n");  //
    }            
    return 0;
}

双指针

思路

for(int i = , j = ;i < ;i++)
{
    。。。
    while(j < i && ...)
    {
        ...
        j++;
    }
    ...
}

注意c++不能用printf

完整代码

#include<iostream>
using namespace std;

const int N = 100010;
int n,a[N],q[N];

int main()
{
    scanf("%d",&n);
    for(int i = 1; i <= n; i++)
    scanf("%d",&a[i]);
     int res = 0;
    for(int i = 1, j = 1; i <= n; i ++)
    {
        q[a[i]] ++;
        while(j < i && q[a[i]] > 1)
        {
            q[a[j ++]] --;  //
            
        }
        res = max(i - j + 1,res);
    }
    printf("%d", res);
    return 0;
}

位运算

25.3.4

思路

位运算两种类型(二进制)
1.求n的第k个数:n >> k & 1 (n右移k与1)

2.返回N的最后一个1:lowbit(n) = n & -n (函数)
eg:1010(10)  lowbit(10) = 10
   101000(40) lowbit(40) = 1000
n & -n相当于n & n的反码+1(n的补码)
原理不记了反正记住函数 
int lowbit(int n)
{
    return n & -n ;
}
可以返回n的最后一位1

完整代码 

#include<iostream>
using namespace std;

int n;

int lowbit(int x)
{
    return x & -x;
}

int main()
{
    cin >> n;
    while(n --) 
    {
        int x;
        cin >> x;
        int res = 0;
        while(x) x -= lowbit(x), res ++;  //每次减去x的最后一位1
        // printf("%d\n", res);
        cout << res << ' ';  //
    }
    return 0;
}

数据结构

单链表

25.3.6 23:25好快啊25年都到3月了

1小时多点吧没算,以前写过不费劲

概念

注意:
1.初始化
void init()
{
idx = 0;
head = -1;
}

2.增加链表,idx ++;

3.删除注意可能是头结点,head = ne[head]

4.输出链表
for(int i = head; i != -1; i = ne[i]) cout << e[i] 
这里i != -1对吗,head不就等于-1,这个循环能执行?
能执行是因为有过删除头结点的操作吗,如果没有过删除头结点的操作,输出单链表就不能这么写是吗?不太懂
代码

#include<iostream>
using namespace std;

const int N = 100010;
int e[N], ne[N];
int idx, head;

void init()
{
    idx = 0;
    head = -1;
}

void add_head(int x)
{
    e[idx] = x;
    ne[idx] = head;
    head = idx ++;   //++
}

void dele(int x)
{
    ne[x - 1] = ne[ne[x - 1]];
}

void insert(int k, int x)
{
    e[idx] = x;
    ne[idx] = ne[k - 1];
    ne[k - 1] = idx++;    //++
}

int main()
{
    init();
    int M;
    cin >> M;
    while(M--)
    {
        char ch;
        int x, k;
        cin >> ch;
        if(ch == 'H')
        {
            cin >> x;
            add_head(x);
        }
        else if(ch == 'D')
        {
            cin >> x;
            if(x == 0) head = ne[head];   //
            else dele(x);
        }
        else{
            cin >> k >> x;
            insert(k, x);
        }
    }
    int i = head;
    // while(ne[i])
    // {
    //     cout << e[i] << ' ';
    //     i = ne[i];
    // }
    for(int i = head; i != -1; i = ne[i]) cout << e[i] << ' ';  
    //这里i != -1对吗,head不就等于-1,这个循环能执行?
    //能执行是因为有过删除头结点的操作吗,如果没有过删除头结点的操作,输出单链表就不能这么写是吗?不太懂
    return 0;
}

双链表

25.3.7 0:28,1小时,简单

概念

双链表:一个节点储存两个指针,一个指向前le[],一个指向后re[]

1.没有头结点尾结点,用下标0和1代替,所有链表在下标0和1之间,idx从2开始
2.输出双链表
for(int i = 0; i != 1; i = re[i]) cout << e[i];
应当I是不指向结尾
代码
#include<iostream>
using namespace std;

const int N = 100010;
int idx, re[N], le[N], e[N], M;

void init()
{
    // idx = 0;
    // head = 0;
    // tail = 1;
    // re[head] = 1;
    // le[tail] = 0;
    idx = 2;
    re[0] = 1;  //相当于没有头结点和尾结点了 链表在0和1中间
    le[1] = 0;
}

void rightadd(int k,int x)
{
    e[idx] = x;
    re[idx] = re[k];
    le[idx] = k;
    le[re[k]] = idx;
    re[k] = idx ++;
}

void dele(int k)
{
    re[le[k]] = re[k]; 
    le[re[k]] = le[k];
    
}

int main()
{
    init();
    cin >> M;
    while(M --)
    {
        //string op;
        char ch,ch2;
        int x, k;
        cin >> ch;
        if(ch == 'L')
        {
            cin >> x;
            rightadd(0, x);
        }
        else if(ch == 'R')
        {
            cin >> x;
            rightadd(le[1], x);
        }
        else if(ch == 'D')
        {
            cin >> x;
            dele(x + 1);  //注意下标
        }
        else if(ch == 'I')
        {
            cin >> ch2;
            if(ch2 == 'L')
            {
                cin >> k >> x;
                rightadd(le[k + 1], x);  
            }
            else{
                cin >> k >> x;
                rightadd(k + 1, x);
            }
        }
    }
    for(int i = re[0]; i != 1; i = re[i]) cout << e[i] << ' ';   //i !=结尾
    cout <<endl;
    return 0;
}

栈和队列

刚打算说简单的我看到了第二题,,,开始背吧

队列2025.3.12.8:59,概念不难,栈也是

概念

栈:先进后出,有底罐子
队列:先进先出,一口进一口出管子

栈
int a[N],tt;
加入:a[++tt] = x
删除:tt --
判断是否为空:if(tt > 0) no
栈顶元素:a[tt]

队列
int q[N], hh, tt = -1;
插入:q[++ tt] = x;
删除:hh ++;
判断是否为空:if(tt >= hh) no
队列头元素尾元素:q[hh], q[tt]

单调栈
代码

队列有道题代码有点复杂,表达式求值

#include <iostream>
#include <cstring>           //<string>.size<cctype>isdigit
#include <algorithm>         //STL算法
#include <stack>             //stack容器,可以使用栈
#include <unordered_map>     //无序映射,类似哈希表,存入运算符优先级

using namespace std;

stack<int> num;    //定义数字栈
stack<char> op;     //定义运算符栈

void eval()
{
    auto b = num.top(); num.pop();     //.top查看栈顶元素
    auto a = num.top(); num.pop();     //.pop移除栈顶元素
    auto c = op.top(); op.pop();
    int x;
    if (c == '+') x = a + b;
    else if (c == '-') x = a - b;
    else if (c == '*') x = a * b;
    else x = a / b;
    num.push(x);
}

int main()
{
    unordered_map<char, int> pr{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};
    string str;
    cin >> str;
    for (int i = 0; i < str.size(); i ++ )
    {
        auto c = str[i];
        if (isdigit(c))   //c是否为0-9的数字
        {
            int x = 0, j = i;
            while (j < str.size() && isdigit(str[j]))
                x = x * 10 + str[j ++ ] - '0';         //
            i = j - 1;
            num.push(x);    //.push存储
        }
        else if (c == '(') op.push(c);
        else if (c == ')')
        {
            while (op.top() != '(') eval();    //
            op.pop();
        }
        else
        {
            while (op.size() && op.top() != '(' && pr[op.top()] >= pr[c]) eval();
            op.push(c);   //
        }
    }
    while (op.size()) eval();   //
    cout << num.top() << endl;
    return 0;
}

单调栈

25.3.12 9:40这道题有点意思

#include<iostream>
using namespace std;

int N;
const int n = 100010;
int q[n], tt;

int main()
{
    cin >> N;
    while(N --)
    {
        int x;
        cin >> x;
        while(tt && q[tt] >= x) tt --;  //&&
        if(tt) cout << q[tt] << ' ';
        else cout << "-1" << ' ';
        q[++ tt] = x;
    }
    return 0;
}

 单调队列

25.3.12 15:40这题有点小难度

#include<iostream>

using namespace std;

const int N = 1000010;
int n, k, a[N], q[N];  //q[N]储存单调数列的下标(滑动窗口数列)

int main()
{
    scanf("%d %d", &n,&k);
    for(int i = 0; i < n; i ++) scanf("%d", &a[i]);
    
    int hh = 0, tt = -1;   //hh还是要初始化的
    for(int i = 0; i < n; i ++ )
    {
        //让队头滑出窗口
        if(hh <= tt && q[hh] < i - k + 1) hh++;
        //单调队列
        while(hh <= tt && a[q[tt]] >= a[i]) tt --;  //=
        q[ ++ tt] = i;
        if(i >= k - 1) cout << a[q[hh]] << ' ';
    }
    puts ("");  //
    //最大值
    hh = 0, tt = -1;
    for(int i = 0; i < n; i ++)
    {
        if(hh <= tt && q[hh] <= i - k) hh ++;
        while(hh <= tt && a[q[tt]] <= a[i]) tt --;
        q[++ tt] = i;
        if(i >= k - 1) cout << a[q[hh]] << ' ';
    }
    return 0;
}

KMP

woc哪个天才想出的KMP啊

25.3.13 12:53有仨小时吧

就不解释了直接看代码吧

代码
#include<iostream>

using namespace std;

int n,m;
const int N = 100010, M = 1000010;
char p[N], s[M];
int ne[N];

int main()
{
    cin >> n >> p + 1 >> m >> s + 1;   //下标从1开始
    
    //next数组 ne[i] = j;以i结尾的后缀数列,向右移,直到匹配前缀数列长度为j,下标为j
    for(int i = 2, j = 0; i <= n; i ++)   //字符串指针i是从1开始,j都从0开始
    {  
        while(j && p[i] != p[j + 1]) j = ne[j];  //p, j = ne[j]
        if(p[i] == p[j + 1]) j ++;  //这里判断不需要j存在
        ne[i] = j;
    }
    
    //KMP匹配
    for(int i = 1, j = 0; i <= m; i ++)
    {
        while(j && s[i] != p[j + 1]) j = ne[j];
        if(s[i] == p[j + 1]) j ++;  
        if(j == n)  //匹配成功
        {
            printf("%d ", i - n);   //题目下标从0开始
            j = ne[j];  //ne j
        }
    }
    return 0;
}

Trie

好难好难好难,简单的只有前面那一点是吧

中午看完的。25.3.13 23:00 1+0.5h

异或运算25.3.14 16:57挺久的仨小时?以后还是刚看完就写吧,不然太浪费时间,虽然这样记得牢

思路

以节点树的形式存储字符串
idx为每个节点唯一的序号
son[p][u] = ++idx存储树,p为当前节点序号idx(我理解的p是指针指向idx),u为下方子节点,等于子节点++idx的序号
cnt[p]表示字符串结尾数量
str[]为要插入或者查询的字符串

插入:
void insert(char str[])
{
    int p = 0;
    for(int i = 0; str[i]; i ++)  不管是插入还是查找,str[i](前一个字母存在)(插进去或找到),在进行下一个字母
    {
        int u = str[i] - 'a';  让字母转化为数字存储
        if(!son[p][u]) son[p][u] = ++idx;   不存在就加个新标识(idx)
        p = son[p][u];   插入完或本身就存在,p指向下一个节点(子节点)的序号
    }
    cnt[p]++;   节点p结尾的字符串数量=cnt[p]
}

查找:
int quar(char str[])
{
    int p = 0;
    for(int i = 0; str[i]; i ++)
    {
        int u = str[i] - 'a';
        if(!son[p][u]) return 0; 从根节点开始遍历的,如果第一个字母就不存在,字符串肯定也不存在
        p = son[p][u];  指向下一个节点后i++
    }
    return cnt[p];  遍历完字符串后p指向字符串结尾,返回单词数量

代码
#include<iostream>

using namespace std;

const int N = 100010;
char str[N];
int son[N][26], cnt[N], idx;

void insert(char str[])
{
    int p = 0;
    for(int i = 0; str[i]; i ++)  //
    {
        int u = str[i] - 'a';
        if(!son[p][u]) son[p][u] = ++idx;  //
        p = son[p][u];
    }
    cnt[p] ++;
}

int quar(char str[])   //
{
    int p = 0;
    for(int i = 0; str[i]; i ++)   //
    {
        int u = str[i] - 'a';
        if(!son[p][u]) return 0;   //
        p = son[p][u];
    }
    return cnt[p]; //
}

int main()
{
    int n;
    scanf("%d", &n);
    while(n --)
    {
        char ch;
        cin >> ch >> str;
        if(ch == 'I') insert(str);
        else cout << quar(str) << endl;
    }
    return 0;
}
异或运算:两个数二进制,不一样的为1,一样的为0,最后的值

#include<iostream>
//#include<algorithm>// 

using namespace std;

const int N = 100010, M = 3100010;  //M
int a[N];
int n;
int son[M][2], idx;   //2

void insert(int x)   //
{
    int p = 0;
    for(int i = 30; i >= 0; i --)   //从最高位开始遍历,int通常32位整数
    {
        // if(!son[p][x >> i & 1]) son[p][x >> i & 1] = ++idx;  //x右移i位取最低位,求二进制的第i位
        // p = son[p][x >> i & 1];
        int  &s = son[p][x >> i & 1];
        if(!s) s = ++idx;
        p = s;
    }
}

//woc好聪明
int qmax(int x)
{
    int p = 0, res = 0;
    for(int i = 30; i >= 0; i --)
    {
        int s = x >> i & 1;
        if(son[p][!s]) 
        {
            res += 1 << i;   //在res中加上2^i,相当于res的第i位加1
            p = son[p][!s];  //!s
        }
        else p = son[p][s];   //
    }
    return res;  
}

int main()
{
    int res = 0;   //
    scanf("%d", &n);
    for(int i = 0; i < n; i ++) scanf("%d", &a[i]);
    for(int i = 0; i < n; i ++) insert(a[i]);
    for(int i = 0; i < n; i ++)  res = max(res, qmax(a[i]));  //woc这太聪明了
    printf("%d\n", res);
    return 0;
}

并查集

一道比一道难,最后一道真的写了好几天,视频至少两遍25.3.16 14:53

概念

1.集合合并
2.询问两个元素是否在一个集合中

每个集合用一棵树表示;树根的编号是整个集合的编号;每个节点存储它的父节点p[x]表示父节点

判断树根:if(p[x] == x)
求x的集合编号:while(p[x] != x) x = p[x];
if(p[x] != x) p[x] = find(x);
return p[x];
合并两个集合:p[x] = y
p[find(a)] = find(b);
代码
#include<iostream>

using namespace std;

const int N = 100010;
int q[N];  //q[N]等于N节点的父节点

int find(int x)  //find查找这个数根节点
{
    // int p[N];
    if(q[x] != x) q[x] = find(q[x]);  !
    return q[x];
}

int main()
{
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i ++) q[i] = i; !1 //一开始每个数各自在一个集合中
    while(m--)
    {
        char op;
        int a, b;
        cin >> op >> a >> b;
        if(op == 'M') 
        {
            q[find(a)] = find(b);  !
        }
        else {
            if(find(a) == find(b)) cout << "Yes" <<endl;
            else cout << "No" << endl;
        }
    }
    return 0;
}

食物链 

#include<iostream>
using namespace std;

const int N = 50010;
int n, m;
int p[N], d[N];  //N的父节点,N到根节点的距离
/* d[x]
%3 = 1, 可以吃根节点
%3 = 2, 可以被根节点吃
%3 = 0, 跟根节点同类
*/

int find(int x)  //x的根节点
{
    if(p[x] != x)
    {
        int t = find(p[x]);
        d[x] += d[p[x]];  
        //初始状态每个节点的父节点都是自己,p[x] = x;d[x] = 0;(d[x]到父节点的距离 = 0)
        //路径压缩:p[x] = find(p[x]); d[x] = d[x] + d[p[x]];
        p[x] = t;
    }
    return p[x];
}

int main()
{
    scanf("%d%d",&n, &m);
    
    for(int i = 1; i <= n; i ++) p[i] = i;
    
    int res = 0;
    while(m --)
    {
        int t, x, y;
        scanf("%d%d%d", &t, &x, &y);
        
        if(x > n || y > n) res ++;   //或
        else {
            int px = find(x), py = find(y);
            if(t == 1) //同类
            {
                if(px == py && (d[x] - d[y]) % 3) res ++;
                else if(px !=py)
                {
                    p[px] = py;
                    d[px] = d[y] - d[x];  //d[x] + ? = d[y],同类%3=0
                }
            }
            else {  //x吃y(x2,y1)
                if(px == py && (d[x] - d[y] -1) % 3) res ++;   //x2 y1
                else if(px != py)
                {
                    p[px] = py;
                    d[px] = d[y] + 1 - d[x];  //d[x] + ? - 1 = d[y]
                }
            }
        }
    }
    printf("%d\n", res);
    return 0;
}

25.3.18 18:08我真服了,手机用了一下clash,然后流量热点全进不了csdn,还以为是csdn崩了,从3.16晚上到现在,才靠deepseek解决

具体原因猜的,用clash不正确,导致临时配置冲突或者残留,clash关了也没用,让csdn把ip封锁了?反正,先卸载手机clash,浏览器清理缓存,浏览器能使用,手机不行。手机重启,然后可以使用。可能不至于ip封锁,就是什么残留吧,浏览器一清手机一重启,重新分配IP就行

堆我都有点忘了,数据结构后面这些都有点难,得复习,但是没时间了,4.13蓝桥杯,我想在4月前把基础算法看完

思路

从trie开始好像都是数?记不清了回来复习再补上
根节点x,左下2x,右下2x+1,也是123的排序

手写堆:
插入一个数:   heap[ ++ size] = x;  up(size);
求集合当中最小值:heap[1];
删除最小值:   heap[1] = heap[size];  size --;  down(1);
删除任意一个元素: heap[k] = heap[size];  size --;  down(k);  up(k);
修改任意一个元素: heap[k] = x;  don(k);  up(k);

原理好简单,但我记得题好难,直接上代码吧

代码
#include<iostream>

using namespace std;

const int N = 100010;
int n, m;
int h[N], cnt;

void down(int x)
{
    int t = x;
    if(2 * x <= cnt && h[t] > h[2 * x]) t = 2 * x;
    if(2 * x + 1 <= cnt && h[t] > h[2 * x + 1]) t = 2 * x + 1;
    if(x != t)     
    {
        swap(h[x], h[t]);
        down(t);
    }
}

int main()
{
    cin >> n >> m;
    cnt = n;
    for(int i = 1; i <= n; i ++) scanf("%d", &h[i]);
    for(int i = n / 2; i; i --) down(i);
    
    while(m --)
    {
        printf("%d ", h[1]);
        h[1] = h[cnt];
        cnt --;
        down(1);
    }
    return 0;
}
#include<iostream>
#include<algorithm>
#include<string.h>

using namespace std;

const int N = 100010;
int h[N], cnt, ph[N], hp[N], n, idx;

void heap_swap(int a, int b)
{
    swap(ph[hp[a]], ph[hp[b]]);
    swap(hp[a], hp[b]);
    swap(h[a], h[b]);
}

void down(int x)
{
    int t = x;
    if(2 * x <= cnt && h[2 * x] < h[t]) t = 2 * x;   //注意x和t
    if(2 * x + 1 <= cnt && h[2 * x + 1] < h[t]) t = 2 * x + 1;
    if(t != x) 
    {
        heap_swap(x, t);
        down(t);
    }
}

void up(int x)
{
    while(x / 2 && h[x / 2] > h[x])
    {
        heap_swap(x, x/2);
        x >>= 1;   // x = x / 2
    }
}

int main()
{
    cnt = 0, idx = 0;
    cin >> n;
    while(n --)
    {
        char op[5];
        cin >> op;
        int x, y;
        if(!strcmp(op,"I"))
        {
            cin >> x;
            ph[++idx] = ++ cnt; //注意这里idx和cnt是从0还是1开始
            hp[cnt] = idx;
            h[cnt] = x;
            up(cnt);
            
        }
        else if (!strcmp(op,"PM")) printf("%d\n", h[1]);
        else if(!strcmp(op, "DM"))   
        {
            heap_swap(1, cnt);
            cnt --;
            down(1);
        }
        else if(!strcmp(op, "D"))
        {
            cin >> x;
            x = ph[x];
            heap_swap(x, cnt);
            cnt --;
            down(x);
            up(x);
        }
        else 
        {
            cin >> x >> y;
            x = ph[x];
            h[x] = y;
            down(x);
            up(x);
        }
    }
    return 0;
}
//不是特别的难但是有点绕,自我感觉写完要疯掉了,还好有deepseek,不然得检查多久,真的会崩

哈希表

哈希表才是快学疯了,折腾整整一两天才把概念写出来,还不怎么记得了

概念

哈希表是将一组大范围数,映射到小范围数组中(h[]),它的存储结构有:
开放寻址法:找茅坑。这个用的最多但是初始化那里不太懂,null,memset,还有16进制
拉链法(单链表法):h[]为每个链表的头结点,这个注意也要初始化为空(!不是0)
代码
//链表法(拉链法)
#include<iostream>
#include<cstring>

using namespace std;

const int N = 100003;  //大于它的最小质数
int h[N], e[N], ne[N], idx;

void insert(int x)
{
    int k = (x % N + N) % N;  //注意N
    e[idx] = x;
    ne[idx] = h[k];
    h[k] = idx ++;
}

bool quar(int x)
{
    int k = (x % N + N) % N;   //注意N
    for(int i = h[k]; i != -1;i = ne[i])
    {
        if(e[i] == x) return true;  // 这里用true,false
        //else if(i == n) return k;   等循环完了就行,这里不用判断
    }
    return false;
}

int main()
{
     int n;
     cin >> n;
     
     memset(h, -1, sizeof h);   //数组初始化为空ctring
     /*
        数组如果定义为全局变量,默认已经初始化为0
        如果在函数内部定义(main),此时 h 的元素是程序运行前内存区域的残留值(随机数)
        memset覆盖掉之前初始化0,将H初始化为-1
     */
     
     while(n --)
     {
         char op;
         int x;
         cin >> op >> x;
         if(op == 'I') insert(x);
         else if(op == 'Q')
         {
             if(quar(x)) cout << "Yes" << endl;
             else cout << "No" << endl;
         }
         
     }
     return 0;
}
//开放寻址法
#include<iostream>
#include<cstring>

using namespace std;

const int N = 200003;  //两倍,比他大的最小质数
int n, h[N];
const int null = 0x3f3f3f3f;   //

int find(int x)
{
    int t = (x % N + N) % N;
    while (h[t] != null && h[t] != x)  //null
    {
        t ++ ;
        if (t == N) t = 0;
    }
    return t;
}


int main()
{
    memset(h, 0x3f, sizeof h);  //
    
    cin >> n;
    
    while(n --)
    {
        char op;
        int x;
        cin >> op >> x;
        if(op == 'I') h[find(x)] = x;
        else if(op == 'Q') 
        {
            if(h[find(x)] != x) puts("No");
            else puts("Yes");
        }
    }
    return 0;
}


 字符串哈希

这个更疯,注意力不集中,没太看懂

23.3.18 20:37杀疯了21:05

为什么这个记得这么详细,因为真的没太懂,也不是没太懂,根本记不住原理过程,直接背模版吧

原理

字符串前缀哈希法
str = "abcabcabcabc"
h[0] = 0
h[1] = "a"的哈希值
h[2] = "ab"的哈希值
h[3] = "abc"的哈希值
哈希值是什么
p进制
a b c d = (1 2 3 4)p = 1xp^3 + 2xp^2 + 3xp^1 + 4xp^0
% Q
p = 131或13331, Q = 2^64基本不存在冲突

1..........L - 1  L.........R
左边高位,右边低位(参考二进制)
h[R] = (axp^r-1 .....axp^0)p
h[L - 1] = (axp^L-1 ....... axp^0)p
R与L - 1相差R - (L - 1)位,将他们对齐:h[L - 1]*p^(R - L + 1)
eg:(bbbbbbbaaaaaa)
   (aaaaaaa)
变成(bbbbbbaaaaaa)
    (aaaaaa000000)

不需要取模,溢出相当于取模(没懂)

插入:h[i] = h[i - 1] * p + str[i]  //woc这是真牛逼,什么人能想出来字符串哈希这种东西

快速判断两个区间内字符串是否完全相同:求前缀,算两个字符串的哈希值,哈希值相同,字符串就相同
    

模版

#include<iostream>

using namespace std;

const int N = 100010;  //P放在这里也行
typedef unsigned long long ULL;
int n, m;
char str[N];  //读入原数组
int P = 131;  //16进制
int h[N], p[N]; //h[]存储哈希值,p[N]相当于P的N次方

ULL get(int l, int r)
{
    return h[r] - h[l - 1] * p[r - l + 1];
}

int main()
{
    cin >> n >> m;
    cin >> str + 1;
    
    p[0] = 1; //初始化
    //读入h[] p[]
    for(int i = 1; i <=n; i ++)
    {
        p[i] = p[i - 1] * P;
        h[i] = h[i - 1] * P + str[i];
    }
    while(m --)
    {
        int l1, l2, r1, r2;
        cin >> l1 >> r1 >> l2 >> r2;
        if(get(l1, r1) != get(l2, r2)) cout << "No" << endl;
        else cout << "Yes" << endl;
    }
    return 0;
}

千言万语就剩了一句woc

搜索与图论

dfs

25.3.20 18:23两天前写的好像,忘了保存。递归画图有点难理解。直接上皇后问题吧

算是深度搜索?遍历完整条路再回溯

模版(皇后问题)

第二种方法更快更熟一点

//第一种方法,每个格子枚举
#include<iostream>

using namespace std;

const int N = 10;
int n;
bool row[N], col[N], dg[N], udg[N];
char g[N][N];

void dfs(int x, int y, int s)
{
    if(s > n) return;   //s存储已放皇后量
    if(y == n) y = 0, x ++;
    if(x == n)    //
    {
        if(s == n)
        {
            for(int i = 0; i < n; i ++) puts(g[i]);
            puts("");
            
        }
         return;
    }
    // if(x == n && s == n)
    // {
    //     for(int i = 0; i < n; i ++) puts(g[i]);
    //     puts("");
    //     return;
    // }这段return的逻辑不对,x到n就一定要return,不然溢出
    g[x][y] = '.'; //初始化
    dfs(x, y + 1, s);  //下一位
    if(!row[x] && !col[y] && !dg[x + y] && !udg[n + x - y])
    {
        g[x][y] = 'Q';
        row[x] = col[y] = dg[x + y] = udg[n + x - y] = true;
        dfs(x, y + 1, s + 1);   //
        row[x] = col[y] = dg[x + y] = udg[n + x - y] = false;
        g[x][y] = '.';
    }
}

int main()
{
    cin >> n;
    dfs(0, 0, 0);
    return 0;
}

//第二种顺序搜索,固定行往不同列中插入
#include<iostream>

using namespace std;

const int N = 20;
int n;
char g[N][N];
bool col[N], dg[N], udg[N];  //

void dfs(int u)
{
    if(u == n)
    {
        for(int i = 0; i < n; i ++)  //
            //cout << g[u][i];  u永远=n,可以用双重循环输出
             puts(g[i]);
        puts("");
        return;
    }
    
    for(int i = 0; i < n; i ++)  //
    {
        if(!col[i] && !dg[u - i + n] && !udg[u + i]) //这里xy轴可以互换
        {
            g[u][i] = 'Q';
            col[i] = dg[n + u - i] = udg[u + i] = true;
            dfs(u + 1);
            col[i] = dg[n + u - i] = udg[u + i] = false;
            g[u][i] = '.';
        }
        
    }
}

int main()
{
    cin >> n;
    
    for(int i = 0; i < n; i ++)  //
        for(int j = 0; j < n; j ++)
            g[i][j] = '.';
            
    dfs(0);  //dfs永远是从0开始的,数组定义也是
    return 0;
}

bfs

25.3.21 12:40有空学下stl,感觉这个有点像背代码了

皇后问题最后是熟了,但是bfs真的不行,也是我不想看了,就当都不会吧,第二题跳过

(25.3.25 23:39补)这段代码不要看了,涉及太多stl背的难受,后面有更简洁的bfs模板,看这个

概念

广度遍历,一层一层向外扩,可搜索最短路径

队列:
typedef pair<int, int> PII;


int bfs()
{
    queue<PII> q;  //创建
    q.push({0, 0});
    while()
    auto t = q.front;
    q.pop();

代码 

#include<iostream>
#include<cstring>   //memset
#include<queue>   //队列
#include <algorithm>

using namespace std;

typedef pair<int, int> PII;   //队列

const int N = 110;
int n, m;
int g[N][N], d[N][N];

int bfs()
{
    queue<PII> q;        //创建空队列
    memset(d, -1, sizeof d);   //
    d[0][0] = 0;
    q.push({0, 0});  // 起点入队
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};   //上右下左
    while(q.size())
    {
        auto t = q.front();
        q.pop();
        for(int i = 0; i < 4; i ++)
        {
            int x = t.first + dx[i], y = t.second + dy[i];
            if(x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1)
            {
                d[x][y] = d[t.first][t.second] + 1;
                q.push({x, y});
            }
           
        }
    }
    return d[n - 1][m - 1];
   
}

int main()
{
    cin >> n >> m;
    
    for(int i = 0; i < n; i ++)
        for(int j = 0; j < m; j ++)
            cin >> g[i][j];
            
    cout << bfs() << endl;
    return 0;
}

25.3.25 17:01 刚从数据结构开始大概看了看,学是学完了,但是记不住啊

树图dfs,bfs

存储

有向图:
1.邻接矩阵(几乎不用)
g[a][b]存储a到b的信息,如果有权重存储的是权重,没有的话是bool,true表示有边,false表示没边
邻接矩阵不能存储重边

2.邻接表
跟哈希表一样,每个点都有自己的单链表,链表的点代表可以指向的点
链表头h[],连接新的点插入到链表头

dfs

模板
//树和图深度优先搜索的代码
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 100010, M = N * 2;   //M
int h[N], e[M], ne[M], idx;   //M,h[]链表头,e[]所有的边()
bool str[N];

void add(int a, int b) //有向图or无向图 树的创建,插入数,边
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx ++;
}

void dfs(int u)    //注意void
{
    str[u] = true;
    for(int i = h[u]; i != -1; i = ne[i])  //背就完了
    {
        int j = e[i];
        if(!str[j]) int s = dfs(j);
    }
    
}

int main()
{
    memset(h, -1, sizeof h);     //头结点指向-1

    dfs(1);
    return 0;
}
代码
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 100010, M = N * 2;   //M
int h[N], e[M], ne[M], idx, n;   //M
int ans = N;  
bool str[N];

void add(int a, int b) //有向图or无向图 树的创建,插入数,边
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx ++;
}

//返回以u为根的点数
int dfs(int u)    
{
    int sum = 1, res = 0;  //sum总点数, res以u为根最大点数 
    str[u] = true;
    for(int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if(!str[j]) 
        {
            int s = dfs(j);  //
            res = max(res, s);
            sum += s;
        }
        
    }
    res = max(res, n - sum);
    ans = min(ans, res);   //这里是min,所以ans必须初始化最大
    return sum;
}

int main()
{
    cin >> n;
    memset(h, -1, sizeof h);     //
    for(int i = 0; i < n - 1; i ++)  //
    {
        int a, b;
        cin >> a >> b;
        add(a, b), add(b, a);  //
    }
    dfs(1);
    cout << ans << endl;
    return 0;
}

bfs

框架
入队 queue
while queue不空
{
    t = 队头
    拓展t所有邻点x
    if(x未遍历)
    {
        x入队
        d[x] = d[t] + 1
    }
}
模板
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 100010;
int h[N], e[N], ne[N], idx;   //有向图单边,不需要2倍的M
int q[N], d[N];  //bfs本质是队列,q[N]存储队列,d[N]存储距离

int bfs()
{
    int tt, hh;  //hh队头, tt队尾
    //初始化
    q[0] = 1;  //队列从节点1开始
    d[1] = 0; //节点1的初始距离为0
    memset(d, -1, sizeof d);
    //逻辑
    while(hh <= tt)
    {
        int t = q[hh ++];  //从队头遍历队列
        for(int i = h[t]; i != -1; i = ne[i]) //遍历每个队列的链表
        {
            int j = e[i];  //先记录链表的值(指向的节点)
            if(d[t] == -1)  //因为初始化距离为-1 ,-1就是没有加入队列中
            {
                d[t] = d[j] + 1;  //记录d[t]距离根节点的距离
                q[++ tt] = j; //将j(节点的值)加入队尾
            } 
        }         
    }
    return d[N];  //返回到n的最短路径
}

int main()
{
    cin >> n;
    memset(h, -1, sizeof h);
    cout << bfs() << endl;
    return 0;
}
    
拓扑排序

25.3.27 11:53有向无环图

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

using namespace std;

const int N = 100010;
int n, m;
int h[N], e[N], ne[N], idx;
int d[N], q[N];  //q为拓扑排序的队列,d为入的个数

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

bool topsort()
{
    int hh = 0, tt = -1;   //

    for(int i = 1; i <= n; i ++)  //入为0的都加进队列
        if(!d[i]) q[++ tt] = i;

    while(hh <= tt)
    {
        int t = q[hh ++];
        for(int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            d[j] --;  //
            if(d[j] == 0)   //j 
                q[++ tt] = j;  //
        }
    }
    return tt == n - 1;  //
}

int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof h);
    for(int i = 0; i < m; i ++)
    {
        int x, y;
        cin >> x >> y;
        add(x, y);
        d[y] ++;
    }
    if(topsort())
    {
        for(int i = 0; i < n; i++)
        cout << q[i] << " ";
    }
    else puts("-1");
    return 0;
}

16进制10进制.txt 32.txt asm.txt Crctable.txt C标志符命名源程序.txt erre.txt erre2.txt ff.txt for循环的.txt list.log N皇后问题回溯算法.txt ping.txt re.txt source.txt winsock2.txt ww.txt 万年历.txt 万年历的算法 .txt 乘方函数桃子猴.txt 乘法矩阵.txt 二分查找1.txt 二分查找2.txt 二叉排序树.txt 二叉树.txt 二叉树实例.txt 二进制数.txt 二进制数2.txt 余弦曲线.txt 余弦直线.txt 傻瓜递归.txt 冒泡排序.txt 冒泡法改进.txt 动态计算网络最长最短路线.txt 十五人排序.txt 单循环链表.txt 单词倒转.txt 单链表.txt 单链表1.txt 单链表2.txt 单链表倒序.txt 单链表的处理全集.txt 双链表正排序.txt 反出字符.txt 叠代整除.txt 各种排序法.txt 哈夫曼算法.txt 哈慢树.txt 四分砝码.txt 四塔1.txt 四塔2.txt 回文.txt 图.txt 圆周率.txt 多位阶乘.txt 多位阶乘2.txt 大加数.txt 大小倍约.txt 大整数.txt 字符串查找.txt 字符编辑.txt 字符编辑技术(插入和删除) .txt 完数.txt 定长串.txt 实例1.txt 实例2.txt 实例3.txt 小写数字转换成大写数字1.txt 小写数字转换成大写数字2.txt 小写数字转换成大写数字3.txt 小字库DIY-.txt 小字库DIY.txt 小孩分糖果.txt 小明买书.txt 小白鼠钻迷宫.txt 带头结点双链循环线性表.txt 平方根.txt 建树和遍历.txt 建立链表1.txt 扫描码.txt 挽救软盘.txt 换位递归.txt 排序法.txt 推箱子.txt 数字移动.txt 数据结构.txt 数据结构2.txt 数据结构3.txt 数组完全单元.txt 数组操作.txt 数组递归退出.txt 数组递归退出2.txt 文件加密.txt 文件复制.txt 文件连接.txt 无向图.txt 时间陷阱.txt 杨辉三角形.txt 栈单元加.txt 栈操作.txt 桃子猴.txt 桶排序.txt 检出错误.txt 检测鼠标.txt 汉字字模.txt 汉诺塔.txt 汉诺塔2.txt 灯塔问题.txt 猴子和桃.txt 百鸡百钱.txt 矩阵乘法动态规划.txt 矩阵转换.txt 硬币分法.txt 神经元模型.txt 穷举搜索法.txt 符号图形.txt 简单数据库.txt 简单计算器.txt 简单逆阵.txt 线性顺序存储结构.txt 线索化二叉树.txt 绘制圆.txt 编随机数.txt 网络最短路径Dijkstra算法.txt 自我复制.txt 节点.txt 苹果分法.txt 螺旋数组1.txt 螺旋数组2.txt 试题.txt 诺汉塔画图版.txt 读写文本文件.txt 货郎担分枝限界图形演示.txt 货郎担限界算法.txt 质因子.txt 输出自已.txt 迷宫.txt 迷宫问题.txt 逆波兰计算器.txt 逆矩阵.txt 逆阵.txt 递堆法.txt 递归桃猴.txt 递归车厢.txt 递推.txt 逻辑移动.txt 链串.txt 链栈.txt 链表十五人排序.txt 链表(递归).txt 链队列.txt 队列.txt 阶乘递归.txt 阿姆斯特朗数.txt 非递归.txt 顺序栈.txt 顺序表.txt 顺序队列.txt 骑士遍历1.txt 骑士遍历2.txt 骑士遍历回逆.txt 黑白.txt
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值