算法学习笔记

chapter1 计划

   

 

chapter2 排序

1快速排序算法学习

//快速排序
#include<iostream>
#include<algorithm>
using namespace std;

const int N = 1e5 + 10;
int n;
int a[N];
/*
void quick_sort(int a[],int l,int r)
{
    if(l>=r)
        return;
    int x=a[l],i=l-1,j=r+1;  
    while(i<j)
    {
        do i++;while(a[i]<x);
        do j--;while(a[j]>x);
        if(i<j)
        swap(a[i],a[j]);
    }
    quick_sort(a,l,j);
    quick_sort(a,j+1,r);
}*/
int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        scanf("%d",&a[i]);
    }
    
    sort(a,a+n);
    for(int i=0;i<n;i++)
    {
        printf("%d ",a[i]);
    }
    return 0;
}

2 归并排序算法学习

//归并排序
#include<iostream>
#include<algorithm>
using namespace std;

const int N = 1e5 + 10;
int n;
int a[N],tmp[N];

void merge_sort(int a[],int l,int r)
{
    if(l>=r)
        return;
    int mid=(l+r)>>1;
    merge_sort(a,l,mid),merge_sort(a,mid+1,r);
    int i=l,j=mid+1,k=0;
    while(i<=mid&&j<=r)
    {
        if(a[i]<=a[j])
        {
        	tmp[k++]=a[i++];
		}
		else
		{
			tmp[k++]=a[j++];
		}
    }
    while(i<=mid)
    	tmp[k++]=a[i++];
    while(j<=r)
    	tmp[k++]=a[j++];
    for(i=l,j=0;i<=r;i++,j++)
    a[i]=tmp[j];
}
int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        scanf("%d",&a[i]);
    }
    
    //sort(a,a+n);
    merge_sort(a,0,n-1);
    for(int i=0;i<n;i++)
    {
        printf("%d ",tmp[i]);
    }
    return 0;
}

 

chapter3 图论

1.prim算法

#include<iostream>
#include<vector>
#include<queue>
using namespace std;

#define inf 1e9
struct edge
{
	int y;//指向的顶点
	int z;//距离
};

const int N=5010;
int x,y,z,n,m,sum,cnt;//sum为最终结果,cnt记录出队的顶点
int d[N],vis[N];//d为距离,vis记录是否被出队
vector<edge> e[N];//记录图信息
priority_queue<pair<int,int>> q;//建立优先队列(最大的排在最上面(大顶堆),若在距离前加负号可将距离最小的放到堆顶)

bool prim(int s)
{
	//初始化距离d
	for(int i=0;i<=n;i++)
	{
		d[i]=inf;
	}
	d[s]=0;//从1号结点开始
	q.push({0,s});//距离为0,顶点为1
	while(q.size())
	{
		int u=q.top().second;//取出队顶 顶点
		q.pop();
		if(vis[u])
			continue;
		vis[u]=1;
		sum+=d[u];
		cnt++;
		for(auto t:e[u])
		{
			int y=t.y;//点
			int z=t.z;//距离
			if(d[y]>z)
			{
				d[y]=z;
				q.push({-d[y],y});
			}
			
		}
	}
	
	return cnt==n;
}
//建立大根堆
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=0;i<m;i++)
	{
		scanf("%d%d%d",&x,&y,&z);
		e[x].push_back({y,z});//无向图
		e[y].push_back({x,z});
	}
	if(!prim(1))
	{
		printf("orz\n");
	}
	else
	{
		printf("%d",sum);
	}
	return 0;
}

 

2.Kruscal算法

image-20240421113510162

 

//利用并查集来建立最优二叉树
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const int N = 2e5 + 10;
struct edge
{
    int u, v, w;
    // 重载小于号
    bool operator<(const edge &t) const
    {
        return w < t.w;
    }
} e[N];

int u, v, w, m, n;
int fa[N], sum, ans = 0; // sum为最小生成树边权之和,ans为构成最小生成树的边数

int find(int x)
{
    if (fa[x] == x)
        return x;
    else
        return fa[x] = find(fa[x]);
}
bool kruskal()
{
    for (int i = 1; i <= n; i++)
        fa[i] = i;
    sort(e, e + m);
    for (int i = 0; i < m; i++)
    {
        int x = find(e[i].u);
        int y = find(e[i].v);
        if (x != y)
        {
            // 可以进行合并
            fa[x] = y;
            ans++;
            sum += e[i].w;
        }
    }
    return ans == n - 1;
}

int main()
{
    cin >> n >> m;
    for (int i = 0; i < m; i++)
    {
        cin >> u >> v >> w;
        e[i] = {u, v, w};
    }
    if (kruskal())
    {
        cout << sum << endl;
    }
    else
    {
        cout << "orz" << endl;
    }
    return 0;
}

 

3 kahn算法(拓扑排序)

image-20240229110132020

/*
注意:以下代码只适用于给结点的数字从1开始,且连续的情况下,用于排序
*/
#include<iostream>
#include<queue>
#include<vector>
using namespace std;
const int N=1e5+10;
int n,m,a,b;//n为结点数,m为边数
vector<int> e[N],tp;
int din[N];//保存结点的入度
bool toposort()
{
    queue<int> q;//在队列中保存入度为0的结点
    for(int i=1;i<=n;i++)
    {
        if(din[i]==0)
        {
            q.push(i);
        }
    }
    while(q.size())
    {
        int x=q.front();
        q.pop();
        tp.push_back(x);
        for(auto y:e[x])
        {
            if(--din[y]==0)
            {
                q.push(y);
            }
        }
    }
    return tp.size()==n;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++)
    {
        scanf("%d%d",&a,&b);
        e[a].push_back(b);
        din[b]++;
    }
    if(!toposort())
    {
        printf("没有拓扑序\n");
    }
    else
    {
        for(auto t:tp)
        {
            printf("%d ",t);
        }
    }
    return 0;
}

 

4 DFS算法

image-20240229110705258

#include<iostream>
#include<algorithm>
#include<vector>
#include <string.h>
using namespace std;

const int N = 1e5+10;
int m,n,a,b;
vector<int> e[N],tp;
int c[N];//染色数组
bool dfs(int x)
{
    c[x]=-1;
    for(int y:e[x])
    {
        if(c[y]<0)
            return 0;
        else if(!c[y])
        {
            if(!dfs(y))
                return 0;
        }
    }
    c[x]=1;
    tp.push_back(x);
    return 1;

}
bool toposort()
{
    memset(c,0,sizeof(c));
    for(int i=1;i<=n;i++)
    {
        if(!c[i])
        {
            if(!dfs(i))
                return 0;
        }
    }
    reverse(tp.begin(),tp.end());
    return 1;
}
int main()
{
   scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++)
    {
        scanf("%d%d",&a,&b);
        e[a].push_back(b);
    } 
    if(!toposort())
    {
        cout<<"不能进行拓扑排序"<<endl;
    }
    else
    {
        for(auto y:tp)
        {
            cout<<y<<" "<<endl;
        }
    }
    return 0;
}

5 最短路Dijkstra算法

image-20240229141135846

/*
P4779 【模板】单源最短路径(标准版)
https://www.luogu.com.cn/problem/P4779
*/
#include <iostream>
#include <vector>
#include <queue>

using namespace std;

#define INF 1e9;
struct edge
{
    int v, w;
};
const int N = 2e5 + 10;
int n, m, s, u, v, w;
vector<edge> e[N]; // 存储输入信息
int d[N],vis[N];//d保存距离,vis判断是否被访问
void dijkstra(int s)
{
    priority_queue<pair<int,int>> q;
    //初始化距离
    for(int i=1;i<=n;i++)
        d[i]=INF;
    d[s]=0;//起点
    q.push({0,s});
    while(q.size())
    {
        int u=q.top().second;
        q.pop();
        if(vis[u])
            continue;
        vis[u]=1;
        for(auto t:e[u])
        {
            int v=t.v,w=t.w;
            if(d[v]>d[u]+w)
            {
                d[v]=d[u]+w;
                q.push({-d[v],v});
            }
        }
    }
    
}
int main()
{
    cin >> n >> m >> s;
    for (int i = 0; i < m; i++)
    {
        cin >> u >> v >> w;
        e[u].push_back({v,w});
    }
    dijkstra(s);
    for(int i=1;i<=n;i++)
    {
        cout<<d[i]<<" ";
    }
    return 0;
}

image-20240229161044783

6 SPFA判负环以及解决有负边权的单元最短路径问题

----有问题
//DFS_spfa 判负环 会卡点 #9
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int inf=0x3f3f3f3f;
const int N=2010,M=6010;
int n,m;
int to[M],ne[M],w[M],h[N],tot;
int d[N],vis[N];

void add(int a,int b,int c){
  to[++tot]=b;w[tot]=c;
  ne[tot]=h[a];h[a]=tot;
}
bool spfa(int u){ //判负环
  vis[u]=1;
  for(int i=h[u];i;i=ne[i]){
    int v=to[i];
    if(d[v]>d[u]+w[i]){
      d[v]=d[u]+w[i];
      if(vis[v]||spfa(v))return 1;
    }
  }
  vis[u]=0;
  return 0;
}
int main(){
  int T; scanf("%d",&T);
  while(T--){
    tot=0; memset(h,0,sizeof(h));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
      int u,v,w;
      scanf("%d%d%d",&u,&v,&w);
      add(u,v,w);
      if(w>=0)add(v,u,w);;
    }
    memset(d,0x3f,sizeof d);d[1]=0;
    memset(vis,0,sizeof vis);
    puts(spfa(1)?"YES":"NO");
  }
  return 0;
}

7最短路 Floyd 算法(全源最短路径)

void floyed
{
	for(int k=1;k<=n;k++)
    {
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
            }
        }
        
    }
}

8 Jhonson最短路(思想重要,比赛不易遇到--难 理解欠缺)

image-20240229192516506

image-20240229203759747

// Luogu P5905 【模板】Johnson 全源最短路
 #include <iostream>
#include <vector>
#include <queue>
#include<cstring>
using namespace std;

struct edge
{
    int v, w;
};
const int inf=1000000000;
const int N = 6010;

vector<edge> e[N];
int n, m, u, v, w, vis[N],cnt[N];
long long h[N],d[N];
//闭环
void spfa()
{
    memset(h,63,sizeof h);
    memset(vis,0,sizeof vis);
    queue<int>q;
    h[0]=0,vis[0]=1,q.push(0);
    while(q.size())
    {
        int u=q.front();
        q.pop();
        vis[u]=0;
        for(auto t:e[u])
        {
            int v=t.v,w=t.w;
            if(h[v]>h[u]+w)
            {
                h[v]=h[u]+w;
                cnt[v]=cnt[u]+1;
                if(cnt[v]>n)
                {
                    cout<<-1<<endl;
                    exit(0);
                }
                if(!vis[v])
                {
                    q.push(v);
                    vis[v]=1;
                }
            }
        }
    }
}
void dijkstra(int s)
{
    priority_queue<pair<long,long>> q;
    for(int i=1;i<=n;i++)
        d[i]=inf;
    memset(vis,0,sizeof vis);
    d[s]=0;
    q.push({0,s});
    while(q.size())
    {
        int u=q.top().second;
        q.pop();
        if(vis[u])
            continue;
        vis[u]=1;
        for(auto t:e[u])
        {
            int v=t.v,w=t.w;
            if(d[v]>d[u]+w)
            {
                d[v]=d[u]+w;
                if(!vis[v])
                q.push({-d[v],v});
            }
        }
    }

}
int main()
{
    cin>>n>>m;
    for(int i=0;i<m;i++)
    {
        cin>>u>>v>>w;
        e[u].push_back({v,w});
    }
    //加虚拟边
    for(int i=1;i<=n;i++)
    {
        e[0].push_back({i,0});
    }
    spfa();
    //构造新边
    for(int i=1;i<=n;i++)
    {
        for(auto &t:e[i])
        {
            t.w+=h[i]-h[t.v];
        }
    }
    for(int i=1;i<=n;i++)
    {
        dijkstra(i);
        long long ans=0;
        for(int j=1;j<=n;j++)
        {
            if(d[j]==inf)
            {
                ans+=(long long)j*inf;
            }
            else
            {
                ans+=(long long)j*(d[j]+h[j]-h[i]);
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
}

 

9 无向图的最小环问题

Luogu P6175 无向图的最小环问题

#include <iostream>
#include<cstring>
#include <algorithm>
using namespace std;

const int N = 5010;

int n, m, u, v, w, d[N][N], W[N][N], ans = 1e8;

void floyd()
{
    for (int k = 1; k <= n; k++)
    {
        for (int i = 1; i < k; i++)
        {
            for (int j = i + 1; j < k; j++)
            {
                ans = min(ans, d[i][j] + W[k][j] + W[i][k]);
            }
        }

        for (int i = 1; i <= n; i++)
        {
            for (int j = 1; j <= n; j++)
            {
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
            }
        }
    }
    if (ans == 1e8)
    {
        cout << "No solution." << endl;
    }
    else
        cout << ans << endl;
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            if (i != j)
                W[i][j] = 1e8;
    for (int i = 0; i < m; i++)
    {
        cin >> u >> v >> w;
        W[u][v] = W[v][u] = w;
    }
    memcpy(d, W, sizeof d);
    floyd();
}

 

10 最近公共祖先

1)倍增算法
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10, M = 2 * N;
struct edge
{
    int v, ne;
} e[M]; // 存边集
int m, n, s, a, b;
int fa[N][22], dep[N]; // 这里的fa[u][i]表示u向上跳2^i之后的点,i是从0-20,当i=0时即为u的父节点,dep保存每个结点的深度

int h[N], idx; // h存点的第一条出边.idx全局变量
void add(int a, int b)
{
    e[++idx] = {b, h[a]};
    h[a] = idx;
}
void dfs(int u, int father)
{
    dep[u] = dep[father] + 1;
    fa[u][0] = father;
    for (int i = 0; i <= 20; i++)
    {
        fa[u][i + 1] = fa[fa[u][i]][i]; // 相当于跳两步——>2^i 除以2,先向上跳一半,再从当前的点向上跳另一半
    }
    for (int i = h[u]; i; i = e[i].ne)
        if (e[i].v != father)
            dfs(e[i].v, u);
}
int lca(int x, int y)
{
    if (dep[x] < dep[y])
        swap(x, y);
    // 先跳到同一层
    for (int i = 20; i >= 0; i--)
    {
        if (dep[fa[x][i]] >= dep[y])
            x = fa[x][i];
    }
    if (x == y)
        return y;
    // 再跳到LCA的下一层
    for (int i = 20; i >= 0; i--)
    {
        if (fa[x][i] != fa[y][i]) // 若相同,则为x,y的公共祖先
            x = fa[x][i], y = fa[y][i];
    }
    return fa[x][0];
}
int main()
{
    scanf("%d%d%d", &n, &m, &s);
    // memset(h,-1,sizeof (h));
    for (int i = 1; i < n; i++)
    {
        scanf("%d%d", &a, &b);
        add(a, b);
        add(b, a);
    }
    dfs(s, 0);
    while (m--)
    {
        scanf("%d%d", &a, &b);
        printf("%d\n", lca(a, b));
    }
    return 0;
}

 

2)Tarjan(塔扬)算法——最近公共祖先2

不太熟练,之后需要巩固

 

/**
 * 该算法基于并查集
 * fa存储父节点
 * e[]存储结点信息
 * vis[]打标记,判断是否被访问
 * query记录查询
 * ans[]存储查询结果
 */

#include <bits/stdc++.h>
using namespace std;

const int N = 5e5 + 10;
const int M = 2 * N;
int fa[N], vis[N], ans[M]; // 存储父节点
vector<int> e[N];
vector<pair<int, int>> query[N];
int n, m, s, x, y, a, b;
int find(int x)
{
    if (fa[x] == x)
        return x;
    else
        return fa[x] = find(fa[x]);
}
void tarjan(int u)
{
    vis[u] = true;
    for (auto v : e[u])
    {
        if (!vis[v])
        {
            tarjan(v);
            fa[v] = u;
        }
    }
    for (auto q : query[u])
    {
        int y = q.first, i = q.second;
        if (vis[y])
            ans[i] = find(y);
    }
}
int main()
{
    cin >> n >> m >> s;
    for (int i = 0; i < n - 1; i++)
    {
        cin >> x >> y;
        e[x].push_back(y);
        e[y].push_back(x);
    }
    for (int j = 1; j <= m; j++)
    {
        cin >> a >> b;
        query[a].push_back({b, j});
        query[b].push_back({a, j});
    }
    for (int i = 1; i < N; i++)
    {
        fa[i] = i;
    }
    tarjan(s);
    for (int i = 1; i <= m; i++)
    {
        cout << ans[i] << endl;
    }
    return 0;
}

 

 

chapter4 数据结构

 

1 并查集

image-20240301153628650

#include <iostream>

using namespace std;
const int N = 2e5 + 10;

int n, m, x, y, z, f[N]; // 这里的f数组保存结点的根节点,以便于压缩路径
int find(int x)
{
    if (f[x] == x)
        return x;
    else
        return f[x] = find(f[x]);//进行路径压缩
}
void unionset(int x, int y)
{
    f[find(x)] = find(y);//合并
}
int main()
{
    cin >> n >> m;
    // 初始化f数组
    for (int i = 1; i <= n; i++)
        f[i] = i;
    for (int i = 0; i < m; i++)
    {
        cin >> z >> x >> y;
        if (z == 1)
        {
            // 进行合并
            unionset(x, y);
        }
        else
        {
            int a = find(x), b = find(y);
            // 判断是否在同意集合中
            if (a == b)

                cout << "Y" << endl;

            else
                cout << "N" << endl;
        }
    }
    return 0;
}

 

2 线段树+懒标记

image-20240301203254265

本题需要之后再次复习,掌握不够完善

P3374 【模板】树状数组 1
//解决区间更新,统计某个区间和等问题  在大量更新、查找混合时,快速方便
#include <iostream>

using namespace std;
#define le p << 1
#define re p << 1 | 1

const int N = 5e5 + 10;
struct tree
{
    int l, r, sum;
} tr[N * 4];
int w[N]; // 初始化值
int n, m, z, x, y;
// 建立线段树
void build(int p, int l, int r)
{
    tr[p] = {l, r, w[l]};
    if (l == r)
        return; // 叶子结点
    int mid = (l + r) >> 1;
    build(le, l, mid);
    build(re, mid + 1, r);
    tr[p].sum = tr[le].sum + tr[re].sum;
}
// 点修改
/**
 * 这里的p为1,从根节点开始查找
 * x为更改的位置
 * k为要更改的值
 */
void update(int p, int x, int k)
{
    if (tr[p].l == x && tr[p].r == x)
    {
        tr[p].sum += k;
        return;
    }
    int mid = tr[p].l + tr[p].r >> 1;
    if (x <= mid)
        update(le, x, k);
    else
        update(re, x, k);
    tr[p].sum = tr[le].sum + tr[re].sum;
}
/**
 * 区间查找
 *
 */
int query(int p, int x, int y)
{
    if (tr[p].l >= x && tr[p].r <= y)
        return tr[p].sum;
    int mid = tr[p].l + tr[p].r >> 1;
    int sum = 0;
    if (x <= mid)
        sum += query(le, x, y);
    if (y > mid)
        sum += query(re, x, y);

    return sum;
}
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        cin >> w[i];
    }
    build(1, 1, n);
    for (int i = 0; i < m; i++)
    {
        cin >> z >> x >> y;
        if (z == 1)
        {
            update(1, x, y);
        }
        else
        {
            cout << query(1, x, y) << endl;
        }
    }

    return 0;
}

3 树状数组

image-20240317210301191

image-20240317210340607

image-20240317210414780

Luogu P3374 【模板】树状数组 1

//向后修,向前查
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
int n, m, k, x, y;
int a[N], s[N];
/**
 * 向前查,向后修
 */

// 提取x的低位二次幂数
/**
 * 3->011 & 101 得到 001 则3的低位2次幂数为1
 *
 * 在树状数组中 x+x的低位二次幂数等于父结点的下标
 * 如 4 其二次幂数为100  ->4+4=8,则4的父节点下标为8
 *
 * 同一层,低位的二次幂数相同
 */
int lowbit(int x)
{
    return x & -x;
}
// 向后修
void change(int x, int k)
{
    while (x <= n)
    {
        s[x] += k;
        // 向后面的父节点依次加
        x += lowbit(x);
    }
}
// 向前查
int query(int x)
{
    int t = 0;
    while (x)
    {
        t += s[x];
        x -= lowbit(x);
    }
    return t;
}
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        change(i, a[i]); // 向后构造树状数组
    }
    for (int i = 1; i <= m; i++)
    {
        cin >> k >> x >> y;
        if (k == 1)
        {
            change(x, y);
        }
        else
        {
            cout << query(y) - query(x - 1) << endl;
        }
    }
    return 0;
}

Luogu P3368 【模板】树状数组 2

#include <bits/stdc++.h>
using namespace std;

const int N = 5e5 + 10;
int a[N], s[N];
int n, m, op, x, y, k;

int lowbit(int x)
{
    return x & -x;
}
// 向后修
void change(int x, int k)
{
    while (x <= n)
    {
        s[x] += k;
        x += lowbit(x);
    }
}
// 向前查
int query(int x)
{
    int t = 0;
    while (x)
    {
        t += s[x];
        x -= lowbit(x);
    }
    return t;
}
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    for (int i = 1; i <= m; i++)
    {
        cin >> op >> x;
        if (op == 1)
        {
            cin >> y >> k;
            // 差分
            change(x, k), change(y + 1, -k);
        }
        else
            printf("%d\n", a[x] + query(x));
    }
    return 0;
}

4 单调队列

image-20240331093653346

image-20240331093715395

Luogu P1886 滑动窗口 /【模板】单调队列

/**
 * 单调队列
 * 用两个指针维护单调队列,h为队头指针,t为队尾指针
 * 队列中的元素范围为:i-k+1<j<i 这里的j用数组q保存
 */
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
LL a[N], q[N];
int n, k;
int main()
{
    cin >> n >> k;

    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    // 求最小值
    int h = 1, t = 0;            // 清空队列
    for (int i = 1; i <= n; i++) // 枚举序列
    {
        while (h <= t && q[h] < i - k + 1) // 队头出队(队列不空,且对头元素滑出窗口)
            h++;
        while (h <= t && a[q[t]] >= a[i]) // 队尾出队(队列不空,且新元素更优)
            t--;
        q[++t] = i;
        if (i >= k)
            printf("%d ", a[q[h]]);
    }
    puts("");
    // 求最大值
    h = 1, t = 0;
    for (int i = 1; i <= n; i++)
    {
        while (h <= t && q[h] < i - k + 1)
            h++;
        while (h <= t && a[q[t]] <= a[i])
            t--;
        q[++t] = i;
        if (i >= k)
            printf("%d ", a[q[h]]);
    }
    return 0;
}

Luogu P1440 求m区间内的最小值

#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 10;
int a[N], q[N];
int n, m;

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);

    if (n >= 1)
        puts("0");
    // 求最小值
    int h = 1, t = 0;
    for (int i = 1; i < n; i++)
    {
        while (h <= t && q[h] < i - m + 1)
            h++;
        while (h <= t && a[q[t]] >= a[i])
            t--;
        q[++t] = i;
        // if (i >= m)
        // cout << a[q[h]] << endl;
        printf("%d\n", a[q[h]]);
    }
    return 0;
}

Luogu P2032 扫描

#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 10;
int a[N], q[N];
int n, k;

int main()
{
    cin >> n >> k;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    // 求区间最大值
    int h = 1, t = 0;
    for (int i = 1; i <= n; i++)
    {
        while (h <= t && q[h] < i - k + 1)
            h++;
        while (h <= t && a[q[t]] <= a[i])
            t--;
        q[++t] = i;
        if (i >= k)
            cout << a[q[h]] << endl;
    }
    return 0;
}

[Luogu P2216 HAOI2007]理想的正方形

 

#include <bits/stdc++.h>
using namespace std;
const int N = 1010, INF = 1e9;
int w[N][N];                // 保存矩阵
int minv[N][N], maxv[N][N]; // 按行计算最大值和最小值
int q[N], a[N], b[N], c[N], d[N];
int n, m, k;
// 区间最大值
void get_max(int a[], int b[], int m)
{
    int h = 1, t = 0;
    for (int i = 1; i <= m; i++)
    {
        while (h <= t && q[h] < i - k + 1)
            h++;
        while (h <= t && a[q[t]] <= a[i])
            t--;
        q[++t] = i;
        b[i] = a[q[h]]; // 记录最大值
    }
}
// 区间最小值
void get_min(int a[], int b[], int m)
{
    int h = 1, t = 0;
    for (int i = 1; i <= m; i++)
    {
        while (h <= t && q[h] < i - k + 1)
            h++;
        while (h <= t && a[q[t]] >= a[i])
            t--;
        q[++t] = i;
        b[i] = a[q[h]]; // 记录最小值
    }
}
int main()
{
    cin >> n >> m >> k;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            scanf("%d", &w[i][j]);
    // 按行计算
    for (int i = 1; i <= n; i++)
    {
        get_max(w[i], maxv[i], m);
        get_min(w[i], minv[i], m);
    }
    int res = INF;
    // 枚举列
    for (int j = k; j <= m; j++)
    {
        for (int i = 1; i <= n; i++)
        {
            a[i] = maxv[i][j];
            b[i] = minv[i][j];
        }
        get_max(a, c, n);
        get_min(b, d, n);
        for (int i = k; i <= n; i++)
            res = min(res, c[i] - d[i]);
    }
    cout << res << endl;
    return 0;
}

5 哈夫曼树

image-20240506164504805

 

image-20240506164314776

image-20240506164118280

 

#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;
typedef long long ll;
/**
 * 其原理是构建k叉哈夫曼树
 */
struct node
{
    ll w, h;
    node() { w = 0, h = 0; }
    /**
     * 这种方法比在构造函数体内逐一对成员变量赋值要更高效,
     * 因为它直接在对象创建时就将成员变量初始化为指定的值。
     */
    node(ll w, ll h) : w(w), h(h) {}
    //这里其实是由小到大进行排序,使得在优先队列中,最小的(出现次数最少)在堆顶
    bool operator<(const node &a) const
    {
        return w == a.w ? h > a.h : w > a.w;
    }
};

priority_queue<node> q;
ll ans, n, k, w;

int main()
{
    cin >> n >> k;
    for (int i = 1; i <= n; i++)
    {
        cin >> w;
        q.push(node(w, 1));
    }
    while ((q.size() - 1) % (k - 1) != 0)
        q.push(node(0, 1));

    // 构造哈夫曼树
    while (q.size() >= k)
    {
        ll h = -1, w = 0;
        for (int i = 0; i < k; i++)
        {
            node t = q.top();
            q.pop();
            h = max(h, t.h);
            w += t.w;
        }
        ans += w;
        q.push(node(w, h + 1));
    }
    printf("%lld\n%lld\n", ans, q.top().h - 1);
    return 0;
}

 

chapter5 动态规划

1 线性DP

1) 最长上升子序列 二分优化
/**
 * 主要原理:
 * 保证子序列最优
 * 若大于当前值,则追加到末尾
 * 否则,替换当前子序列中第一个大于它的元素
 */
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N], n, len; // a为输入的序列,b保存当前最优子序列
inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
    {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}
// 注意是最小化查找
int find(int x)
{
    int l = 0, r = len + 1;
    while (l + 1 < r)
    {
        int mid = (l + r) >> 1;
        if (b[mid] >= x)
            r = mid;
        else
            l = mid;
    }
    return r;
}
/**
 * b数组中的元素并不是最大的上升子序列,仅仅数量与最长上升子序列的一致
 */
int main()
{
    n = read();
    for (int i = 1; i <= n; i++)
        a[i] = read();
    b[1] = a[1];
    len = 1;
    for (int i = 2; i <= n; i++)
    {
        if (b[len] < a[i])
        {
            b[++len] = a[i];
        }
        else
        {
            //b[find(a[i])] = a[i];
            /**
             * lower_bound 返回指向第一个值不小于val的位置,也就是返回第一个大于等于val值的位置。(通过二分查找)
             * 与其相反的是upper_bound
            */
            *lower_bound(b,b+len,a[i])=a[i];
        }
    }
    cout << len << endl;
    return 0;
}
2)最长公共子序列

image-20240309163754956

P1439 【模板】最长公共子序列 - 洛谷

//n^2的做法


//对于本题要考虑到全排列的性质
#include<iostream>
#include<cstdio>
using namespace std;
int a[100001],b[100001],map[100001],f[100001];
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){scanf("%d",&a[i]);map[a[i]]=i;}
	for(int i=1;i<=n;i++){scanf("%d",&b[i]);f[i]=0x7fffffff;}
	int len=0;
	f[0]=0;
	for(int i=1;i<=n;i++)
	{
		int l=0,r=len,mid;
		if(map[b[i]]>f[len])f[++len]=map[b[i]];
		else 
		{
		while(l<r)
		{	
		    mid=(l+r)/2;
		    if(f[mid]>map[b[i]])r=mid;
			else l=mid+1; 
		}
		f[l]=min(map[b[i]],f[l]);
     	}
    }
    cout<<len;
    return 0
}
3)最长公共子串
#include <bits/stdc++.h>
using namespace std;
const int N = 255;
char a[N], b[N];
int f[N][N];
int ans;
int main()
{
    cin >> a + 1 >> b + 1;
    int la = strlen(a + 1), lb = strlen(b + 1);
    int max = -1, flag = 0;
    for (int i = 1; i <= la; i++)
    {
        for (int j = 1; j <= lb; j++)
        {
            if (a[i] == b[j])
            {
                f[i][j] = f[i - 1][j - 1] + 1;
            }
            else
                f[i][j] = 0;
            if (max < f[i][j])
            {
                max = f[i][j];
                flag = i;
            }
        }
    }
    cout << max << endl;
    for (int i = max - 1; i >= 0; i--)
        cout << a[flag - i];
    puts("");
}
4)记忆化搜索:数字三角形

image-20240324212716635

Luogu P1216 [USACO1.5][IOI1994]数字三角形 Number Triangles

#include <bits/stdc++.h>
using namespace std;
const int N = 1010;

int f[N][N]; // 计算和
int a[N][N]; // 存储树塔
int n;
int dfs(int x, int y)
{
    if (f[x][y] != -1) // 已经被搜索过
        return f[x][y];
    if (x == n) // 到达底部边界
        return f[x][y] = a[x][y];

    return f[x][y] = max(dfs(x + 1, y), dfs(x + 1, y + 1)) + a[x][y];
}
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= i; j++)
        {
            cin >> a[i][j];
        }
    }
    memset(f, -1, sizeof f);
    dfs(1, 1);
    cout << f[1][1] << endl;
}

 

5)方格dp---数字三角形

image-20240324215233102

Luogu P1216 [USACO1.5][IOI1994]数字三角形 Number Triangles

#include <bits/stdc++.h>
using namespace std;
const int N = 1010;

int f[N][N]; // 计算和
int a[N][N]; // 存储树塔
int n;
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= i; j++)
        {
            cin >> a[i][j];
        }
    }
    for (int i = n; i >= 1; i--)
        for (int j = 1; j <= i; j++)
            f[i][j] = max(f[i + 1][j], f[i + 1][j + 1]) + a[i][j];
    cout << f[1][1] << endl;
    return 0;
}

[Luogu P1004 NOIP2000 提高组] 方格取数

#include <bits/stdc++.h>
using namespace std;

int a[12][12], s[12][12][12][12];
int n, x, y, v;

/**
 * 此题要考虑到思维dp
 * 由于分成两次dp处理时,由于不确定过多,无法求得最优解,因此从起点开始,让两个点一同开始走
 * f[i,j,x,y] 最后,x+y==i+j 终点相同
 */
int main()
{
    cin >> n;
    for (int i = 1; i; i++)
    {

        cin >> x >> y >> v;
        if (x == 0 && y == 0 && v == 0)
            break;
        else
            a[x][y] = v;
    }
    for (int i = n; i >= 1; i--)
        for (int j = n; j >= 1; j--)
            for (int x = n; x >= 1; x--)
                for (int y = n; y >= 1; y--)
                {
                    int &t = s[i][j][x][y];
                    // 上一步
                    // t = s[i][j][x][y] = max(max(s[i - 1][j][x - 1][y], s[i - 1][j][x][y - 1]), max(s[i][j - 1][x - 1][y], s[i][j - 1][x][y - 1]));
                    t = s[i][j][x][y] = max(max(s[i + 1][j][x + 1][y], s[i + 1][j][x][y + 1]), max(s[i][j + 1][x + 1][y], s[i][j + 1][x][y + 1]));
                    if (x == i && y == j)
                    {
                        t += a[i][j]; // 走到同一个点
                    }
                    else
                        t += a[i][j] + a[x][y];
                }

    cout << s[1][1][1][1] << endl;
}
#include <bits/stdc++.h>
using namespace std;

int a[12][12], s[24][12][12];
int n, x, y;

/**
 * 此题要考虑到思维dp
 * 由于分成两次dp处理时,由于不确定过多,无法求得最优解,因此从起点开始,让两个点一同开始走
 * f[i,j,x,y] 最后,x+y==i+j 终点相同
 * 利用条件进行降维优化
 * i+j=x+y=k,因此可以进行优化,s[k][i][x] 表示走了k步,1到达了i行,2到达了j行,1,2表示两条路径
 */
int main()
{
    cin >> n;
    while (cin >> x >> y >> a[x][y], x)
        ;
    for (int k = 2 * n; k >= 2; k--)
        for (int i = n; i >= 1; i--)
            for (int x = n; x >= 1; x--)
            {
                int j = k - i, y = k - x;
                if (j >= 1 && j <= n && y >= 1 && y <= n)
                {
                    int &t = s[k][i][x];
                    t = max(max(s[k + 1][i + 1][x + 1], s[k + 1][i + 1][x]), max(s[k + 1][i][x + 1], s[k + 1][i][x]));
                    if (i == x)
                    {
                        t += a[i][j]; // 走到同一个点
                    }
                    else
                        t += a[i][j] + a[x][y];
                }
            }

    cout << s[2][1][1] << endl;
}

[Luogu P1006 NOIP2008 提高组] 传纸条

#include <bits/stdc++.h>

using namespace std;
const int N = 55;
int a[N][N], m, n;
int s[N * 2][N][N];
/**
 * 只要起点和终点一致
 * 可以看作均从起点开始出发,到达终点两次
 */
int main()
{
    cin >> m >> n;
    for (int i = 1; i <= m; i++)
        for (int j = 1; j <= n; j++)
            cin >> a[i][j];
    for (int k = m + n; k >= 2; k--)
    {
        for (int i = m; i >= 1; i--)
        {
            for (int x = m; x >= 1; x--)
            {
                int j = k - i, y = k - x;
                if (j >= 1 && j <= n && y >= 1 && y <= n)
                {
                    int &t = s[k][i][x];
                    t = max(max(s[k + 1][i + 1][x + 1], s[k + 1][i + 1][x]), max(s[k + 1][i][x + 1], s[k + 1][i][x]));
                    if (i == x)
                        t += a[i][j];
                    else
                        t += a[i][j] + a[x][y];
                }
            }
        }
    }
    cout << s[2][1][1] << endl;
}
6)编辑距离问题

image-20240330161857679

#include <bits/stdc++.h>
using namespace std;
const int N = 2010;
char a[N], b[N];
int f[N][N];
int main()
{
    cin >> a >> b;
    int la = strlen(a), lb = strlen(b);
    for (int i = 1; i <= la; i++)
        f[i][0] = i;
    for (int j = 1; j <= lb; j++)
        f[0][j] = j;
    for (int i = 1; i <= la; i++)
    {
        for (int j = 1; j <= lb; j++)
        {
            if (a[i - 1] == b[j - 1])
                f[i][j] = f[i - 1][j - 1];
            else
            {
                f[i][j] = min(min(f[i][j - 1], f[i - 1][j]), f[i - 1][j - 1]) + 1;
            }
        }
    }
    cout << f[la][lb] << endl;
}
/**
 * 使用滚动数组进行优化
 * 降低空间复杂度
 */

#include <bits/stdc++.h>
using namespace std;
const int N = 2010;
char a[N], b[N];
int f[N];
int t1, t2;
int main()
{
    cin >> a >> b;
    int la = strlen(a), lb = strlen(b);
    for (int j = 1; j <= lb; j++)
        f[j] = j; // f[0][j]=j;
    for (int i = 1; i <= la; i++)
    {
        t1 = f[0]++; // t1等价于f[i-1][0]
        for (int j = 1; j <= lb; j++)
        {
            t2 = f[j];
            if (a[i - 1] == b[j - 1])
                // f[i][j] = f[i - 1][j - 1];
                f[j] = t1;
            else
            {
                // f[i][j] = min(min(f[i][j - 1], f[i - 1][j]), f[i - 1][j - 1]) + 1;
                f[j] = min(t1, min(f[j - 1], f[j])) + 1;
            }
            t1 = t2; // t1等价于f[i-1][j-1]
        }
    }
    cout << f[lb] << endl;
}

2 背包DP

1) 01背包问题

image-20240330163845826

 

image-20240330164259252

 

image-20240330165248608

image-20240330165508879

[Luogu P2871 USACO07DEC]Charm Bracelet S

//朴素写法,会超内存限制

#include <bits/stdc++.h>
using namespace std;
const int N = 3410;
const int M = 13000;

int w[N], d[N], f[N][M], n, m;

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        cin >> w[i] >> d[i];
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            if (j < w[i])
                f[i][j] = f[i - 1][j];
            else
                f[i][j] = max(f[i - 1][j], f[i - 1][j - w[i]] + d[i]);
        }
    }
    cout << f[n][m] << endl;
}
//内存优化

#include <bits/stdc++.h>
using namespace std;
const int N = 3410;
const int M = 13000;

int w[N], d[N], f[M], n, m;

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        cin >> w[i] >> d[i];
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = m; j >= w[i]; j--)
        {
            f[j] = max(f[j], f[j - w[i]] + d[i]);
        }
    }
    cout << f[m] << endl;
    return 0;
}

[Luogu P1048 NOIP2005 普及组] 采药

#include <bits/stdc++.h>
using namespace std;

const int N = 1010;
int t, m, f[N], T[N], V[N];

int main()
{
    cin >> t >> m;
    for (int i = 1; i <= m; i++)
        cin >> T[i] >> V[i];
    for (int i = 1; i <= t; i++)
    {
        for (int j = t; j >= T[i]; j--)
        {
            f[j] = max(f[j], f[j - T[i]] + V[i]);
        }
    }
    cout << f[t] << endl;
    return 0;
}

[ Luogu P1049 NOIP2001 普及组] 装箱问题

#include <bits/stdc++.h>
using namespace std;
const int N = 2e4 + 10;
int v[32], f[N];
int main()
{
    int m, n;
    cin >> m >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> v[i];
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = m; j >= v[i]; j--)
        {
            f[j] = max(f[j], f[j - v[i]] + v[i]);
        }
    }
    cout << m - f[m] << endl;
    return 0;
}

[ Luogu P1060 NOIP2006 普及组] 开心的金明

#include <bits/stdc++.h>
using namespace std;

const int N = 3e4 + 10;
int n, m, f[N], v[N], p[N];

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= m; i++)
        cin >> v[i] >> p[i];
    for (int i = 1; i <= m; i++)
    {
        for (int j = n; j >= v[i]; j--)
        {
            f[j] = max(f[j], f[j - v[i]] + v[i] * p[i]);
        }
    }
    cout << f[n] << endl;
    return 0;
}

[Luogu P2639 USACO09OCT]Bessie's Weight Problem G

#include <bits/stdc++.h>
using namespace std;

const int N = 45010;
const int M = 510;
int h, n, s[M], f[N];

int main()
{
    cin >> h >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> s[i];
        for (int j = h; j >= s[i]; j--)
        {
            f[j] = max(f[j], f[j - s[i]] + s[i]);
        }
    }
    cout << f[h] << endl;
    return 0;
}
2)完全背包问题

image-20240331083432957

image-20240331084223127

image-20240331084244294

image-20240331084419486

 

Luogu P1616 疯狂的采药

#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
const int M=1e7+10;
long long f[M], a[N], b[N];
int t, m;
int main()
{
    cin >> t >> m;
    for (int i = 1; i <= m; i++)
    {
        cin >> a[i] >> b[i];
    }
    for (int i = 1; i <= m; i++)
    {
        for (int j = a[i]; j <= t; j++)
        {
            f[j] = max(f[j], f[j - a[i]] + b[i]);
        }
    }
    cout << f[t] << endl;
    return 0;
}
// 滚动数组
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=1e4+5,M=1e7+5;
long long v[N],w[N],f[2][M];

int main(){
  int n, m; scanf("%d%d",&m,&n);
  for(int i=1; i<=n; i++) 
    scanf("%d%d",&v[i],&w[i]);  //费用,价值
  
  for(int i=1; i<=n; i++)       //枚举物品
    for(int j=1; j<=m; j++)     //枚举体积
      if(j<v[i]) f[i&1][j]=f[i-1&1][j];
      else f[i&1][j]=max(f[i-1&1][j],f[i&1][j-v[i]]+w[i]);         
      
  printf("%lld\n",f[n&1][m]);
}
3)多重背包问题

image-20240331090940247

Luogu P1776 宝物筛选

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;
int vv[N], ww[N], f[N];
int n, W, v, w, m;

int main()
{
    cin >> n >> W;
    int idx = 0;
    for (int i = 1; i <= n; i++)
    {
        cin >> v >> w >> m;

        for (int j = 1; j <= m; j <<= 1)
        {
            vv[++idx] = j * v;
            ww[idx] = j * w;
            m -= j;
        }
        if (m)
        {
            vv[++idx] = m * v;
            ww[idx] = m * w;
        }
    }
    // 调用01背包解决问题
    for (int i = 1; i <= idx; i++)
    {
        for (int j = W; j >= ww[i]; j--)
        {
            f[j] = max(f[j], f[j - ww[i]] + vv[i]);
        }
    }
    cout << f[W] << endl;
    return 0;
}

5 数位DP

image-20240515213033280

 

image-20240515211731660

image-20240515212352519

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 12;

int a[N];    // 存储数字的每一位
int f[N][N]; // f[i][j]表示一共有i位,最高位为j时的不降数的个数
int cnt;     // 数字的位数
// 处理不降数的个数
void init()
{
    for (int i = 0; i <= 9; i++)
        f[1][i] = 1;            // 一位数
    for (int i = 2; i < N; i++) // 阶段:枚举位数
    {
        for (int j = 0; j <= 9; j++) // 状态:枚举最高位
        {
            for (int k = j; k <= 9; k++) // 决策:枚举次高位
            {
                f[i][j] += f[i - 1][k];
            }
        }
    }
}
int dp(int n)
{
    if (!n)
        return 1; // 特判n==0返回1
    int cnt = 0;
    while (n)
        a[++cnt] = n % 10, n /= 10;
    int res = 0, last = 0; // last表示now的高一位
    for (int i = cnt; i >= 1; i--)
    {
        int now = a[i];
        for (int j = last; j < now; j++)
            res += f[i][j];
        if (now < last)
            break;
        last = now;
        if (i == 1)//特判,走到a1的情况 
            res++;
    }
    return res;
}
int main()
{
    init();
    int l, r;
    while (cin >> l >> r)
    {
        cout << dp(r) - dp(l - 1) << endl;
    }
    return 0;
}
1)windy数
// 数位dp
#include <iostream>
using namespace std;

const int N = 12;
int f[N][10]; // 第i位的最大值为j时的满足条件的数量
int a[N];     // 存储每一位的值
// 初始化
void init()
{
    for (int i = 0; i <= 9; i++)
        f[1][i] = 1;
    for (int i = 2; i < N; i++)
    {
        for (int j = 0; j <= 9; j++)
        {
            for (int k = 0; k <= 9; k++)
            {
                if (abs(k - j) >= 2)
                    f[i][j] += f[i - 1][k];
            }
        }
    }
}
int dp(int x)
{
    if (!x)
        return 0;
    int cnt = 0;
    while (x)
        a[++cnt] = x % 10, x /= 10;
    int ans = 0, last = -2;
    for (int i = cnt; i >= 1; i--)
    {
        int now = a[i];
        for (int j = (i == cnt); j < now; j++) // 最高位从1开始
        {
            if (abs(j - last) >= 2)
                ans += f[i][j];
        }
        if (abs(now - last) < 2)
            break;
        last = now;
        if (i == 1)
            ans++;
    }
    // 小于cnt位的满足条件的数字
    for (int i = 1; i < cnt; i++)
    {
        for (int j = 1; j <= 9; j++)
        {
            ans += f[i][j];
        }
    }
    return ans;
}
int main()
{
    int l, r;
    cin >> l >> r;
    init();
    cout << dp(r) - dp(l - 1) << endl;
    return 0;
}
2)度的数量

image-20240521091036097

image-20240521092134180

image-20240521093008010

/**
 * 主要思想为,若满足k个b给整数次方和的数字,则转换成b进制数后,只需看在规定范围内的数字,该进制数可以放k个1的组合数
 */

#include <bits/stdc++.h>
using namespace std;

const int N = 34;
int a[N];    // 记录b机制数
int f[N][N]; // f[i][j],在i个位置上放j个1的组合数
int k, b;
// 初始化组合数
void init()
{
    for (int i = 0; i < N; i++)
        f[i][0] = 1;
    for (int i = 1; i < N; i++)
    {
        for (int j = 1; j <= i; j++)
        {
            f[i][j] = f[i - 1][j - 1] + f[i - 1][j];
        }
    }
}
int dp(int x)
{
    if (!x)
        return 0;
    int cnt = 0;
    while (x)
        a[++cnt] = x % b, x /= b;
    int res = 0, last = 0;
    for (int i = cnt; i >= 1; i--)
    {
        int t = a[i];
        if (t)
        {
            res += f[i - 1][k - last]; // 第i位放0
            if (t > 1)
            {
                if (k - last - 1 >= 0)
                    res += f[i - 1][k - last - 1]; // 第i位放1
                break;
            }
            else // t==1
            {
                last++;
                if (last > k)
                    break;
            }
        }
        // 走到末位的情况
        if (i == 1 && last == k)
            res++;
    }
    return res;
}
int main()
{
    init();
    int x, y;
    cin >> x >> y;

    cin >> k >> b;
    cout << dp(y) - dp(x - 1) << endl;
    return 0;
}

 

4 状压DP

1) 小国王

在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。

输入格式 只有一行,包含两个数 N,K( 1 ≤ N ≤ 9, 0 ≤ K ≤ N * N) 输出格式 所得的方案数

image-20240515113029544

image-20240515113454047

image-20240515113707405

image-20240515113839776

image-20240515113917087

 

[Luogu P1896 SCOI2005] 互不侵犯 题目有一定难度,需要好好消化一下
/**
 * 状压DP
 * 用2进制表示状态,用十进制存储状态(降低空间复杂度)
 * 通过位运算筛选出合法的状态
 * 用位运算判断状态转移的条件
 * 计算时每个类累加 上一行的兼容类
 */

#include <iostream>
using namespace std;
int s[1 << 12];                // 存储状态
int num[1 << 12];              // 存储对应状态的数国王的数量
int cnt;                       // 同一行合法状态数
long long f[12][144][1 << 12]; // f[i][j][a],第i行j个国王,第i行a状态时的方案数
int main()
{
    int n, k;
    cin >> n >> k;
    for (int i = 0; i < (1 << n); i++)
    {
        if (!(i & (i >> 1))) // 同一行没有相邻的国王
        {
            s[cnt++] = i;
            // 统计同一行合法状态的国王数量
            for (int j = 0; j < n; j++)
                num[i] += (i >> j & 1);
        }
    }
    // 边界
    f[0][0][0] = 1;
    for (int i = 1; i <= n + 1; i++) // 枚举每一行
    {
        for (int j = 0; j <= k; j++) // 枚举国王数
        {
            for (int a = 0; a < cnt; a++) // 枚举第i行合法状态
            {
                int c = num[s[a]];            // 第i行第a个状态的国王数
                for (int b = 0; b < cnt; b++) // 枚举第i-1行合法状态
                {
                    if ((j >= c) && !(s[b] & s[a])        // 不存在同列的1
                        && !(s[b] & (s[a] >> 1))          // 不存在右上角的1
                        && !(s[b] & (s[a] << 1)))         // 不存在左上角的1
                        f[i][j][a] += f[i - 1][j - c][b]; // 行间转移
                }
            }
        }
    }
    cout << f[n + 1][k][0] << endl;
    return 0;
}
2)玉米田
[Luogu P1879 USACO06NOV]Corn Fields(玉米田)

image-20240515152143893

image-20240515152833939

image-20240515153021811

加强判断

#include <iostream>
using namespace std;
const int MOD = 1e8;
int s[1 << 14];     // 保存状态
int f[14][1 << 14]; // 保存方案数
int g[1 << 14];     // 保存每一行的土地情况
int cnt;            // 计数
int main()
{
    int m, n, x;
    cin >> m >> n;
    for (int i = 1; i <= m; i++)
    {
        for (int j = 0; j < n; j++)
        {
            cin >> x;
            g[i] = (g[i] << 1) + x;
        }
    }
    // 找出行内的合法状态
    for (int i = 0; i < (1 << n); i++)
    {
        if (!(i & i >> 1))
            s[cnt++] = i;
    }
    f[0][0] = 1;
    for (int i = 1; i <= m + 1; i++) // 枚举每一行
    {
        for (int a = 0; a < cnt; a++) // 枚举第i行的合法状态
        {
            for (int b = 0; b < cnt; b++) // 第i-1行的合法状态
            {
                // 注意运算的先后次序,注意加括号
                if ((s[a] & g[i]) == s[a] && (s[b] & g[i - 1]) == s[b] && !(s[a] & s[b]))
                    f[i][a] = (f[i][a] + f[i - 1][b]) % MOD;
            }
        }
    }
    cout << f[m + 1][0] << endl;
    return 0;
}
POJ3254 Corn Fields
 

 

chapter6 基础算法

1 高精度乘、出、加、减

//
#include<iostream>
using namespace std;

const int N=5e10+10;
int a[N],b[N],r[N],n1,n2;


void add(int a[],int b[])
{
    if(n1<n2)
    {
        add(b,a);
    }
	int c=0;//进位
    for(int i=0;i<n2;i++)
    {
        r[i]=(a[i]+b[i]+c)%10;
        c=(a[i]+b[i]+c)/10;
    }
    if(a[n2])
    {
        r[n2]=c;
        for(int i=n2+1;i<n1;i++)
    	{
        	r[i]=a[i];
    	}
    }
    
    for(int i=n1-1;~i;i--)
    {
        printf("%d",r[i]);
    }
}

int main()
{
    string a1,a2;//a和b为两个加数
    scanf("%s %s",a1,a2);
    n1=a1.size();
    n2=a2.size();
    for(int i=n1-1,k=0;~i;i--,k++)
        //~ 是按位取反运算符。当i为0时,~i结果为-1,而-1在计算机中以全1的二进制表示。因此,条件~i 在C++中相当于i != 0
    {
        a[k]=a1[i];
    }
    for(int i=n2-1,k=0;~i;i--,k++)
    {
        b[k]=a2[i];
    }
    add(a,b);
        
    return 0;
}
#include<iostream>
#include<string>
using namespace std;

const int N=1e500+10;
int a[N],b[N],c[N],len;
string s1,s2;


void add(int a[],int b[])
{
    for(int i=0;i<len;i++)
    {
        c[i]+=a[i]+b[i];//累加
        c[i+1]=c[i]/10;//进位
        c[i]=c[i]%10;//存入
    }
    if(c[len])//最高位有进位
    {
        len++;
    }
    for(int i=len-1;~i;i--)
    {
        cout<<c[i];
    }
}
int main()
{
    cin>>s1>>s2;
    if(s1.size()>s2.size())
    {
        len=s1.size();
    }
    else
    {
        len=s2.size();
    }
    for(int i=s1.size()-1;~i;i--)
    {
        a[s1.size()-1-i]=s1[i]-'0';
    }
    for(int i=s2.size()-1;~i;i--)
    {
        b[s2.size()-1-i]=s2[i]-'0';
    }
    add(a,b);
    return 0;
}

 

 

#include <iostream>
#include <cstring>
using namespace std;
const int N = 20;

int a[N], b[N], c[N + N];
int size_s1, size_s2, size_c;
void Multiply()
{
    for (int i = 0; i < size_s1; i++)
    {
        for (int j = 0; j < size_s2; j++)
        {
            c[i + j] += a[i] * b[j];       // 存乘积
            c[i + j + 1] += c[i + j] / 10; // 存余数
            c[i + j] = c[i + j] % 10;      // 存余数
        }
    }
    while (size_c && c[size_c] == 0)
        size_c--;
}

int main()
{
    // char s1[N],s2[N],r[N*N];
    string s1, s2;
    cin >> s1 >> s2;
    size_s1 = s1.size();
    size_s2 = s2.size();
    size_c = size_s1 + size_s2;
    for (int i = size_s1 - 1; ~i; i--)
    {
        a[i] = s1[size_s1 - i - 1] - '0';
    }
    for (int i = size_s2 - 1; ~i; i--)
    {
        b[i] = s2[size_s2 - i - 1] - '0';
    }
    Multiply();
    for (int i = size_c; ~i; i--)
        cout << c[i];
}

 

 

2 信息学奥赛算法

先序遍历

image-20240215111809113

 

//先序遍历
void preorder(tree t)
{
    if(t)
    {
        cout<<t->value<<endl;
        preorder(t->left);
        preorder(t->right);
    }
}

 

中序遍历

image-20240215112913820

void inorder(tree t)
{
	if(t)
    {
        inorder(t->left);
        printf("%c",t->value);
        inorder(t->right);
    }
}

 

后续遍历及总结

image-20240215114031706

//后序遍历
void postorder(tree t)
{
    if(t)
    {
        postorder(t->left);
        postorder(t->right);
        printf("%c",t->value);
    }
}
//总结
/*
先序遍历:在轨迹的左侧位置
中序遍历:在轨迹的底部位置
后序遍历:在轨迹的右侧位置
*/

 

推断二叉树

image-20240215114830278

image-20240215114907021

总结:根据后序遍历或先序遍历确定根节点,然后根据中序遍历确定左右子树,最终确定一个唯一的二叉树。但若只知道先序和后序遍历,则无法确定一个唯一的二叉树。

 

3 二分查找 二分答案

1 整数二分与浮点数二分(其中二分查找是二分答案的基础)

 

//最大化查找,可行区在左侧
//例如:查找最后一个<=q的数的下标 满足条件可行域发生变化,不满足条件非可行域发生变化

int find(int q)
{
    int l=0,r=n+1;//开区间,这里假设下标从1开始,n结束
    int mid=l+r>>1;
    while(l+1<r)
    {
        if(a[mid]<=q)
            l=mid;
        else
            r=mid;
    }
    return l;
}
//最小化查找
int find(int q)
{
    int l=0,r=n+1;
    int mid=l+r>>1;
    while(l+1<r)
    {
        if(a[mid]>=q)
        {
            r=mid;
        }
        else
        {
            l=mid;
        }
    }
    return r;
}

//若为浮点数
//则需要注意 double mid =(l+r)/2;

image-20240123225836334

(1)题目:洛谷P2249
#include<iostream>
using namespace std;

const int N=1e6+10;
const int M=1e5+10;
int a[N],n,c[M],m;

int find(int q)
{
    int l=-1,r=n+1;
    while(l+1<r)
    {
        int mid=l+r>>1;
        if(a[mid]>=q)
        {
            r=mid;
        }
        else
        {
            l=mid;
        }
    }
    return a[r]==q ? r+1:-1;
}
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=0;i<n;i++)
    {
        scanf("%d",&a[i]);
    }
    for(int i=0;i<m;i++)
    {
        scanf("%d",&c[i]);
    }
    for(int i=0;i<m;i++)
    {
        printf("%d ",find(c[i]));
    }
    
}
(2) 题目:luoguP1024
2 二分答案

image-20240126173505944

P2440 木材加工
/*
这里是典型的最大化答案问题。
题目要求找出最大的l,并满足要求的切割后的数目
**可以将l(段长)的值作为x,y作为段数,容易直到随着段长x的增加,段数会减少。
可行域再左侧,当满足段长时,向右收缩
不满足时,向左收缩。
*/

#include<iostream>
using namespace std;

const int N=1e5+10;
int a[N],n,k;//用于存放原木长度

bool check(int x)
{
	int y=0;
	for(int i=1;i<=n;i++)
	{
		y+=a[i]/x;
	}
	return y>=k;
}

int find()
{
	int l=0,r=1e8+10;
	while(l+1<r)
	{
		int mid=l+r>>1;
		if(check(mid)) 
		{
			l=mid;
		}
		else
		{
			r=mid;
		}
	}
	return l;
}
int main()
{
	scanf("%d %d",&n,&k);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	int l=find();
	printf("%d",l);
		
} 
 
P2678跳石头
/*


*/
#include<iostream>
using namespace std;

const int N=5e4+10;
int a[N],n,l,m;

//随着最短距离的增大,势必会使得移走的岩石数增多 
bool check(int x)
{
	int y=0,last=0;
	for(int i=1;i<=n+1;i++)
	{
		if((a[i]-a[last])<x)
		{
			y++;
		}
		else
		{
			last=i;
		}

	}
	return y<=m;
}

int find()
{
	int l=0,r=1e9+1;
	while(l+1<r)
	{
		int mid=l+r>>1;
		if(check(mid)) 
		{
			l=mid;
		}
		else
		{
			r=mid;
		}
	}
	return l;
}
int main()
{
	scanf("%d %d %d",&l,&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	a[n+1]=l;//右边界 
	int l=find();
	printf("%d",l);
		
} 
 
P1314 聪明的质检员

 

/*

*/

 

4 分数规划(见day4)

网页地址:A07 01分数规划 - 董晓 - 博客园

1POJ2976 Dropping tests
//最大化问题
#include<iostream>
#include<algorithm>
using namespace std;

const int N=10010;
double a[N],b[N],c[N];
int n,k;


bool check(double x)
{
	double s=0;
	for(int i=1;i<=n;i++)
	{
		c[i]=a[i]-x*b[i];
	}
	sort(c+1,c+n+1);//升序排列
	for(int i=k+1;i<=n;i++)
	{
		s+=c[i];
	}
	return s>=0;
}
//最大化,当check为真时,l右移
double find()
{
	double l=0,r=1;
	while(r-l>1e-4)//结果会保留两位小数后进行*100操作,因此需要将区间划分到1e-4
	{
		double mid=(l+r)/2;
		if(check(mid))
		{
			l=mid;	
		}
		else
		{
			r=mid;
		}
	}
	return l;
}
int main()
{
	while(scanf("%d%d",&n,&k),n)
	{
		for(int i=1;i<=n;i++)
		{
			scanf("%lf",&a[i]);
		}
		for(int i=1;i<=n;i++)
		{
			scanf("%lf",&b[i]);
		}
		printf("%.0lf\n",100*find());
	}
	
	return 0;
}
2 [Luogu P4377 USACO18OPEN] Talent Show G

image-20240221164359031

image-20240221164857417

image-20240221170359832

image-20240221170533604

 

image-20240221175755138

//这里用到01背包
for(int i=1;i<=n;i++)
{
	for(int j=1;j<=m;j++)
    {
        if(j<w[i])
        {
            f[i][j]=f[i-1][j];
        }
        else
        {
            max(f[i-1][j],f[i-1][j-w[i]]+c[i]);//这里的c表示价值
        }
    }
}
//通过简化板子,降低空间复杂度
f[0]=0;
for(int i=1;i<=n;i++)
{
    for(int j=m;j>=w[i];j--)
    {
        f[j]=max(f[j],f[j-w[i]]+c[i]);
    }
}
printf("%d",f[m]);
-------------------------------------------------------------------------------------------------
#include<iostream>
#include<algorithm>
using namespace std;

const int N=255;
int n,W,w[N],t[N];
double f[1005];

bool check(double x)
{
	for(int i=1;i<=W;i++)
	{
		f[i]=-1e9;
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=W;j>=0;j--)
		{
			int k=min(W,j+w[i]);
			f[k]=max(f[k],f[j]+t[i]-x*w[i]);
		}
	}
	return f[W]>=0;
}
double find()
{
	double l=0,r=1000;
	while(r-l>1e-5)
	{
		double mid=(l+r)/2;
		if(check(mid))
			l=mid;
		else
			r=mid;
	}
	return r;//为了达到*1000后向下取整
}
int main()
{
//	f[0]=0;
	scanf("%d%d",&n,&W);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&w[i],&t[i]);
	}
	printf("%d",int(find()*1000));
	return 0;
	
}

 

5 前缀和二维前缀和

1)一维前缀和

 

image-20240303135557669

 

Luogu P8218 【深进1.例1】求区间和

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;
int s[N], a[N]; // s存前i项的和,a存数列
int n, m, l, r;
int main()
{

    cin >> n;
    s[0] = 0;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        s[i] = s[i - 1] + a[i];
    }
    cin >> m;
    for (int j = 0; j < m; j++)
    {
        cin >> l >> r;
        cout << s[r] - s[l - 1] << endl;
    }
    return 0;
}

 

2)二维前缀和

image-20240303141638782

[Luogu P2280 HNOI2003] 激光炸弹

/**
 * 二维前缀和
 * 基于容斥原理
 * 难点:两个公式 s[i][j]=s[i][j-1]+s[i-1][j]-s[i-1]s[j-1]+a[i][j];
 * 一段区间的和:sum=s[i][j]-s[a-1][j]-s[i][b-1]+s[a-1][b-1];
 * (a,b)为区间左上方顶点,(i,j)为区间右下方顶点
 */

#include <bits/stdc++.h>

using namespace std;
const int N = 1e4 + 10;
int ad[N][N], s[N][N];
int n, m, x, y, v;
int main()
{
    cin >> n >> m;
    // s[0][0]=0;
    for (int i = 1; i <= n; i++)
    {
        cin >> x >> y >> v;
        x++, y++; // 让坐标从(1,1)开始
        ad[x][y] += v;
    }
    for (int i = 1; i <= 5010; i++)
    {
        for (int j = 1; j <= 5010; j++)
        {
            s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + ad[i][j];
        }
    }
    int res = 0;
    for (int x = m; x < 5010; x++)
    {
        for (int y = m; y < 5010; y++)
        {
            res = max(res, s[x][y] - s[x - m][y] - s[x][y - m] + s[x - m][y - m]);
        }
    }
    cout << res << endl;
    return 0;
}

Luogu P1387 最大正方形

#include <bits/stdc++.h>

using namespace std;
const int N = 103;
int n, m;
int s[N][N], a[N][N];
/**
 * 这里的边长为x
 */
bool check(int x)
{
    for (int i = x; i <= n; i++)
    {
        for (int j = x; j <= m; j++)
        {

            int y = s[i][j] - s[i - x][j] - s[i][j - x] + s[i - x][j - x];
            if (y == x * x)
                return true;
        }
    }
    return false;
}
int find()
{
    int l = 1, r = min(n, m) + 1;
    while (l + 1 < r)
    {
        int mid = l + r >> 1;
        if (check(mid))
            l = mid;
        else
            r = mid;
    }
    return l;
}
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            cin >> a[i][j];
            s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
        }
    }
    int ans = find();
    cout << ans << endl;
    return 0;
}

 

3)树上前缀和(理解不深)

image-20240303155849590

可利用Tarjan(塔扬算法),复习巩固,以及掌握倍增法求公共祖先。以此为前提掌握树上前缀和

 

[Luogu P4427 BJOI2018] 求和

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 3e5 + 10;
const int M = N * 2;

const LL mod = 998244353;
struct edge
{
    int v, ne;
};
int i, j, k, n, m, idx;
edge e[M]; // 保存结点信息
int fa[N][22], h[N], dep[N];
LL mi[60], s[N][60]; // mi[j]保存dep[v]的j次幂,s[u][j]表示从根节点到u的深度的j次幂之和

void add(int a, int b)
{
    e[++idx] = {b, h[a]};
    h[a] = idx;
}
void dfs(int u, int f)
{
    for (int i = 0; i <= 19; i++)
    {
        fa[u][i + 1] = fa[fa[u][i]][i];
    }
    for (int i = h[u]; i; i = e[i].ne)
    {
        int v = e[i].v;
        if (v == f)
            continue;
        fa[v][0] = u;
        dep[v] = dep[u] + 1;

        for (int i = 1; i <= 50; i++)
            mi[i] = mi[i - 1] * dep[v] % mod;
        for (int i = 1; i <= 50; i++)
        {
            s[v][i] = (s[u][i] + mi[i]) % mod;
        }
        dfs(v, u);
    }
}
int lca(int x, int y)
{
    if (dep[x] < dep[y])
        swap(x, y);
    //
    for (int i = 19; i >= 0; i--)
    {
        if (dep[fa[x][i]] >= dep[y])
            x = fa[x][i];
    }
    if (x == y)
        return x;
    for (int i = 19; i >= 0; i--)
    {
        if (fa[x][i] != fa[y][i])
            x = fa[x][i], y = fa[y][i];
    }
    return fa[x][0];
}
int main()
{
    cin >> n;
    for (int a = 1; a <= n - 1; a++)
    {
        cin >> i >> j;
        add(i, j);
        add(j, i);
    }
    mi[0] = 1;
    dfs(1, 0);
    cin >> m;
    for (int b = 1; b <= m; b++)
    {
        cin >> i >> j >> k;
        int l = lca(i, j);
        // cout << l << endl;
        LL ans = (s[i][k] + s[j][k] - s[l][k] - s[fa[l][0]][k] + 2 * mod) % mod;
        cout << ans << endl;
    }

    return 0;
}

 

6 差分

 

1)一维差分

image-20240304202730861

注意结论的推导

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
LL a[N], b[N];
int n;

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    // 建立差分数组
    for (int i = 1; i <= n; i++)
    {
        b[i] = a[i] - a[i - 1];
    }
    LL p = 0, q = 0;
    for (int i = 2; i <= n; i++)
    {
        if (b[i] > 0)

            p += b[i];
        else
            q += abs(b[i]);
    }
    cout << max(p, q) << "\n"
         << abs(p - q) + 1 << endl;
}

 

2)二维差分

image-20240304210852723

 

#include <iostream>
using namespace std;

const int N = 1010;
int n, m, x1, y, x2, y2;
int a[N][N];

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= m; i++)
    {
        cin >> x1 >> y >> x2 >> y2;
        // 进行差分
        for (int j = x1; j <= x2; j++)
        {
            a[j][y]++;
            a[j][y2 + 1]--;
        }
    }
    // 进行前缀和
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            a[i][j] += a[i][j - 1];
            cout << a[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}

 

3)树上差分

[Luogu P3128 USACO15DEC] Max Flow P

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;
const int M = 2 * N;
struct edge
{
    int to, ne;
} e[M];
int idx, n, k, x, y, s, t;
int h[N], fa[N][22];
int dep[N];
int power[N]; // 存储子节点的和
int ans;      // 存储最大值
void add(int a, int b)
{
    e[++idx] = {b, h[a]};
    h[a] = idx;
}
/**
 * 倍增法求最近总共祖先
 *
 */
void dfs(int u, int f)
{
    dep[u] = dep[f] + 1;
    fa[u][0] = f;
    for (int i = 0; i < 21; i++)//一定要注意数组的下标,防止越界
    {
        fa[u][i + 1] = fa[fa[u][i]][i];
    }
    for (int i = h[u]; i; i = e[i].ne)
    {
        if (e[i].to != f)
            dfs(e[i].to, u);
    }
}
int lca(int x, int y)
{
    if (dep[x] < dep[y])
        swap(x, y);
    // 先跳到同一深度
    for (int i = 21; i >= 0; i--)
    {
        if (dep[fa[x][i]] >= dep[y])
            x = fa[x][i];
    }
    if (x == y)
        return x;
    // 再调到公共结点的下一层
    for (int i = 21; i >= 0; i--)
    {
        if (fa[x][i] != fa[y][i])
        {
            x = fa[x][i], y = fa[y][i];
        }
    }
    return fa[x][0];
}
void dfs2(int u, int f)
{
    for (int i = h[u]; i; i = e[i].ne)
    {
        int v = e[i].to;
        if (v == f)
            continue;
        dfs2(v, u);
        power[u] += power[v];
    }
    ans = max(ans, power[u]);
}
int main()
{
    cin >> n >> k;
    for (int i = 0; i < n - 1; i++)
    {
        cin >> x >> y;
        add(x, y);
        add(y, x);
    }
    dfs(1, 0);
    for (int j = 0; j < k; j++)
    {
        cin >> s >> t;
        // 进行树上差分
        int l = lca(s, t);
        ++power[s], ++power[t];
        --power[l], --power[fa[l][0]];
    }
    dfs2(1, 0);
    cout << ans << endl;
}

[Luogu P3258 JLOI2014] 松鼠的新家

 

#include <iostream>

using namespace std;

const int N = 3e5 + 10;
const int M = 2 * N;
struct edge
{
    int to, ne;
} e[M];
int h[N], idx;
int fa[N][31], dep[N];
int a[N], x, y, n, t;
int ans[N]; // 记录走过的次数
void add(int a, int b)
{
    e[++idx] = {b, h[a]};
    h[a] = idx;
}
void dfs(int u, int f)
{
    dep[u] = dep[f] + 1;
    fa[u][0] = f;
    for (int i = 0; i < 30; i++)
    {
        fa[u][i + 1] = fa[fa[u][i]][i];
    }
    for (int i = h[u]; i; i = e[i].ne)
    {
        if (e[i].to != f)
        {
            dfs(e[i].to, u);
        }
    }
}
int lca(int x, int y)
{
    if (dep[x] < dep[y])
        swap(x, y);
    for (int i = 30; i >= 0; i--)
    {
        if (dep[fa[x][i]] >= dep[y])
        {
            x = fa[x][i];
        }
    }
    if (x == y)
        return x;
    for (int j = 30; j >= 0; j--)
    {
        if (fa[x][j] != fa[y][j])
        {
            x = fa[x][j], y = fa[y][j];
        }
    }
    return fa[x][0];
}
void dfs2(int u, int f)
{
    for (int i = h[u]; i; i = e[i].ne)
    {
        int v = e[i].to;
        if (v == f)
            continue;
        dfs2(v, u);
        ans[u] += ans[v];
    }
}
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        // cin >> a[i];
        scanf("%d", &a[i]);
    }
    for (int j = 1; j <= n - 1; j++)
    {
        // cin >> x >> y;
        scanf("%d%d", &x, &y);
        add(x, y);
        add(y, x);
    }
    dfs(1, 0);
    for (int k = 1; k <= n - 1; k++)
    {
        int x = a[k], y = a[k + 1];
        int l = lca(x, y);
        ++ans[x], ++ans[y];
        --ans[l], --ans[fa[l][0]];
    }
    dfs2(1, 0);
    for (int i = 1; i <= n; i++)
    {
        //这里要尤其注意,对题意要充分理解
        if (i == a[1])
            // cout << ans[i] << endl;
            printf("%d\n", ans[i]);
        else
            // cout << ans[i] - 1 << endl;
            printf("%d\n", ans[i] - 1);
    }
    return 0;
}

 

[Luogu P2680 NOIP2015 提高组] 运输计划

需要自己手敲一下

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=300010;
struct node{
	int to,next,w;
}edge[maxn*2];
struct no{
	int u,v,lcaa,diss;
}lu[maxn*2];
int summ,cnt=0,k,n,m,num[maxn],mi[maxn],vis[maxn];
int temp[maxn],head[maxn],deep[maxn],dis[maxn];
int fa[maxn][25],dp[maxn][25];
void adde(int u,int v,int w){
	k++;
	edge[k].to=v;
	edge[k].next=head[u];
	edge[k].w=w;
	head[u]=k;
}
void dfs(int x,int pa,int dep){
	cnt++;
	num[cnt]=x;
	deep[x]=dep;
	vis[x]=1;
	for(int i=1;i<25;i++){
		fa[x][i]=fa[fa[x][i-1]][i-1];
	}
	for(int i=head[x];i>0;i=edge[i].next){
		int v=edge[i].to;
		if(!vis[v]){
			fa[v][0]=x;
			dis[v]=dis[x]+edge[i].w;
			dfs(v,x,dep+1);
		}
	}
}
int lca(int x,int y){
	if(deep[x]<deep[y]) swap(x,y);
	int t=deep[x]-deep[y];
	for(int i=0;i<25;i++){
		if((1<<i)&t) x=fa[x][i];
	}
	if(x==y) return x;
	for(int i=24;i>=0;i--){
		if(fa[x][i]!=fa[y][i]){
			x=fa[x][i];y=fa[y][i];
		}
	}
	return fa[x][0];
}
bool check(int mid){
	int cnt=0,ans=0;
	memset(temp,0,sizeof(temp));
	for(int i=1;i<=m;i++){
		if(lu[i].diss>mid){
			temp[lu[i].u]++;temp[lu[i].v]++;temp[lu[i].lcaa]-=2;
			ans=max(ans,lu[i].diss-mid);
			cnt++;
		}
	}
	if(cnt==0) return true;
	for(int i=n;i>=1;i--) temp[fa[num[i]][0]]+=temp[num[i]];
	for(int i=2;i<=n;i++) if(temp[i]==cnt&&dis[i]-dis[fa[i][0]]>=ans) return true;
	return false;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n-1;i++){
		int x,y,w;
		scanf("%d%d%d",&x,&y,&w);
		adde(x,y,w);adde(y,x,w);
		summ+=w;
	}
	dis[1]=0;
	dfs(1,0,1);
	for(int i=1;i<=m;i++){
		scanf("%d%d",&lu[i].u,&lu[i].v);
		lu[i].lcaa=lca(lu[i].u,lu[i].v);
		lu[i].diss=dis[lu[i].u]+dis[lu[i].v]-2*dis[lu[i].lcaa];
	}
	int left=0,right=summ;
	int mid;
	while(left<right){
		mid=(left+right)>>1;
		if(check(mid)) right=mid;
		else left=mid+1;
	}
	printf("%d",left);
	return 0;
}

[Luogu P1600 NOIP2016 提高组] 天天爱跑步

 

[Luogu P4556 Vani有约会] 雨天的尾巴

 

7 ST表 RMQ问题

Luogu P3865 【模板】ST 表

#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;

const int N = 1e5 + 10;

int f[N][22], m, n, t, l, r;
inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
    {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}
int main()
{
    // scanf("%d%d", &n, &m);
    n = read(), m = read();
    for (int i = 1; i <= n; i++)
        // scanf("%d", &f[i][0]);
        f[i][0] = read();
    // ST表初始化
    for (int j = 1; j <= 20; j++)
    {
        for (int i = 1; i + (1 << j) - 1 <= n; i++)
        {
            f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
        }
    }
    // 计算l-r区间内的最大值
    for (int j = 1; j <= m; j++)
    {
        // scanf("%d%d", &l, &r);
        l = read(), r = read();
        int k = log2(r - l + 1);
        printf("%d\n", max(f[l][k], f[r - (1 << k) + 1][k]));//cout耗时久
    }
    return 0;
}

 

Luogu P1440 求m区间内的最小值

 

[Luogu P1816 忠诚](https://www.luogu.com.cn/problem/P18

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;

int m, n, f[N][17], l, r;
inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
    {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}
int main()
{
    m = read(), n = read();

    for (int i = 1; i <= m; i++)
    {
        f[i][0] = read();
    }
    // 初始化ST表
    for (int j = 1; j <= 16; j++)
    {
        for (int i = 1; i + (1 << j) - 1 <= m; i++)
        {
            f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
        }
    }
    for (int i = 1; i <= n; i++)
    {
        l = read(), r = read();
        int k = log2(r - l + 1);
        printf("%d ", min(f[l][k], f[r - (1 << k) + 1][k]));//一定要注意要求输出的格式
    }
    return 0;
}

Luogu P2251 质量检测

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int f[N][22], n, m;
// 预处理ST表
inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
    {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}
void init_ST(int n)
{
    for (int j = 1; j <= 20; j++)
    {
        for (int i = 1; i + (1 << j) - 1 <= n; i++)
        {
            f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
        }
    }
}

int main()
{
    n = read(), m = read();
    for (int i = 1; i <= n; i++)
    {
        f[i][0] = read();
    }
    init_ST(n);
    for (int i = 1; i <= (n - m + 1); i++)
    {
        int l = i, r = i + m - 1;
        int k = log2(r - l + 1);
        printf("%d\n", min(f[l][k], f[r - (1 << k) + 1][k]));
    }
    return 0;
}

 

[Luogu P2880 USACO07JAN] Balanced Lineup G

#include <bits/stdc++.h>
using namespace std;

const int N = 5e4 + 10;
int f1[N][22], f2[N][22], n, q, a, b;
inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (ch > '9' || ch < '0')
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
    {
        x = x * 10 + ch - 48;
        ch = getchar();
    }
    return x * f;
}
void init_ST(int n)
{
    for (int j = 1; j <= 20; j++)
    {
        for (int i = 1; i + (1 << j) - 1 <= n; i++)
        {
            f1[i][j] = max(f1[i][j - 1], f1[i + (1 << (j - 1))][j - 1]);
            f2[i][j] = min(f2[i][j - 1], f2[i + (1 << (j - 1))][j - 1]);
        }
    }
}
int main()
{
    n = read(), q = read();
    for (int i = 1; i <= n; i++)
    {
        f1[i][0] = f2[i][0] = read();
    }
    init_ST(n);
    for (int i = 1; i <= q; i++)
    {
        a = read(), b = read();
        int k = log2(b - a + 1);
        printf("%d\n", max(f1[a][k], f1[b - (1 << k) + 1][k]) - min(f2[a][k], f2[b - (1 << k) + 1][k]));
    }
    return 0;
}

8 贪心算法

1.[Luogu P1090 NOIP2004 提高组] 合并果子

image-20240502193001339

#include <iostream>
#include <queue>
using namespace std;
const int N = 1e4 + 10;
int n;
// 使用小根堆
priority_queue<int, vector<int>, greater<int>> q;
int ans; // 保存所消耗的体力之和
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        int a;
        cin >> a;
        q.push(a);
    }
    while (q.size() > 1)
    {
        int a = q.top();
        q.pop();
        int b = q.top();
        q.pop();
        ans += a + b;
        q.push(a + b);
    }
    cout << ans << endl;
    return 0;
}
2. POJ 3617 Best Cow Line
/**
 * 解题思路
 *  1.不断地比较a开头和结尾的字符的大小,将小的输出,若在开头指针加1
 *  若在结尾,指针减1.
*/
#include <iostream>
using namespace std;

const int N = 2010;
int n;
char a[N];
void solve()
{
    int s = 1, e = n;
    int ant = 0;
    while (s <= e)
    {
        bool flag = false;
        for (int i = 0; s + i <= e; i++)
        {
            if (a[s + i] < a[e - i])
            {
                flag = true;
                break;
            }
            else if (a[s + i] > a[e - i])
            {
                flag = false;
                break;
            }
        }
        ant++;
        if (flag)
            putchar(a[s++]);
        else
            putchar(a[e--]);
        if (ant % 80 == 0)
            putchar('\n');
    }
}
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    solve();

    return 0;
}
3. POJ 3069 Saruman Army
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1010;
int r, n; // r表示距离,n表示点的个数
int a[N];
/**
 * 注意:这里的距离范围包括前后,所以一个点找出后,还得继续向右找到下一个开始点
*/
void solve()
{
    int ans = 0;
    sort(a, a + n);
    int k = 0;
    while (k < n)
    {
        //s是没有别覆盖的最做的点的位置
        int s = a[k++];
        //一直向右前进到距s大于r的点
        while (k < n && a[k] <= s + r)
            k++;
        //p是新标记的点的位置
        int p = a[k - 1];
        //一直向右前进到距p大于r的点
        while (k < n && a[k] <= p + r)
            k++;
        ans++;
    }
    cout << ans << endl;
}
int main()
{
    while (1)
    {
        cin >> r >> n;
        if (r == -1 && n == -1)
            break;
        for (int i = 0; i < n; i++)
            cin >> a[i];
        solve();
    }
    return 0;
}
4. POJ 3253 Fence Repair
//solve1:时间复杂度O(NlogN)
//简简单单一个小根堆解决
// 使用默认的比较函数(从大到小排序)std::priority_queue<int, std::vector<int>, std::greater<int>> q;
//在C++的标准库中,priority_queue 默认使用 std::less 作为比较函数,这会导致堆顶元素是最大值,因此它是一个大根堆。
#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
typedef long long ll;
int n;
ll ans;
int x;
priority_queue<int, vector<int>, greater<int>> q;
void solve()
{
    while (q.size() - 1)
    {
        int x = q.top();
        q.pop();
        int y = q.top();
        q.pop();
        ans += x + y;
        q.push(x + y);
    }
    cout << ans << endl;
}
int main()
{
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        cin >> x;
        q.push(x);
    }
    solve();
    return 0;
}
//solve2:时间复杂度O(n^2)
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 2e4 + 10;
int n, l[N];
ll ans;
void solve()
{
    while (n > 1)
    {
        int mi1 = 0, mi2 = 1;
        if (l[mi1] > l[mi2])
            swap(mi1, mi2);
        for (int i = 2; i < n; i++)
        {
            if (l[i] < l[mi1])
            {
                mi2 = mi1;
                mi1 = i;
            }
            else if (l[i] < l[mi2])
            {
                mi2 = i;
            }
        }
        // 将两块板子拼起
        int t = l[mi1] + l[mi2];
        ans += t;
        if (mi1 == n - 1)
            swap(mi1, mi2);
        l[mi1] = t;
        l[mi2] = l[n - 1];
        n--;
    }
    cout << ans << endl;
}
int main()
{
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        cin >> l[i];
    }
    solve();
    return 0;
}

 

chapter7 字符串

1 最小表示法

Luogu P1368 【模板】最小表示法

image-20240229215647588

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 3e5 + 10;
int n, a[N + N];
int get_min()
{
    for (int i = 1; i <= n; i++)
    {
        a[n + i] = a[i];
    }
    int i = 1, j = 2, k = 0;
    while (i <= n && j <= n)
    {
        for (k = 0; k < n && a[i + k] == a[j + k]; k++);
        a[i + k] > a[j + k] ? i = i + k + 1 : j = j + k + 1;
        if (i == j)
            j++;
    }

    return min(i, j);
}
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    int min = get_min();
    for (int i = 1; i <= n; i++)
    {
        cout << a[min + i - 1] << " ";
    }
    return 0;
}

 

2 字符串哈希(字符串)

Luogu P3370【模板】字符串哈希

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef unsigned long long ULL;
const int N = 1e4 + 10;
const int P = 131;      // 或是133331
ULL p[N], h[N], ans[N]; // 这里的ans存储每个串的哈希值
char s[N];
int n;

// 计算串的hash值
ULL init(char *s, int n)
{
    h[0] = 0, p[0] = 1; // 这里的保存前缀和,这里的p保存P的平方值
    for (int i = 1; i <= n; i++)
    {
        p[i] = p[i - 1] * P;
        h[i] = h[i - 1] * P + s[i];
    }
    return h[n];
}
int main()
{
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        cin >> s + 1;
        int len = strlen(s + 1);
        ans[i] = init(s, len);
    }
    sort(ans, ans + n);
    int t = n;
    for (int i = 0; i < n - 1; i++)
    {
        if (ans[i] == ans[i + 1])
        {
            t--;
        }
    }
    cout << t << endl;
    return 0;
}

注:

//字符串哈希还可以进行比较某一区间内的字符串是否相等
ULL get_s(int l,int r)
{
	return x1=h[r]-h[l-1]*p[r-l+1];
}

 

3 KMP算法

//计算相等前后缀的最长长度 p(长度为n)为s(长度为m)的子串 
/**
* 模板
*/
ne[1]=0;
for(int i=2,j=0;i<=n;i++)
{
    while(j&&p[i]!=p[j+1])
        j=ne[j];
    if(p[i]==p[j+1])
        j++;
    ne[i]=j;
}
//求下标
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)
       cout<< (i-n+1)<<endl;
}

Luogu P3375【模板】KMP字符串匹配

#include <bits/stdc++.h>
using namespace std;

const int N = 1e6 + 10;
char s1[N], s2[N];
int ne[N];
// string s1,s2;
int len1, len2;
int main()
{
    cin >> s1 + 1 >> s2 + 1;
    len1 = strlen(s1 + 1), len2 = strlen(s2 + 1);
    // 求相等前后缀的最长长度
    ne[1] = 0;
    for (int i = 2, j = 0; i <= len2; i++)
    {
        while (j && s2[i] != s2[j + 1])
            j = ne[j];
        if (s2[i] == s2[j + 1])
            j++;
        ne[i] = j;
    }
    // 计算位置
    for (int i = 1, j = 0; i <= len1; i++)
    {
        while (j && s1[i] != s2[j + 1])
            j = ne[j];
        if (s1[i] == s2[j + 1])
            j++;
        if (j == len2)
            cout << (i - len2 + 1) << endl;
    }
    for (int i = 1; i <= len2; i++)
        cout << ne[i] << " ";

    return 0;
}

4 扩展KMP(z函数)

image-20240311175438999

Luogu P5410【模板】扩展 KMP(Z 函数)

#include <bits/stdc++.h>
using namespace std;
const int N = 2e7+10;
int z[N], p[N];
char a[N], b[N];
// 计算z函数
void KMP_z(char *s, int n)
{
    z[1] = n; // b与b的每一个后缀的LCP长度
    for (int i = 2, l, r = 0; i <= n; i++)
    {
        // 判断是否在盒内
        if (i <= r)
            z[i] = min(z[i - l + 1], r - i + 1);
        // 不在盒内就进行暴力枚举
        while (s[1 + z[i]] == s[i + z[i]])
            z[i]++;
        // 改变区间
        if (i + z[i] - 1 > r)
            l = i, r = i + z[i] - 1;
    }
}
// p[N]--> b与a的每一个后缀的LCP长度数组,其长度为m
void kmp_p(char *a, int n, int m)
{
    for (int i = 1, l, r = 0; i <= m; i++)
    {
        if (i <= r)
            p[i] = min(z[i - l + 1], r - i + 1);
        while (b[1 + p[i]] == a[i + p[i]] && 1 + p[i] <= n && i + p[i] <= m)
            p[i]++;
        if (i + p[i] - 1 > r)
            l = i, r = i + p[i] - 1;
    }
}
// 按公式计算
void calculate(int a[], int n)
{
    long long ans = 0;
    for (int i = 1; i <= n; i++)
    {
        ans ^= 1LL * i * (a[i] + 1);
    }
    cout << ans << endl;
}
int main()
{
    cin >> a + 1 >> b + 1;
    int len1 = strlen(a + 1);
    int len2 = strlen(b + 1);
    KMP_z(b, len2);
    kmp_p(a, len2, len1);
    calculate(z, len2);
    calculate(p, len1);

    return 0;
}

 

5 Manacher算法

Luogu P3805【模板】manacher 算法

image-20240314133555483

 

#include <bits/stdc++.h>
using namespace std;
const int N = 3e7;

int d[N];        // 记录回文半径
char s[N], t[N]; // s为输入的串,t为改造后的串
void get_d(char *t, int n)
{
    d[1] = 1;
    for (int i = 2, l, r = 1; i <= n; i++)
    {
        if (i <= r)
            d[i] = min(d[r - i + l], r - i + 1);//一定要注意边界【r-i+l】
        while (t[i - d[i]] == t[i + d[i]])
            d[i]++;
        if (i + d[i] - 1 > r)
            l = i - d[i] + 1, r = i + d[i] - 1;
    }
}
int main()
{
    cin >> s + 1;
    int n = strlen(s + 1);
    int k = 0;
    t[0] = '$', t[++k] = '#';

    // 改造输入的字符串
    for (int i = 1; i <= n; i++)
    {
        t[++k] = s[i], t[++k] = '#';
    }
    n = k;
    // 计算d函数
    get_d(t, n);
    int ans = 0;
    for (int i = 1; i <= n; i++)
    {
        ans = max(ans, d[i]);
    }
    cout << ans - 1 << endl;
    return 0;
}

 

chapter8 搜索

 

2 图的存储

image-20240302141741764

 

//链式临接表
//可以反向
// 链式临接表
// 可以反向
#include <iostream>
#include <vector>
using namespace std;

const int N = 1005;

int n, m, u, v, w;
/**
 * 用于存储图的信息
 */
struct edge
{
    int u, v, w;
};
vector<edge> e;
/**
 * 用于存储出边信息
 */
vector<int> h[N];

/**
 * 存储边的信息函数
 */
void add(int a, int b, int c)
{
    e.push_back({a, b, c});
    h[a].push_back(e.size() - 1);
}
/**
 * fa为父节点
 * u为开始查找的点
 */
void dfs(int u, int fa)
{
    // 先找边的序号
    for (int i = 0; i < h[u].size(); i++)
    {
        int j = h[u][i];
        int v = e[j].v, w = e[j].w;
        if (v == fa)
            continue;
        cout << u << " " << v << " " << w << endl;
        dfs(v, u);
    }
}
int main()
{
    cin >> n >> m;
    for (int i = 0; i < m; i++)
    {
        cin >> u >> v >> w;
        add(u, v, w);
        add(v, u, w);
    }
    dfs(1, 0);
    return 0;
}
///邻接矩阵 示例 
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=1010,M=1010;
int n,m,a,b,c;
int w[N][N];//边权 
int vis[N];

void dfs(int u){
  vis[u]=true;
  for(int v=1;v<=n;v++)
    if(w[u][v]){
      printf("%d,%d,%d\n",u,v,w[u][v]);
      if(vis[v]) continue;
      dfs(v);
    }
}
int main(){
  cin>>n>>m;
  for(int i=1;i<=m;i++){
    cin>>a>>b>>c;
    w[a][b]=c; 
    // w[b][a]=c;
  }
  dfs(1);
  return 0;
}

///边集数组 示例 
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=1010,M=1010;
int n,m,a,b,c;
struct edge{
  int u,v,w;
}e[M];//边集 
int vis[N];

void dfs(int u){
  vis[u]=true;
  for(int i=1;i<=m;i++)
    if(e[i].u==u){
      int v=e[i].v,w=e[i].w;
      printf("%d,%d,%d\n",u,v,w);
      if(vis[v]) continue;
      dfs(e[i].v);
    }
}        
int main(){
  cin>>n>>m;
  for(int i=1;i<=m;i++){
    cin>>a>>b>>c;
    e[i]={a,b,c};
    // e[i]={b,a,c};
  }
  dfs(1);
  return 0;
}

///邻接表 示例 
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;

const int N=510;
int n,m,a,b,c;
struct edge{int v,w;};
vector<edge> e[N];//边集 

void dfs(int u,int fa){
  for(auto ed : e[u]){
    int v=ed.v, w=ed.w;
    if(v==fa) continue;
    printf("%d,%d,%d\n",u,v,w);
    dfs(v, u);
  } 
}
int main(){
  cin>>n>>m;
  for(int i=1;i<=m;i++){ 
    cin>>a>>b>>c,
    e[a].push_back({b,c});
    // e[b].push_back({a,c});
  }
  dfs(1, 0);
  return 0;
}

///链式邻接表 示例 
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;

const int N=510;
int n,m,a,b,c;
struct edge{int u,v,w;};
vector<edge> e;//边集
vector<int> h[N];//点的所有出边

void add(int a,int b,int c){
  e.push_back({a,b,c});
  h[a].push_back(e.size()-1);
}
void dfs(int u,int fa){
  for(int i=0;i<h[u].size();i++){
    int j=h[u][i];
    int v=e[j].v,w=e[j].w;
    if(v==fa) continue;
    printf("%d,%d,%d\n",u,v,w);
    dfs(v,u);
  }
}
int main(){
  cin>>n>>m;
  for(int i=1;i<=m;i++){
    cin>>a>>b>>c,
    add(a,b,c);
    add(b,a,c);
  }  
  dfs(1, 0);
  return 0;
}

///链式前向星 示例 
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;

const int N=510,M=3000;
int n,m,a,b,c;
struct edge{int v,w,ne;};
edge e[M];//边集
int idx,h[N];//点的第一条出边 

void add(int a,int b,int c){
  e[idx]={b,c,h[a]};
  h[a]=idx++;
}
void dfs(int u,int fa){
  for(int i=h[u];~i;i=e[i].ne){
    int v=e[i].v, w=e[i].w;
    if(v==fa) continue;
    printf("%d,%d,%d\n",u,v,w);
    dfs(v,u);
  }
}
int main(){
  cin>>n>>m;
  memset(h,-1,sizeof h);
  for(int i=1;i<=m;i++){
    cin>>a>>b>>c,
    add(a,b,c);
    add(b,a,c);
  }  
  dfs(1, 0);
  return 0;
}

 

3 深搜(DFS)算法

#include <iostream>
#include <vector>

using namespace std;

const int N = 1000;
// 利用邻接表存边的信息
vector<int> e[N];
int n, m, a, b;
void dfs(int a, int fa)
{
    for (auto t : e[a])
    {
        if (fa == t)
            continue;
        // cout<<"下走"
        dfs(t, a);
        // 上回
    }
    // 离开
}
int main()
{
    cin >> n >> m;
    for (int i = 0; i < m; i++)
    {
        cin >> a >> b;
        e[a].push_back(b);
        e[b].push_back(a);
    }
    dfs(1, 0);
    return 0;
}
例题1 Luogu P1605 迷宫
#include <iostream>
using namespace std;
const int N = 20;
// 上右下左的顺序
const int ax[4] = {0, -1, 0, 1};
const int ay[4] = {1, 0, -1, 0};
int n, m, t, sx, sy, fx, fy;
int ans = 0;
int g[N][N]; // 存储障碍信息
// 深搜
void dfs(int x, int y)
{
    if (x == fx && y == fy)
    {
        ans++;
        return;
    }
    for (int i = 0; i < 4; i++)
    {
        int a = x + ax[i], b = y + ay[i];

        if (a < 1 || a > n || b < 1 || b > m || g[a][b])
            continue;
        g[a][b] = 1;
        dfs(a, b);
        g[a][b] = 0;
    }
}
int main()
{
    cin >> n >> m >> t;
    cin >> sx >> sy >> fx >> fy;
    for (int i = 0; i < t; i++)
    {
        int x, y;
        cin >> x >> y;
        g[x][y] = 1;
    }
    g[sx][sy] = 1;
    dfs(sx, sy);
    cout << ans << endl;
    return 0;
}
例题2 P1644 跳马问题
#include <bits/stdc++.h>

using namespace std;

const int N = 18;
const int ax[] = {2, 2, 1, 1};
const int ay[] = {1, -1, 2, -2};

int n, m, ans = 0;
void dfs(int x, int y)
{
    if (x == m && y == n)
    {
        ans++;
        return;
    }
    for (int i = 0; i < 4; i++)
    {
        int a = x + ax[i], b = y + ay[i];
        if (a > m || b > n || b < 0)
            continue;
        dfs(a, b);
    }
}
int main()
{
    cin >> n >> m;
    dfs(0, 0);
    cout << ans << endl;
    return 0;
}
例题3 [Luogu P1219 USACO1.5]八皇后
/**
 * 注意找规律
 * 行关系 row 每次放隔一行放
 * 列关系 col 当可以放后,讲这一列标记为1
 * 主对角线关系 row+col相同
 * 副对角线关系 row-col相同 为了避免出现负数,给结果加一个n
 */
#include <bits/stdc++.h>
using namespace std;
const int N = 23;
int n, ans, sum;
int g[N][N];                       // 存储是否被访问
int pos[N], col[N], ma[N], sub[N]; // 其中ma为主对角线,sub为副对角线

void dfs(int x)
{
    if (x > n)
    {
        ans++;
        if (ans <= 3)
        {
            for (int i = 1; i <= n; i++)
                cout << pos[i] << " ";
            cout << endl;
        }
        return;
    }
    for (int j = 1; j <= n; j++)
    {
        if (col[j] || ma[x + j] || sub[x - j + n])
        {
            continue;
        }
        pos[x] = j;
        col[j] = 1, ma[x + j] = 1, sub[x - j + n] = 1;
        dfs(x + 1);
        col[j] = 0, ma[x + j] = 0, sub[x - j + n] = 0;
    }
}
int main()
{
    cin >> n;
    dfs(1);
    cout << ans << endl;
    return 0;
}
4 dfs树的重心

image-20240405085924613

 

image-20240405083050025

image-20240405083109983

image-20240405083307476

#include <bits/stdc++.h>
using namespace std;
/**
 * 树的重心
 * 用size记录以u为根节点的树的结点数
 * mx记录u的上方和下方的最大值
 * TODO:树的重心
 *  重心是指树种的一个结点,如果将这个结点删除后,剩余各个联通块中点数的最大值最小
 *  那么这个结点被称为树的重心。
 */
const int N = 1e6 + 10;
vector<int> e[N]; // 用邻接表存储树
int n, a, b;
int size[N], ans = 1e9, pos; // pos记录重心

void DFS(int x, int fa)
{
    size[x] = 1;
    int mx = 0;
    for (auto t : e[x])
    {
        if (t == fa)
            continue;
        // 向下递
        DFS(t, x);
        // 向上归
        size[x] += size[t];
        mx = max(mx, size[t]);
    }
    mx = max(mx, n - size[x]);
    //找出最小值
    if (ans > mx)
        ans = mx, pos = x;
}
int main()
{
    cin >> n;
    for (int i = 1; i <= n - 1; i++)
    {
        cin >> a >> b;
        e[a].push_back(b);
        e[b].push_back(a);
    }
    DFS(1, 0);
    cout << pos << " " << ans << endl;
    for (int i = 1; i <= n; i++)
        cout << size[i] << endl;
    return 0;
}
5 树的直径

image-20240405102433467

image-20240405102346349

 

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5;
struct edge
{
    int v, w;
};
vector<edge> e[N];
int n; // 结点数
int a, b, c;
int ans;
int dfs(int x, int fa)
{
    int d1 = 0, d2 = 0; // 最大边和次大边
    for (auto y : e[x])
    {
        int v = y.v, w = y.w;
        if (v == fa)
            continue;
        int d = dfs(v, x) + w;
        if (d >= d1)
            d2 = d1, d1 = d;
        else if (d > d2)
            d2 = d;
    }
    ans = max(ans, d1 + d2);
    return d1;
}
int main()
{
    cin >> n;
    for (int i = 1; i <= n - 1; i++)
    {
        cin >> a >> b >> c;
        e[a].push_back({b, c});
        e[b].push_back({a, c});
    }
    dfs(1, 0);
    cout << ans << endl;
    return 0;
}

 

6 树的中心

image-20240405110324954

image-20240405110411054

 

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;
struct edge
{
    int v, w;
};
vector<edge> e[N];  // 存储树
int d1[N], d2[N];   // 最长边和次长边权值
int up[N], path[N]; // path记录最长的路径
int a, b, c;
int n;
/**
 * 测试用例
5
2 1 1
3 2 1
4 3 1
5 1 1
*/
void dfs_d(int x, int fa)
{
    for (auto ex : e[x])
    {
        int y = ex.v, z = ex.w;
        if (y == fa)
            continue;
        dfs_d(y, x);
        if (d1[y] + z > d1[x])
            d2[x] = d1[x], d1[x] = d1[y] + z, path[x] = y;
        else if (d1[y] + z > d2[x])
            d2[x] = d1[y] + z;
    }
}
// 自上而下,由父节点更新子节点
void dfs_up(int x, int fa)
{
    for (auto ex : e[x])
    {
        int y = ex.v, z = ex.w;
        if (y == fa)
            continue;
        // 若在最长路径上
        if (path[x] == y)
            up[y] = max(up[x], d2[x]) + z;
        else
            up[y] = max(up[x], d1[x]) + z;
        dfs_up(y, x);
    }
}
int main()
{
    cin >> n;
    for (int i = 1; i < n; i++)
    {
        cin >> a >> b >> c;
        e[a].push_back({b, c});
        e[b].push_back({a, c});
    }
    dfs_d(1, 0);
    dfs_up(1, 0);
    // 求中心
    int ans = 2e9;
    for (int i = 1; i <= n; i++)
        ans = min(ans, max(d1[i], up[i]));
    cout << ans << endl;
    return 0;
}

[Luogu P1596 USACO10OCT]Lake Counting S水坑计数

#include <bits/stdc++.h>
using namespace std;
const int N = 110;

int dx[] = {-1, -1, -1, 0, 0, 1, 1, 1};
int dy[] = {-1, 0, 1, -1, 1, -1, 0, 1};
int m, n;
char ma[N][N];
int ans; // 统计数
void dfs(int x, int y)
{
    ma[x][y] = '.';
    for (int i = 0; i < 8; i++)
    {
        int a = x + dx[i], b = y + dy[i];
        if (a < 0 || a >= n || b < 0 || b >= m || ma[a][b] == '.')
            continue;
        dfs(a, b);
    }
}
int main()
{
    cin >> n >> m;
    for (int i = 0; i < n; i++)
        cin >> ma[i];
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            if (ma[i][j] == 'W')
            {
                ans++;
                dfs(i, j);
            }
        }
    }
    cout << ans << endl;
    return 0;
}

 

 

4 广搜(BFS)算法

1.bfs模板

image-20240405134019623

#include<bits/stdc++.h>
using namespace std;
const int N=1e7+10;
vector<int> e[N]; //保存图
queue<int> q; 
int vis[N];
/**
* 将相邻结点入队
* 出队时,将出队元素的所有领点入队
* 为避免重复,设置vis数组记录是否被入队 
**/
void bfs(int x)
{
	q.push(x);
	vis[x]=1;
	while(q.size())
	{
		int s=q.front();
		cout<<s<<endl;
		q.pop();
		for(auto t:e[s])
		{
			if(vis[t])
			continue;
			vis[t]=1;
			q.push(t);
		}
	}
}
int main()
{
	int n,a,b;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a>>b;
		e[a].push_back(b);
		e[b].push_back(a);
	}
	bfs(1);
}

迷宫问题

// #include <bits/stdc++.h>
#include <iostream>
#include <queue>
using namespace std;
const int N = 1010;
typedef pair<int, int> pa;
int maze[N][N];
int dx[] = {1, 0, 0, -1};
int dy[] = {0, 1, -1, 0};
int n;
struct node
{
    int x, y;
} path[N][N]; // 记录路径
void bfs(int x, int y)
{
    queue<node> q;
    q.push({x, y});
    maze[x][y] = 1;
    while (q.size())
    {
        node u = q.front();
        q.pop();
        for (int i = 0; i < 4; i++)
        {
            int a = u.x + dx[i], b = u.y + dy[i];
            if (a < 0 || a >= n || b < 0 || b >= n || maze[a][b])
                continue;
            maze[a][b] = 1;
            path[a][b] = {u.x, u.y};
            q.push({a, b});
        }
    }
}
int main()
{
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)
            cin >> maze[i][j];
    }
    bfs(n - 1, n - 1);
    node p = {0, 0};
    while (1)
    {
        if (p.x == n - 1 && p.y == n - 1)
            break;
        cout << "(" << p.x << ", " << p.y << ")" << endl;
        p = path[p.x][p.y];
    }
    puts("(4, 4)");
    return 0;
}

 

chapter 9 数学

1 高精度快速幂

image-20240316151212431

luoguP1226 快速幂

#include <bits/stdc++.h>
using namespace std;
long long a, b, p;
long long quickpow(long long a, long long n, long long p)
{
    long long res = 1;
    // 对底数倍增,对指数进行二进制拆分
    while (n)
    {
        if (n & 1)
            res = res * a % p;
        a = a * a % p;
        n >>= 1;
    }
    return res;
}
int main()
{
    cin >> a >> b >> p;
    cout << a << "^" << b << " mod " << p << "=" << quickpow(a, b, p) << endl;
    return 0;
}

[Luogu P1045 NOIP2003 普及组] 麦森数

#include <bits/stdc++.h>
using namespace std;
const int N = 500;
typedef vector<int> VI;
VI a(N), res(2 * N);
int p;
// 大整数乘法
VI mul_t(VI &a, VI &b)
{
    VI t(N * 2);
    for (int i = 0; i < N; i++)
    {
        for (int j = 0; j < N; j++)
        {
            t[i + j] += a[i] * b[j];
            t[i + j + 1] += t[i + j] / 10; // 保存进位
            t[i + j] %= 10;                // 保存位的值
        }
    }
    return t;
}
// 快速幂
void quickpow(int p)
{
    res[0] = 1, a[0] = 2;
    while (p)
    {
        if (p & 1)
            res = mul_t(a, res);
        a = mul_t(a, a);
        p >>= 1;
    }
    res[0]--;
}
int main()
{
    cin >> p;
    cout << (int)(p * log10(2) + 1) << endl;
    quickpow(p);
    for (int i = 0, k = 499; i < 10; i++)
    {
        for (int j = 0; j < 50; j++, k--)
        {
            printf("%d", res[k]);
        }
        puts("");
    }
    return 0;
}

 

2 矩阵快速幂

image-20240317083743785

Luogu P3390 【模板】矩阵快速幂

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 110;
const int mod = 1e9 + 7;
int n;
LL k;
struct matrix
{
    LL c[100][100];
    // 进行初始化
    matrix() { memset(c, 0, sizeof c); }
} A, res; // 这里的A保存原来的矩阵,res保存结果
// 重载矩阵的*号
matrix operator*(matrix &a, matrix &b)
{
    matrix t; // 用于返回结果

    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            for (int k = 1; k <= n; k++)
            {
                t.c[i][j] = (t.c[i][j] + a.c[i][k] * b.c[k][j]) % mod;
            }
        }
    }
    return t;
}
// 快速幂
void quickpow(LL k)
{
    // 初始化t为单位矩阵
    for (int i = 1; i <= n; i++)
        res.c[i][i] = 1;
    while (k)
    {
        if (k & 1)
            res = res * A;
        A = A * A;
        k >>= 1;
    }
}
int main()
{
    cin >> n >> k;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            cin >> A.c[i][j];
        }
    }
    // 利用快速幂进行计算
    quickpow(k);
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            printf("%d ", res.c[i][j]);
        }
        puts("");
    }
    return 0;
}

 

3 矩阵加速递推

image-20240317091257252

//解决斐波那契数列的方式很巧妙
斐波那契数列:f[n]=f[n-1]+f[n-2] n>2 当n<=2时,为1;
[f[n] f[n-1]]-->[f[n-1] f[n-2]]

f[n]=f[n-1]*1+f[n-2]*1
f[n-1]=f[n-1]*1+f[n-2]*0
可以推出=>[f[n] f[n-1]]=[f[n-1] f[n-2]]*{[1 1]*[1 0]}

Luogu P1962 斐波那契数列

#include <bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
typedef long long LL;
LL n;//要注意体重要求的n的值的范围
struct matrix
{
    LL c[3][3];
    // 矩阵初始化
    matrix() { memset(c, 0, sizeof c); }
} A, res;

// 重载矩阵乘法
matrix operator*(matrix &a, matrix &b)
{
    matrix t; // 用于返回结果
    for (int i = 1; i <= 2; ++i)
        for (int j = 1; j <= 2; ++j)
            for (int k = 1; k <= 2; ++k)
                t.c[i][j] = (t.c[i][j] + a.c[i][k] * b.c[k][j]) % mod;
    return t;
}

// 进行快速幂运算
void quickpow(LL n)
{
    // 初始化A矩阵
    A.c[1][1] = A.c[1][2] = A.c[2][1] = 1;
    // 初始化res矩阵
    res.c[1][1] = res.c[1][2] = 1;
    while (n)
    {
        if (n & 1)
            res = res * A;
        A = A * A;
        n >>= 1;
    }
}
int main()
{
    cin >> n;
    if (n <= 2)
    {
        puts("1");
        return 0;
    }
    quickpow(n - 2);
    cout << res.c[1][1] << endl;
    return 0;
}

Luogu P1939 【模板】矩阵加速(数列)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 1e9 + 7;
LL n;
int t;
//
struct matrix
{
    LL c[4][4];
    matrix() { memset(c, 0, sizeof c); }
} A, ans;
// 重载乘法运算
matrix operator*(matrix &a, matrix &b)
{
    matrix t;
    for (int i = 1; i <= 3; i++)
        for (int j = 1; j <= 3; j++)
            for (int k = 1; k <= 3; k++)
                t.c[i][j] = (t.c[i][j] + a.c[i][k] * b.c[k][j]) % mod;
    return t;
}
// 快速幂
void quickpow(LL n)
{
    // 初始化
    ans.c[1][1] = ans.c[1][2] = ans.c[1][3] = 1;
    memset(A.c, 0, sizeof A.c);
    A.c[1][1] = A.c[3][1] = A.c[1][2] = A.c[2][3] = 1;
    while (n)
    {
        if (n & 1)
            ans = ans * A;
        A = A * A;
        n >>= 1;
    }
}
int main()
{
    cin >> t;
    while (t--)
    {
        cin >> n;
        if (n <= 3)
        {
            puts("1");
            continue;
        }

        quickpow(n - 3);
        cout << ans.c[1][1] << endl;
    }
    return 0;
}

4 进制转换

16进制转8进制

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int c[N];
/**
 * cin、cout、printf 默认都是十进制式,如果需要输入或输出十六进制,
 * 需要重新指定进制式。对于cin、cout ,其中 oct为八进制,hex为十六进制,dec为十进制。
 * 特别注意,一旦指明进制数后,将一直有效,除非重新指定进制数。
 */
/**
 * 将16进制的数先转换为2进制数,之后通过在高位补0,3位3位的取出值,转换为8进制的数
 */

int main()
{
    int n;
    cin >> n;
    char ch;
    while (n--)
    {
        string s, s2;
        s2 = "";
        cin >> s;
        for (int i = 0; i < s.size(); i++)
        {
            switch (s[i])
            {
            case '0':
                s2 += "0000";
                break;
            case '1':
                s2 += "0001";
                break;
            case '2':
                s2 += "0010";
                break;
            case '3':
                s2 += "0011";
                break;
            case '4':
                s2 += "0100";
                break;
            case '5':
                s2 += "0101";
                break;
            case '6':
                s2 += "0110";
                break;
            case '7':
                s2 += "0111";
                break;
            case '8':
                s2 += "1000";
                break;
            case '9':
                s2 += "1001";
                break;
            case 'A':
                s2 += "1010";
                break;
            case 'B':
                s2 += "1011";
                break;
            case 'C':
                s2 += "1100";
                break;
            case 'D':
                s2 += "1101";
                break;
            case 'E':
                s2 += "1110";
                break;
            case 'F':
                s2 += "1111";
                break;
            default:
                break;
            }
        }
        int len = s2.size();
        if (len % 3 == 1)
            s2 = "00" + s2;
        else if (len % 3 == 2)
            s2 = "0" + s2;
        int flag = 0;
        for (int i = 0; i <= s2.size() - 3; i += 3)
        {
            int num = 4 * (s2[i] - '0') + 2 * (s2[i + 1] - '0') + (s2[i + 2] - '0');
            if (num)
                flag = 1;
            if (flag)
                printf("%d", num);
        }
        puts("");
    }

    return 0;
}

4 全排列问题

#include <bits/stdc++.h>
using namespace std;
int a[10];
int n;
int main()
{
    cin >> n;
    int j = 1; // 计算全排列的大小
    for (int i = 1; i <= n; i++)
    {
        a[i] = i;
    }
    do
    {
        for (int j = 1; j <= n; j++)
            printf("%5d", a[j]);

        puts("");
        /* code */
    } while (next_permutation(a + 1, a + n + 1));//调用c++的库函数
    return 0;
}

5 最大公约数-欧几里得算法

image-20240419215254742

image-20240419215326361

[Luogu P1029 NOIP2001 普及组] 最大公约数和最小公倍数问题
#include<iostream>
using namespace std;
#define int long long
int x,y;
int ans;

int gcd(int a,int b)
{	
	return b==0?a:gcd(b,a%b);
}

signed main()
{
	cin>>x>>y;
	int t=x*y;
	for(int i=1;i*i<=t;i++)
	{
		if(t%i==0&&gcd(i,t/i)==x)
		ans+=2;
	}
	if(x==y)
	ans--;
	cout<<ans<<endl;
	return 0; 
}

6 试除法判质数

image-20240827221852670

Luogu P5736 【深基7.例2】质数筛
#include <iostream>
#include <math.h>
// #include<bits/stdc++.h> //万能头文件,切记有些比赛是禁用的
using namespace std;

bool is_prim(int a)
{
    if (a == 1)
        return 0;
    for (int i = 2; i <= sqrt(a); i++) //为了避免i*i<=a,i*i过大,故这里采用根号
    {
        if (a % i == 0)
            return 0;
    }
    return 1;
}
int main()
{
    int n;
    cin >> n;
    int a;
    for (int i = 0; i < n; i++)
    {
        cin >> a;
        if (is_prim(a))
        {
            cout << a << " ";
        }
    }
    cout << endl;
    return 0;
}

7 分解质因数-唯一分解定理 试除法

image-20240828210905080

image-20240828211038736

 

Luogu P2043 质因子分解
-- 唯一分解定理:任何一个数都可以是 一些质数的次方的乘积

#include <iostream>
using namespace std;

const int N = 1e4 + 10;
int a[N]; // 质因子的个数
int n;

void decompose(int x) // 分解质因数
{
    for (int i = 2; i * i <= x; i++)
    {
        while (x % i == 0)
        {
            a[i]++;
            x /= i;
        }
    }
    if (x > 1)
    {
        a[x]++;
    }
}
int main()
{
    cin >> n;
    for (int i = 2; i <= n; i++)
        decompose(i);
    for (int i = 2; i <= n; i++)
    {
        if (a[i])
        {
            cout << i << " " << a[i] << endl;
        }
    }
    return 0;
}

欧拉筛法,筛质数

image-20240912000003660

 

Luogu P3383 【模板】线性筛素数
#include <iostream>
using namespace std;

typedef long long LL;

const int N = 1e8 + 10;

int n, q;
int vis[N];  // 划掉合数
int prim[N]; // 记录质数
int cnt;     // 记录质数个数

// 线性筛法
void get_prim(int n)
{
    for (int i = 2; i <= n; i++)
    {
        if (!vis[i])
            prim[++cnt] = i;
        for (int j = 1; 1LL * i * prim[j] <= n; j++)
        {
            vis[i * prim[j]] = 1;
            if (i % prim[j] == 0)
                break;
        }
    }
}

int main()
{
    cin >> n >> q;
    get_prim(n);
    int t;
    while (q--)
    {
        cin >> t;
        cout << prim[t] << endl;
    }
    return 0;
}

 

 

chapter10 STL

1 set

 

P5250 木材仓库

 

 

chapter11 每日一题

2024/4/17

1 团队舞力最大值

 

思路:

本题主要难点为要解决超时问题,再尽可能短的时间内,运行出结果。可以从数组的第2个元素开始循环数组,直到倒数第2个,找出左右两边的最大值,在除以当前下标为i的数组值,并与当前的最大值做判断,更新最大值。

注意:为了避免在找最值时造成时间的浪费,在利用数组保存最值。

#include <iostream>
using namespace std;
#include <algorithm>
int main()
{
  int N;
  cin>>N;
  int *W=new int[N];
  int *maxl=new int [N];
  int *maxr=new int [N];
  if(N>=3)
  {
    for(int i=0;i<N;i++)
    {
      cin>>W[i];
    }
    maxl[0]=W[0];
    maxr[N-1]=W[N-1];
    for(int i=1;i<N;i++)
    {
    maxl[i]=max(maxl[i-1],W[i]);
    }
    for(int i=N-2;i>=0;i--)
    {
      maxr[i]=max(maxr[i+1],W[i]);
    }
    int maxW=0;
    for(int i=1;i<N-2;i++)
    {
      int ml=maxl[i-1];
      int mr=maxr[i+1];
      int w=(ml+mr)/W[i];
      if(maxW<w)
      {
        maxW=w;
      }
    }
    cout<<maxW;
    return 0;
  }
  else
  {
    return 0;
  } 
}

 

2 leetcode 392. 判断子序列

给定字符串 st ,判断 s 是否为 t 的子序列。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace""abcde"的一个子序列,而"aec"不是)。

进阶:

如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?

致谢:

特别感谢 @pbrother 添加此问题并且创建所有测试用例。

 

示例 1:

输入:s = "abc", t = "ahbgdc"
输出:true

示例 2:

输入:s = "axc", t = "ahbgdc"
输出:false

题解思路:在进行本题时,可以利用string中的find函数去解决,也可以使用双指针去遍历。核心时要子串中的所有字符,按递增顺序出现在父串中。利用官方题解中的双指针更好一些。

代码如下:

利用string中的find函数。

class Solution {
public:
    bool isSubsequence(string s, string t) {
        int size=s.size();
        int cpos=0;
        for(int i=0;i<size;i++)
        {
            int pos=t.find(s[i],cpos);
            if(pos==-1)
            {
                return false;
            }
            else
            {
                cpos=pos+1;
            }
        }
        return true;     
    }
};
//利用双指针
class Solution {
public:
    bool isSubsequence(string s, string t) {
        int s_size=s.size(),t_size=t.size();
        int i=0,j=0;
        while(i<s_size&&j<t_size)
        {
            if(s[i]==t[j])
            {
                i++;
            }
            j++;
        }
        return i==s;
        
    }
};

2024/4/18

1 2007. 从双倍数组中还原原数组
//总结如下:
/*
实现方式:
1.排序+哈希表
2.排序+队列
3.消消乐
*/
class Solution {
public:
    vector<int> findOriginalArray(vector<int>& changed) {
        if(changed.size()%2!=0)
        return {};
        sort(changed.begin(),changed.end());
        const int N=1e5+10;
        vector<int> a;
        int vis[N];//标注是否被访问
        memset(vis,0,sizeof vis);
        for(int l=0,r=1;l<changed.size();l++)
        {
            if(vis[l])
            continue;
            // for(int r=l+1;r<changed.size();r++)
            // {
            //     if(changed[l]!=0)
            //     {
            //         if(changed[l]*2==changed[r]&&vis[r]==0)
            //         {
            //             a.push_back(changed[l]);
            //             //cout<<changed[l]<<endl;
            //             vis[l]=vis[r]=1;
            //             break;
            //         }
            //     }
            //     else
            //     {
            //         if(changed[r]==0&&vis[r]==0)
            //         {
            //             a.push_back(changed[l]);
            //             //cout<<changed[l]<<endl;
            //             vis[l]=vis[r]=1;
            //             break;
            //         }
            //     }
                
            // }
            while((l==r)||(r<changed.size()&&changed[l]*2!=changed[r]))
            r++;
            if(r==changed.size())
            return {};
            vis[r++]=1;
            a.push_back(changed[l]);
        }
        // if(2*a.size()==changed.size())
        // return a;
        // else
        // return {};
        return a;

    }
};

2024/4/21

洛谷Treasure G P6204 [USACO07CHN] Treasure G - 洛谷
//本题初步思想为最小生成树,然后判断有几层

2024/5/2

1 luoguP1090(见基础算法-贪心算法1)

 

chapter 12 真题

P8597 [蓝桥杯 2013 省 B] 翻硬币

P8598 [蓝桥杯 2013 省 AB] 错误票据

P8599 [蓝桥杯 2013 省 B] 带分数

P8600 [蓝桥杯 2013 省 B] 连号区间数

P8601 [蓝桥杯 2013 省 A] 剪格子

P8602 [蓝桥杯 2013 省 A] 大臣的旅费

P8611 [蓝桥杯 2014 省 AB] 蚂蚁感冒

P8612 [蓝桥杯 2014 省 AB] 地宫取宝

P8613 [蓝桥杯 2014 省 B] 小朋友排队

P8614 [蓝桥杯 2014 省 A] 波动数列

P8623 [蓝桥杯 2015 省 B] 移动距离

P8624 [蓝桥杯 2015 省 AB] 垒骰子

P8625 [蓝桥杯 2015 省 B] 生命之树

P8626 [蓝桥杯 2015 省 A] 灾后重建

P8627 [蓝桥杯 2015 省 A] 饮料换购

P8635 [蓝桥杯 2016 省 AB] 四平方和

P8636 [蓝桥杯 2016 省 AB] 最大比例

P8637 [蓝桥杯 2016 省 B] 交换瓶子

P8638 [蓝桥杯 2016 省 A] 密码脱落

P8646 [蓝桥杯 2017 省 AB] 包子凑数

P8647 [蓝桥杯 2017 省 AB] 分巧克力

P8648 [蓝桥杯 2017 省 A] 油漆面积

P8649 [蓝桥杯 2017 省 B] k 倍区间

P8650 [蓝桥杯 2017 省 A] 正则问题

P8651 [蓝桥杯 2017 省 B] 日期问题

P8661 [蓝桥杯 2018 省 B] 日志统计

P8662 [蓝桥杯 2018 省 AB] 全球变暖

P8663 [蓝桥杯 2018 省 A] 倍数问题

P8664 [蓝桥杯 2018 省 A] 付账问题

P8665 [蓝桥杯 2018 省 A] 航班时间

P8666 [蓝桥杯 2018 省 A] 三体攻击

P8667 [蓝桥杯 2018 省 B] 递增三元组

P8668 [蓝桥杯 2018 省 B] 螺旋折线

P8669 [蓝桥杯 2018 省 B] 乘积最大

P8678 [蓝桥杯 2019 省 A] 填空问题

P8679 [蓝桥杯 2019 省 B] 填空问题

P8680 [蓝桥杯 2019 省 B] 特别数的和

P8681 [蓝桥杯 2019 省 AB] 完全二叉树的权值

P8682 [蓝桥杯 2019 省 B] 等差数列

P8683 [蓝桥杯 2019 省 B] 后缀表达式

P8684 [蓝桥杯 2019 省 B] 灵能传输

P8685 [蓝桥杯 2019 省 A] 外卖店优先级

P8686 [蓝桥杯 2019 省 A] 修改数组

P8687 [蓝桥杯 2019 省 A] 糖果

P8688 [蓝桥杯 2019 省 A] 组合数问题

P8704 [蓝桥杯 2020 省 A1] 填空问题

P8705 [蓝桥杯 2020 省 B1] 填空问题

P8706 [蓝桥杯 2020 省 AB1] 解码

P8707 [蓝桥杯 2020 省 AB1] 走方格
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 40;
ll ans; // 保存方案数
int dx[] = {1, 0};
int dy[] = {0, 1};
int n, m;
void dfs(int x, int y)
{
    if (x == n && y == m)
    {
        ans++;
    }
    for (int i = 0; i < 2; i++)
    {
        int a = x + dx[i], b = y + dy[i];
        if (a > n || b > m || (a % 2 == 0 && b % 2 == 0))
            continue;
        dfs(a, b);
    }
}
int main()
{
    cin >> n >> m;
    if (n % 2 == 0 && m % 2 == 0)
        ans = 0;
    else
        dfs(1, 1);
    cout << ans << endl;
    return 0;
}
/**
 * 深搜会超时
 * 1.记忆化搜索
 * 2.动态规划
 *
 */
// 1.记忆化搜索
#include <bits/stdc++.h>
using namespace std;
const int N = 40;
int f[N][N]; // 保存方案数
int dx[] = {1, 0};
int dy[] = {0, 1};
int n, m;
int dfs(int x, int y)
{
    if (x & 1 || y & 1) // 满足条件
    {
        if (f[x][y]) // 是否被访问
            return f[x][y];
        for (int i = 0; i < 2; i++)
        {
            int a = x + dx[i], b = y + dy[i];
            if ((a & 1 || b & 1) == 0 || a > n || b > m)
                continue;
            f[x][y] += dfs(a, b);
        }
    }
    return f[x][y];
}
int main()
{
    cin >> n >> m;
    f[n][m] = n & 1 || m & 1;
    if (!f[n][m])
        puts("0");
    else
        cout << dfs(1, 1) << endl;
    return 0;
}

//2.动态规划
/**
 * 动态规划解决走方格问题
 * 状态转移方程
 * f(i,j)=f(i-1,j)+f(i,j-1)
 */

#include <bits/stdc++.h>
using namespace std;
const int N = 40;
int n, m;
int f[N][N];
int main()
{
    cin >> n >> m;
    f[1][1] = 1;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            // 判断是否满足条件可以走
            if ((i & 1 || j & 1) && (i != 1 || j != 1)) // i,j不同为偶数,且i,j不同时等于0
            {
                f[i][j] = f[i - 1][j] + f[i][j - 1];
            }
        }
    }
    cout << f[n][m] << endl;
    return 0;
}

 

P8708 [蓝桥杯 2020 省 A1] 整数小拼接

P8709 [蓝桥杯 2020 省 A1] 超级胶水

P8710 [蓝桥杯 2020 省 AB1] 网络分析

P8711 [蓝桥杯 2020 省 B1] 整除序列

P8712 [蓝桥杯 2020 省 B1] 整数拼接

P8713 [蓝桥杯 2020 省 A2] 填空问题

P8714 [蓝桥杯 2020 省 B2] 填空问题

P8715 [蓝桥杯 2020 省 AB2] 子串分值

P8716 [蓝桥杯 2020 省 AB2] 回文日期

P8717 [蓝桥杯 2020 省 AB2] 成绩分析

P8718 [蓝桥杯 2020 省 A2] 荒岛探测

P8719 [蓝桥杯 2020 省 AB2] 字串排序

P8720 [蓝桥杯 2020 省 B2] 平面切分

P8721 [蓝桥杯 2020 省 AB3] 填空问题(缺少 inc.txt, E 题数据)

P8722 [蓝桥杯 2020 省 AB3] 日期识别

P8723 [蓝桥杯 2020 省 AB3] 乘法表

P8724 [蓝桥杯 2020 省 AB3] 限高杆

P8725 [蓝桥杯 2020 省 AB3] 画中漂流

P8726 [蓝桥杯 2020 省 AB3] 旅行家

P8740 [蓝桥杯 2021 省 A] 填空问题

P8741 [蓝桥杯 2021 省 B] 填空问题

P8742 [蓝桥杯 2021 省 AB] 砝码称重

P8743 [蓝桥杯 2021 省 A] 异或数列

P8744 [蓝桥杯 2021 省 A] 左孩子右兄弟

P8745 [蓝桥杯 2021 省 AB] 括号序列

P8746 [蓝桥杯 2021 省 A] 分果果

P8747 [蓝桥杯 2021 省 B] 双向排序

P8748 [蓝桥杯 2021 省 B] 时间显示

P8749 [蓝桥杯 2021 省 B] 杨辉三角形

P8750 [蓝桥杯 2021 省 A2] 填空问题

P8751 [蓝桥杯 2021 省 B2] 填空问题

P8752 [蓝桥杯 2021 省 B2] 特殊年份

P8753 [蓝桥杯 2021 省 AB2] 小平方

P8754 [蓝桥杯 2021 省 AB2] 完全平方数

P8755 [蓝桥杯 2021 省 AB2] 负载均衡

P8756 [蓝桥杯 2021 省 AB2] 国际象棋

P8757 [蓝桥杯 2021 省 A2] 完美序列

P8770 [蓝桥杯 2022 省 A] 填空问题

P8771 [蓝桥杯 2022 省 B] 填空问题

P8772 [蓝桥杯 2022 省 A] 求和

P8773 [蓝桥杯 2022 省 A] 选数异或

P8774 [蓝桥杯 2022 省 A] 爬树的甲壳虫

P8775 [蓝桥杯 2022 省 A] 青蛙过河

P8776 [蓝桥杯 2022 省 A] 最长不下降子序列P8777 [蓝桥杯 2022 省 A] 扫描游戏

P8778 [蓝桥杯 2022 省 A] 数的拆分

P8779 [蓝桥杯 2022 省 A] 推导部分和

P8780 [蓝桥杯 2022 省 B] 刷题统计

P8781 [蓝桥杯 2022 省 B] 修剪灌木

P8782 [蓝桥杯 2022 省 B] X 进制减法

P8783 [蓝桥杯 2022 省 B] 统计子矩阵

P8784 [蓝桥杯 2022 省 B] 积木画

P8786 [蓝桥杯 2022 省 B] 李白打酒加强版

P8787 [蓝桥杯 2022 省 B] 砍竹子

P9230 [蓝桥杯 2023 省 A] 填空问题

P9231 [蓝桥杯 2023 省 A] 平方差

P9232 [蓝桥杯 2023 省 A] 更小的数

P9233 [蓝桥杯 2023 省 A] 颜色平衡树

P9234 [蓝桥杯 2023 省 A] 买瓜

P9235 [蓝桥杯 2023 省 A] 网络稳定性

P9236 [蓝桥杯 2023 省 A] 异或和之和

P9237 [蓝桥杯 2023 省 A] 像素放置

P9238 [蓝桥杯 2023 省 A] 翻转硬币

P9240 [蓝桥杯 2023 省 B] 冶炼金属

P9241 [蓝桥杯 2023 省 B] 飞机降落

P9242 [蓝桥杯 2023 省 B] 接龙数列

chapter 13 蓝桥杯国赛真题

细碎知识

1 文件读取

#include<iostream>
#include<fstream>
//注意string不能少
#include<string>
using namespace std;
int main()
{
    ifstream infile;
    infile.open("./numlist.txt",ios::in);
    if(!infile.is_open())
    {
        return 0;
    }
    int ans=0;
    char a[1024];
    while(infile>>a)
    {
        unsigned long long b=stoll(a);//数字与字符串转换
        if(!(b&1))
        ans++;
    }
    cout<<ans<<endl;
    return 0;

}

 

 

 
posted @ 2025-06-22 22:29  自在_随心  阅读(14)  评论(0)    收藏  举报  来源