程序设计思维实践总结

IO方式

  1. 标准输入输出,scanf,printf

    scanf 识别给定字符类型,忽略空格换行,识别末位条件:scanf()!=EOF ;printf最后输出注意**\n** 来换行;数据范围:

    int:-109-109

    long:-1018-1018

    float 32 +/- 3.40282e+038 %f、

    double 64 +/- 1.79769e+308 %lf、

    scanfprintf
    double%lf%f
    int%d-
    float char-%f %c
  2. 头文件为#include<bits/stdc++.h>万能格式;

  3. *isupper()/islower()*判断大小写

    直接使用‘a’,’z‘判断

  4. string s;
    while(getline(cin,s)){
        stringstream ss(s);
        int sum=0,x;
        while(ss>>x){
            sum+=x;
        }
    

    getline获取一行字符,到达文件末尾退出;

    stringtream&int/char/string搭配使用用于字符类型转换;ss自动带有指针,当识别完成后返回false

    ss每次使用后第二次使用时:ss.clear()清除上一次内容才可以

  5. error 时钟角度计算,时针不是标准旨在整点上而是会有分数的部分;

  6. **vector(size,value)**size为大小,v为初始值;

  7. 对于输入一行的问题,采用先getchar吞字符,然后getline(cin,s);

  8. 字符读入

    int init()
    {//读取一个整数并带有符号
        int x=0,f=1;char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
        while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
        return x*f;
    }
    
    

9.按照某种格式读取

        scanf("%d'%d%c", &mm, &ss, &cc);//2‘55’‘时间

10:printf(“%02d”,mon);

image-20210616201126558

11.时间格式

固定转化:image-20210616210818243

涉及到年月日时分秒,全部转化成统一单位s、min处理;

如果有跨天,最后加上一个周期。

读入格式有限制,使用规定格式读取:

scanf("%d:%d-%d:%d",&h1,&m1,&h2,&m2);
printf("%02d:%02d-%02d:%02d\n",h1,m1,h2,m2);

sscanf(s,"%d(%d)",x,y);//从字符中格式化读取

while (scanf("%d%d%d",&x,&y,&C)!=EOF){

12.结构体+比较函数+map读取+vector排序

struct Node {
    int num;
    int count;
};
 
bool compare(Node n1, Node n2) {
    if (n1.count != n2.count) {
        return n1.count > n2.count;
    }
    return n1.num < n2.num;
}
 
int main() {
    int n;
    cin >> n;
    map<int, int> dict;
    vector<Node> list;
    for (int i = 0; i < n; i++) {
        int tmp;
        cin >> tmp;
        dict[tmp]++;
    }
 
    map<int, int>::iterator it = dict.begin();
    for (it; it != dict.end(); it++) {
        list.push_back({it->first, it->second});
    }
    sort(list.begin(), list.end(), compare);
    for (int i = 0; i < list.size(); i++) {
        Node node = list[i];
        cout << node.num << " " << node.count;
        if (i != list.size() - 1) {
            cout << endl;
        }
    }
    return 0;
}

13.输入字符串,读取其中的数字;将数字转化成字符

string s;
	cin>>s;
	int num = 0, count = 1;
	for(int i = 0; i < s.size()-1; i++){
		if(s[i] >= '0' && s[i] <= '9'){
			num += (s[i] - '0') * count++;
		}
	}
//
num = num%11;
	char ans;
	if(num == 10) ans = 'X';
	else ans = num + '0';

俄罗斯方块模拟题

#include<iostream>
using namespace std;
const int N = 15, M = 10, R = 4;

int map[N + 1][M + 1];                           //15 x 10的地图
int point[N + 1][2];                             //方案里面存在的点
int cnt, x, flag = 1;


int main()
{<!-- -->
    for (int i = 1; i <= N; i++)                 //读入地图
        for (int j = 1; j <= M; j++)
            cin >> map[i][j];

    for (int i = 1; i <= R; i++)
        for (int j = 1; j <= R; j++)
        {<!-- -->
            cin >> x;
            if (x)                               //把图案里面存在的点存起来
            {<!-- -->
                point[cnt][0] = i;
                point[cnt++][1] = j;
            }
        }
    cin >> x;

    for (int i = 0; i < cnt; i++)
    {<!-- -->    
        point[i][1] = point[i][1] + x - 1;      //把所有点的位置更新
    }
    while (flag == 1)
    {<!-- -->
        for (int i = 0; i < cnt; i++)          //先给所有的点位置进行移动
            point[i][0] += 1;      
        for (int i = 0; i < cnt; i++)
            if (map[point[i][0]][point[i][1]] || point[i][0] > 15)   //如果地图上对应点有障碍,或者越界了flag = 0
                flag = 0;
        if (!flag)                             //如果flag等于0,那么当前的点都不合法,我们需要退一步
            for (int i = 0; i < cnt; i++)
            {<!-- -->
                point[i][0] -= 1;
                map[point[i][0]][point[i][1]] = 1;      //这里把合法的点在地图上标记,然后退出循环
            }
    }
    for (int i = 1; i <= N; i++)                     //输出
    {<!-- -->
        for (int j = 1; j <= M; j++)
            if (j == 1) cout << map[i][j];
            else cout << " " << map[i][j];
        cout << endl;
    }


}

易错小点程序书写规范

  1. 开数组大数组开在全局变量
  2. 使用min,max,abs,fabs来减少代码量
  3. 初始化定义
  4. memset初始化只用于0,-1;其他使用fill函数。
  5. 使用vector注意是否为空,空不能访问
  6. 使用数组检查下标是否合法,-1,操作格外注意 。

CSP骗分

image-20210331063320413

STL

  1. pair<>,数对,字典序比较大小

  2. vector,数组扩充每次*2,复杂度为O(1)

    • v.emplace(x.begin()+1,1)在第二个位置直接插入1,不复制构造

    循环写法

    for(vector::iterator it = ~~)

    • for(int i:v) {}
    • for(auto i:v)//只变化
    • for(auto& i:v)
  3. list链表,reverse_iterator,

    for(auto& i:l)

    li.reverse()

    去重li.sort(),li。unique()

    删除全部li。remove

  4. string,substr(2,3)//public–bli;

    s.c_str()返回常量字符数组

  5. stack《》栈

  6. 大根堆 priority_queue<int,greater > /less

默认为最大堆;定义<右边的边的优先级更高,可以对结构体定义。

  1. 复杂结构体大小比较:

    struct P{

    ​ int x,y,z;

    bool operator<(const P &p)const{

    if(x!=p.x) return x<p.x;

    if(y!=p.y) return y<p.y;

    return z<p.z;

    }

    }

  2. sort

  3. vector v={1,2,3}

    //传起始,结束指针、迭代器,返回找到位置迭代器

    cout<<*lower_bound(a,a+6,4)/*upperbound(a,a+6,3),位置

    *min_element()a,a+10),最小值位置

    reverse(a+2,a+6)

    排列 next_permutation()

    p19

  4. algorithm:

image-20210615134601880匿名函数可以使用;

lower_bound(data.begin(), data.end(), i)

image-20210618200020264

搜索

bfs

vis,dis,queue

image-20210409064121504

image-20210409064722693

迷宫问题bfs:

image-20210615135052150

image-20210615141338359

直接进行一次BFS,把起始的所有点都一次性添加进队列中,dis
初值赋为0(其余点的dis值为inf),最终得到的dis值就是到该点的最小距离。更好的理解方式:加一个超级源点 -> 到各起始点的代价是 0

dfs

image-20210615142120772选这个数,不选这个数;

image-20210615142324090

再卖菜暴力:

image-20210615143217194

image-20210615143623964

image-20210409064255691

image-20210409064812866

image-20210409064905506

嵌入式

贪心二分***

  • 背包问题:价值最大,重量限制:

单位重量价值挑选;

  • 最轻的人选择能与它一起坐船的最重的

  • 剩下的人越轻越好

  • 每步选取最优

  • 证明:

    反证法:如果交换方案中任意两个元素/相邻的两个元素后,答案不会变得更好,那么可以推定目前的解已经是最优解了。
    归纳法:先算得出边界情况(例如 n=1 )的最优解F
    1 ,然后再证明:对于每个Fn+1 , 都可以由 Fn 推导出结果。

  • 题型 :最常见的贪心题型有两种。
    1.我们将 XXX 按照某某顺序排序,然后按某种顺序(例如从小到大)选择。–离线的,先处理后选择
    2.我们每次都取 XXX 中最大/小的东西,并更新 XXX。–在线的,边处理边选择。

  • 解法:排序解法
    用排序法常见的情况是输入一个包含几个权值的数组,通过排序然后遍历模拟计算的方法求出最优值。
    后悔解法
    思路是无论当前的选项是否最优都接受,然后进行比较,如果选择之后不是最优了,则反悔,舍弃掉这个选项;否则,正式接受。如此往复。

  • 区间调度问题:选择 —— 最早的完成时间 Fi 或者最晚的开始时间Si

    证明:

从左向右选,一开始的范围是(-∞,+∞),每选择一个区间后就变成了
[Fi ,+∞),让这个范围尽可能越大越好,所以每次要选结束时间最早的,
这样选完之后剩下的范围最大

  • 工作调度 先假设每一项工作都做,将各项工作按截止时间排序后入队;
    • 在判断第 i 项工作做与不做时,若其截至时间符合条件,则将其与队中报酬最小的元素比较,若第 i 项工作报酬较高(后悔),则 ans += a[ i ].p - q.top () 。用优先队列(小根堆)来维护队首元素最小。

    struct Node{
    10   ll d,p;
    11 }node[maxn];
    12 priority_queue<ll,vector<ll>,greater<ll> >q;
    13 bool cmp(Node a,Node b){
    14   return a.d<b.d;
    15 }
    16 int main(){
    17   cin>>n;
    18   for(ll i=1;i<=n;i++) cin>>node[i].d>>node[i].p;
    19   sort(node+1,node+n+1,cmp);
    20   for(ll i=1;i<=n;i++){
    21     if(node[i].d>q.size()){
    22       ans+=node[i].p;q.push(node[i].p);
    23     }
    24     else{
    25       if(node[i].p>q.top()){
    26         ans+=node[i].p-q.top();q.pop();q.push(node[i].p); 
    27       }
    28     }
    29   }
    30   cout<<ans<<endl;
    31   return 0;
    32 } 
    
  • 国王游戏

image-20210330214728781

image-20210330214749523

二分

int find(int k){
    int l=1,r=n,mid;
    while(l<r){
        mid=(l+r)/2;
        if(a[mid]>=k) r=mid;
        else l=mid+1;
    }
    return l;
}
//float
double find(){
    double l=1,r=10;
    while(abs(r-l)>eps){
        double mid=(l+r)/2;
        if(f(mid)<=0){
            l=mid;
        }else{
            r=mid;
        }
    }
    return l;
}
  • 二分答案 看例题系统上的DC题https://oj.qd.sdu.edu.cn/contest/33/problem/3 https://oj.qd.sdu.edu.cn/contest/33/problem/4

二分答案

最大段最小

image-20210618231726815

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[100002];
int n,m;
int check(int x){
    int sum=0,t=0;
    for(int i=0;i<n;i++){
        if(sum+a[i]<=x) sum+=a[i];
        else {sum=a[i]; t++;}
    }
    return t;
}
int main(){
    cin>>n>>m;
    int l=-1;
    int r=0;
    for(int i=0;i<n;i++){
        scanf("%d",&a[i]);
        if(a[i]>l) l=a[i];
        r+=a[i];
    }
    int mid=(l+r)/2;
    while(l<r){
        if(check(mid)<m) r=mid;
        else l=mid+1;
        mid=(l+r)/2; 
    }
    cout<<l;   
}

平均值最大

二分两种类型

auto lower = std::lower_bound(data.begin(), data.end(), i);

返回指向范围 [first, last) 中首个不小于(即大于或等于) value 的元素的迭代器,或若找不到这种元素则返回 last

最小值最大问题–返回右边界需要取到右边界:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JcxQjSSb-1626147807075)(…/…/…/pictures-md/image-20210421200041657.png)]

最大值最小问题–返回需要能取到左边界:

image-20210421200122684

H4贪心二分实验OJ题目分析

  1. 按照右端点排序,从右向左遍历 贪心[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-igoeiIlt-1626147807077)(…/…/…/pictures-md/image-20210408212313195.png)]

贪心选择标准按照r排序,每一个从右往左遍历标记;mlogm+m(r-l)+n O(n)

sort复杂度为nlogn

  • 排序标准image-20210408213355692
  • 贪心选择过程image-20210408213417423

流水作业调度Johnson法则image-20210408213514496

  • Johnson法则 n+a1loga1+a2+loga2

image-20210408214728938

image-20210408214810133

image-20210408214820644

两个时间根据累计的原则画图得出:

image-20210408214933760

3.数分成几段连续的一段数重量和最大的尽可能小二分答案

  • 二分答案 最大重量为l,总重量为r,对于mid若使用mid进行分段使每一段不大于它,分段数大于则重量和应该增加,右侧;反之左侧;最后二分选出一个;nlogn;

通用写法选择左侧:右侧可能为答案;

image-20210408215647415

while(l<r){
        int mid =(l+r)>>1;

        if(满足条件){
            l=mid+1;
        }
        else{
            r=mid;
        }
    }
return l;

具体对条件处理时注意边界;

  • 浮点二分image-20210408221635968

二分答案,前缀和数组;数据处理每个数减去平均数;

nlogn

img

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-II3xbEby-1626147807086)(https://gitee.com/mth01/imgs_md/raw/master/imgs/20210713112208.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kN5BPicp-1626147807087)(…/…/…/pictures-md/20190212185049826.png)]

image-20210618232446814

数学基础

取模运算image-20210401061952474

image-20210401062155790

image-20210401062333812

image-20210401062732610

image-20210401062846576

  • 快速幂

image-20210401063555291

qpow()

ll qpow(int a,int p){
	int re=1;
	while(p){
		if(p&1) re=re*p%mod;
		a=a*a;
		p>>1;
	}
	return re;
}

前缀和13

•前缀和作用•

O(1) 求出一个区域所有元素数值之和

•当涉及快速求取某一区域和时,可考虑使用前缀和进行快速计算

•即前缀和通常用于优化算法中的某一步骤,进而降低复杂度

•一维前缀和•sum[i] = sum[i-1] + a[i]•sum[L, R] = sum[R] –sum[L-1]

image-20210401063811918

  • 差分

image-20210401063915574

  • 尺取法

image-20210401064221263

image-20210401064237650

image-20210401064358628

image-20210401064416743

  • 单调栈

image-20210401064519208

image-20210401064605810

  • 单调队列

image-20210401065146594

h5数学运算课后练习

image-20210407135157275

1.二维前缀和判断 O(n)=nx26x2

判断一个期间有没有直接在这个区间每一个字符对应的相减可以得到;

存取:

for(int i=1;i<=n;i++){
        for(int j=0;j<26;j++){
            a[i][j]=a[i-1][j];
        }
        a[i][s[i]-'A']+=1;
    }

检验:

if(a[r][i]-a[l-1][i]==0){ 
                flag=false;
                break;
            }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J4LD6fTV-1626147807107)(…/…/…/pictures-md/image-20210407135612869.png)]

2.尺取法 26xn

满足条件,左边右移,不满足,右边右移增加;

模板:

for(l,r;l+25<=n&&r<=n;){
        bool flag=check();
        if(!flag){
            r++;
            a[s[r]-'A']++;
        }
        if(flag){
            re=min(re,r-l+1);
            a[s[l]-'A']--;
            l++;
        }
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5pdfDjXa-1626147807108)(…/…/…/pictures-md/image-20210407200038342.png)]

3.单调队列 nlogn

单调队列通常维护 局部 的单调性

前面伪代码!!

求最小值:

image-20210407201056415

最大:

image-20210407201113908

4.直方图求最大矩形面积

  • 单调栈 nlogx

从左到右单调栈,右边第一个比它小的image-20210408142534359

从右到左递增栈,左边第一个比他小的image-20210408142629679

初始化ll 为0;rr为n+1;

image-20210618191316546

5.差分数组+快速幂nlogn

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kMZd2UXv-1626147807114)(…/…/…/pictures-md/image-20210408142721992.png)]

  • 差分数组,快速幂nlogn
  • 质数x幂次(差分表示),最后前缀和求出每个质数的最小幂次,求快速幂;

取模运算:模板(上面快速幂模板)image-20210408144251092

image-20210618193836579

void isprime(int l,int r,int c,int b){//指数ll差分数组
    for(int i=2;i<100;i++){
        if(c==1) break;
        ll cnt=0;
        while(c%i==0){
            c/=i;
            cnt++;
        }
        cha[i][l]+=cnt*b;
        cha[i][r+1]-=cnt*b;
    }
}
ll qpow(ll a,ll b,ll m){
    if(b==0) return 1;
    ll now=qpow(a,b/2,mod);
    if(b&1) return a*now%m*now%m;
    else return now*now%m;   
}
int main(){
    int m;
    cin>>n>>m;
    int l,r,c,b;
    while (m--)
    {
        cin>>l>>r>>c>>b;
        isprime(l,r,c,b);
    }
    ll ans=1;
    for(int i=2;i<100;i++){
        ll temp=0,mi=(ll)1e14;
        for(int j=1;j<=n;j++){
            temp+=cha[i][j];
            mi=min(temp,mi);
        }
        ans=(ans*qpow(i,mi,mod))%mod;
    }
    cout<<ans;
    
}

h6图

并查集

image-20210422143713891

数组记录应该输出的位置;

image-20210422143912587

分析:并查集的个数-1;求并查集的个数;一开始有n个并查集,之后每次添加一条边,若两个点属于一个并查集,不改变并查集个数;否则,并查集的个数-1,需要修建的条数-1,合并两个并查集;并查集查找过程中修改来减少之后的查找次数;“O(4)”

  • //通过par设置根元素
    int find(int x){
        int root=par[x];//并查集代表元素
        while(root!=par[root]){//找到
            root=par[root];
        }
        while(x!=root){//将到达根的所有的par设为根
            int t=par[x];
            par[x]=root;
            x=t;
        }
        return root;
    }
    //合并
    void unite(int x,int y){
        x=find(x);//找到根之后合并
        y=find(y);
        par[x]=y;
    }
    

    image-20210422144655135

  • z最远的肯定为叶子节点;树的直径问题,从一点开始找到距离最远的点;然后从最远的点开始找到另一个最远的点;从这两个点开始遍历记录到所有点的距离,每个点上输出两次dfs的最大值;

image-20210422152319892

设立超级原点,到每个点边的权值为c,则为求最小连通子图;

最小生成树:

Kruskal 每次贪心地尝试将图中最小的非树边标记为树边,非法则跳过

image-20210422153408288

image-20210422153419586

并查集复杂度O(mlog(1+m/n**n)

image-20210422154203642

  • 画图分析,两个集合不同前进,直到一个为空;image-20210422154221298

image-20210422154256152

负权环路

  • bellmanford算法,经过m-1次松弛一定会有最短路,每次对每一条边对应的点进行松弛;最后,如果还可以进行松弛说明含有负权环路。

  • Floyd 算法解决的是多源最短路径问题,

image-20210422214412166

对于单源最短路径问题有些许小题大做

• Dijkstra 算法在图中存在负权边时不能保证结果的正确性

image-20210422214543247

image-20210422214553555

image-20210422214620161

算法模板****:**

image-20210615212703591

  • Bellman-ford 算法及其队列优化 (SPFA)可以给出源点 S 至图内其他所有点的最短路𝑂(𝑛𝑚);

    所有的点都在最短边上;

    image-20210422214826555

image-20210422214951508

image-20210422215021830

image-20210422215037947

image-20210422215049171

image-20210422215616034

image-20210422215634872

spfa就是有队列优化的bellmanford算法;求解有负数的单元最短路径问题。所有的都可以用spfa

  • 负权环路

当图中存在负环时,那么可以沿着负环不断走下去,那么最短路长
度为负无穷,没有意义。如果图中含有负权边时,则需要考虑图中最短路是否存在。也就是
我们应用 Bellman - ford 算法及其队列优化所求出的最短路是否有效。

  • 负权环路

image-20210615213649146

对于本题负权环可以对任意一点,所以从任意一点开始进行算法判断是否存在负权环路即可;image-20210422222857774

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WWw0Qu1i-1626147807139)(…/…/…/pictures-md/image-20210422222925178.png)]

  • dij算法单元最短路径求出去的最短路,然后反向建图,单元最短路求出回去的最短路,两者相加;复杂度 𝑂 ( 𝑛 + 𝑚) 𝑙𝑜𝑔𝑛 )image-20210422223416846

image-20210422223456026

二分答案,+单元最短路径dij;二分最小值最大,为最大问题,mid=(l+r+1)/2;

n+m)lognlogm

image-20210422223840447

image-20210422223910897

H8

image-20210506143528795

manacher算法,只对没有检查过的部分进行遍历,复杂度为O(n);

特殊的len数组,回文串的长度为leni-1?

len性质决定在处理过的字符串中,回文长度为2*leni-1,期中其他符号#比原本符号多一个,所以本来符号位leni-1;

int malacher(string s){
    //预处理
    str[0]='@';//防止越界首部增加一个
    str[1]='#';//#a#b#
    for(int i=0;i<n;i++){
        str[(i+1)*2]=s[i];//error str[i]
        str[(i+1)*2+1]='#';
    }
    str[2*n+2]='?';

    memset(len,0,sizeof len);

    int po=0,mx=0,ans=0;//已有的回文串最右端到达mx,对应的中心位置为po
    for(int i=1;i<=n*2+1;i++){
        if(mx>i) len[i]=min(mx-i,len[2*po-i]);//在mx左边,对称位置的长度,或者超过当前限制
        else len[i]=1;//在mx右边
        while (check(str[i-len[i]],str[i+len[i]]))//以i位置为中心左右两边的是否回文更新+1
        {
            len[i]++;
        }
        if(i+len[i]>mx){//若最右回文串位置需要更新
            mx=i+len[i];po=i;
        } 
        ans=max(ans,len[i]);//取最大回文串长度
    }
    return ans-1;//可以证明回文串长度为len数值-1;
}

image-20210506144036133

拓扑排序

;入度为0

建图,通过入度为0的队列存储排列,保证学号小的排在前面通过一个优先队列实现;

image-20210506144822906

kahn算法实现拓扑排序:

image-20210506144843874

n*nlogn;

差分问题:转化为图

image-20210506145126099

i-j<=w:j到i权威w;

i-j<w==>i-j<=w-1;

i-j>w==>j-i<-w;

i-j=w==>i-j>=w&&j-I<=w;

建立前缀和数组,后一个与前一个的图限制,必须权重为0或者1,;

从0开始到最后spfa遍历,小于等于转化为最小路问题,求最多为多少,为求图的最短路问题,使用spfa,从0求单元最短路径有负边;

差分建图:

cin >> n >> m;
    dis[0] = 0;
    memset(head, 0, sizeof(head));
    for (int i = 0; i < n; i++)
    {
        add(i, i + 1, 1);
        add(i + 1, i, 0);
    }
    int k, a, b, c;

    while (m--)
    {
        cin >> k >> a >> b >> c;
        a-=1;
        switch (k)
        {
        case 1:
            add(a, b, c);
            break;
        case 2:
            add(b, a, -c);
            break;
        case 3:
            add(a, b, c - 1);//输入error
            break;
        case 4:
            add(b, a, -c - 1);
            break;
        default:
            add(a, b, c);
            add(b, a, -c);
            break;
        }
    }

spfa:nlogn

void spfa(int x)
{
    for (int i = 0; i <= n; i++)
    {
        dis[i] = inf;
        vis[i] = 0; cnt[i]=0;
    }
    queue<int> q;
    dis[x] = 0;
    vis[x] = 1;//防止已经入队的重复入队。
    q.push(x);
    bool flag=false;
    while (!q.empty())
    {
        int now = q.front();
        q.pop();
        vis[now] = 0;
        for (int e = head[now]; e; e = nxt[e])
        {
            if (dis[v[e]] > dis[now] + w[e])
            {
                if(v[e]==n) flag=true;//n处更新过了表示有最短路,
                //否则负权环路可能没有最短路不能图不连通
                dis[v[e]] = dis[now] + w[e];
                cnt[v[e]]=cnt[now]+1;//求出最短路最多遍历n-1次,若超过说明没有最短路
                if(cnt[v[e]]>=n){
                    cout << "impossible";
                    return;
                }
                if (!vis[v[e]])//没在队列中,后面可能会有最短路,入队
                {
                    q.push(v[e]);
                    vis[v[e]] = 1;
                }
            }
        }
    }
    if (flag)
        cout << dis[n];
    else
        cout << "impossible";
}
  • 拓扑排序将入度为0的点放入,每次减去与点相连的边

image-20210615215303204

强联通分量

image-20210506151022372

强联通分量表明分量内部互相通信,进行缩点,求入度为0的点,就是要求的最少人数。

强联通分量求法kasaraju算法:

一遍dfs求出后序序列,按照逆后序序列求出标记连通分量,每个点都有连通分量序号,然后对所有点,若不属于一个连通分量,可以对出点所在的连通分量增加一个入度,最后求出根据连通分量建图中的所有入度为0的点。

image-20210506153951675

image-20210506153938982

image-20210506154005734缩点直接求新图的入度数。

h9模拟题方程式,文件系统

方程式问题:

使用split分解,使用=分解左右两边,对于左右两边分别使用+分解成每一个化学式,,对每个化学式执行读取系数,读取元素,遇到()时进入一个新的读取范围的操作,对于新构件的化学式结构体,存取元素和个数用map实现;

关键在于如何设计每个函数的返回值与参数;

#include<bits/stdc++.h>
using namespace std;
struct elementset{
    map<string,int> p;
    void operator *=(const int n){
        for(auto& x:p){
            x.second*=n;
        }
    }
    void operator +=(elementset e){
        for(auto& x:e.p){
            p[x.first]+=x.second;
        }
    }
    bool operator ==(elementset e){
        return p==e.p;
    }

};
vector<string> split(string& s,char p){
    stringstream ss(s);
    string temp;
    vector<string> res;
    while (getline(ss,temp,p))
    {
        res.push_back(temp);
    }
    return res;
}
int read_int(string& s,int& p){
    int res=0;
    while (p<s.length()&&isdigit(s[p]))
    {
        res=res*10+s[p]-'0';
        p++;
    }
    
    return res==0?1:res;
    
}
string read_element(string& s,int& p){
    string res;
    res+=s[p];
    if(islower(s[p+1])) res+=s[++p];
    return res;

}
elementset str2set(string& s,int& p){    
    int head=read_int(s,p);
    elementset res;
    while (p<s.size())
    {
        if(s[p]=='('){//有括号重新设置为新的化学式
            res+=str2set(s,++p);
        }
        else if (s[p]==')')//左括号内部的*,只会对括号里面的进行操作
        {
            res*=read_int(s,++p);
            return res;
        }
        else{
            string temp=read_element(s,p);
            int i=read_int(s,++p);
            res.p[temp]+=i;
        }
        
    }
    res*=head;
    return res;
    
}
bool solve(vector<string> v){
    elementset e[2];
    vector<string> oneside;
    for(int i=0;i<2;i++){
        oneside=split(v[i],'+');
        for(auto& x:oneside){
            int p=0;
            e[i]+=str2set(x,p);
        }
    }
    return e[0]==e[1];
}
int main(){
    freopen("1.in","r",stdin);
    int n;
    cin>>n;
    string s;
    while (n--)
    {
        cin>>s;
        vector<string> side2=split(s,'=');
        if(solve(side2)) cout<<"Y"<<"\n";
        else cout<<"N\n";

    }
    return 0;
    
}

文件系统:

创建一个文件结构体,type表示哪种文件,以及相应的各个文件属性;创建文件系统结构体,存取所有的节点并给定相应的编号,可以对文件树进行搜索查找修改等操作。

#include<bits/stdc++.h>
#define ll long long
#define N 4000003
using namespace std;
struct file{
    int type;//0 普通文件 1--目录文件
    ll size;
    ll ld,lr,sld,slr;//ld孩子大小 lr后代大小
    map<string,ll> child;
    file(int _t=0,int _s=0){
        type=_t;
        size=_s;
        ld=lr=sld=slr=0;
        child.clear();
    }
    void addsize(ll _size,bool flag=false){
        if(flag) sld+=_size;
        slr+=_size;
    }
    bool check_size(ll _size,bool flag=false){
        if(flag&&ld&&sld+_size>ld) return false;
        if(lr&&slr+_size>lr) return false;
        return true;
    }

    bool change_size(ll newld,ll newlr){
        if(newld&&sld>newld) return false;
        if(newlr&&slr>newlr) return false;
        ld=newld;
        lr=newlr;
        return true;
    }
};
struct filesystem{
    int root,cnt;//根目录 计数器
    string name;
    vector<string>path;
    file files[N];
    filesystem(){
        root=1;
        cnt=1;
        files[1]=file(1);
    }
    void get_path(string s){
        path.clear();
        name="";
        stringstream ss(s);
        string ch;
        getline(ss,ch,'/');
        while (getline(ss,ch,'/'))
        {
            path.push_back(ch);
        }
        if(path.size()) name=ch,path.pop_back();
        
    }
    int find(){
        int now=root;
        if(name=="") return root;
        for(auto& x:path){
            int t=files[now].child[x];
            if(!t) return 0;
            //是否冲突
            if(files[t].type==0) return -1;//!!目录文件与普通文件冲突
            now=t;
        }
        return files[now].child[name];
    }
    int get_type(int now){
        return files[now].type;
    }
    ll get_size(int now){
        if(files[now].type==0) return files[now].size;
        return files[now].slr;
    }
    bool create_file(ll size){
        int now=root;
        //是否可以创建文件
        for(auto& x:path){
            if(!files[now].check_size(size)) return false;
            if(files[now].child[x]==0){
                //目录缺失创建目录
                files[++cnt]=file(1);
                files[now].child[x]=cnt;
            }
            now=files[now].child[x];
        }
        if(!files[now].check_size(size,true)) return false;
        //创建普通文件
        now=root;
        for(auto& x:path){
            files[now].addsize(size);
            now=files[now].child[x];
        }
        files[now].addsize(size,true);
        files[++cnt]=file(0,size);
        files[now].child[name]=cnt;
        return true;
    }

    bool remove_file(ll size){
        int now=root;
        for(auto& x:path){
            files[now].addsize(-size);
            now=files[now].child[x];
        }
        if(files[files[now].child[name]].type==0){
            files[now].addsize(-size,true);
        } 
        else files[now].addsize(-size);
        files[now].child.erase(name);
        //return true;
    }

    bool change_size(int now,ll newld,ll newlr){
        return files[now].change_size(newld,newlr);
    }

}File;//只有在外面时才行??
int main(){
   
    int n;
    // filesystem File;
    cin>>n;
    char c;string s;
    while (n--)
    {
        cin>>c;
        cin>>s;
        File.get_path(s);
        int now=File.find();//0不存在,-1与目录文件冲突,+数存在
        if(c=='C'){
            ll size;
            cin>>size;
            if(now==-1){
                printf("N\n");
                continue;
            }
            else if(now>0){
                if(File.get_type(now)==0) 
                    File.remove_file(File.get_size(now));//普通文件存在
                else{
                    printf("N\n");
                    continue;
                }
            }
            if(File.create_file(size)) printf("Y\n");
            else{
                if(now>0) File.create_file(File.get_size(now));//恢复
                printf("N\n");//数组中删除只是在父节点里删除指向孩子,实际文件还在
            }
        }
        else if(c=='R'){
            if(now>0) File.remove_file(File.get_size(now));
     
            printf("Y\n");
        }
        else if(c=='Q'){
            ll ld,lr;
            cin>>ld>>lr;
            if(now>0&&File.get_type(now)==1){
                if(File.change_size(now,ld,lr)) printf("Y\n");
                else printf("N\n");
            }
            else{
                printf("N\n");
                continue;
            }
        }
    }
    return 0;
    
}

W7416模拟

模除问题:取模只能减去第一个值

image-20210507084122098

image-20210507085019953

每个只要和他上一次看的不一样就行,保证当天去的看的都不一样;

用一个集合存取已经放过的牌子,保证不会重复放入,对于每一天来的人数,读他们最近的一天,那么要展示的就是最近的前一天的牌子,把它放入到牌子集合中,更新每一个人所看到的最近一天的编号。

#include<bits/stdc++.h>
using namespace std;//20min做出来,考场上先看全部的题,先尽量做,有点小毛病确实看不出来
int k[1000000];//存取一个人上一次看的最近一次的牌子的显示天数
int main(){
    int N;
    scanf("%d",&N);
    for(int i=0;i<1000000;i++){
        k[i]=N+1;
    }
    int num;
    int m=N+1,x;
    set<int> s;//集合如果已经放过了不会增加,存取放过的牌子
    vector<int> temp;//存取一天来看的人的编号
    for(int j=1;j<=N;j++)
    {
        temp.clear();
        m=N+1;//几个人看的最小的牌子数
        scanf("%d",&num);//多少人
        while (num--)
        {
            scanf("%d",&x);
            temp.push_back(x);
            if(k[x]<m) m=k[x];//这几个人看的最近的天的编号
            
        }
        s.insert(m-1);//这一天应该展示的牌子为m-1,放入集合中
        for(int nm:temp){
            k[nm]=m-1;//更新这一天来的每一个人所看的最近编号
        }
        
        
    }
    printf("%d",s.size());
    

}

h10

image-20210518092327412根据k加上不同时间,注意数字匹配;image-20210518092406716

小中大:

image-20210518093838333

这里int问题深入:https://blog.csdn.net/cressball/article/details/43016307 %d是对存取的数按照整数方式读取,实际存储按照float型,读取错误,应先用int强制转化。

树状数组:

logn从0开始,数据从1开始的特殊处理。

image-20210518094132663

逆序对:

image-20210518094220434

先按照运行时间和内存时间字典序排序,按照运行时间顺序,来逐个将占用放到树状数组中,每次ask就可求出;第一个排序保障了第一层有序,第二个可以求取前缀和;

#define N (int)1e6+10
ll tr[N*2];
int n;
struct point
{
    int x;
    int y;
}p[N*2];
bool cmp(point& p1,point& p2){
    if(p1.x!=p2.x) return p1.x<p2.x;
    return p1.y<p2.y;
}
// #define lb(x) (x&(-x))
int lb(int x){
    return x&(-x);
}
void change(int x,int v){
    for(int i=x;i<=N;i+=lb(i)){//为什么是小于N????
        tr[i]+=v;
    }
}
ll ask(int x){//logn
    ll res=0;
    for(int i=x;i>=1;i-=lb(i)){
        res+=tr[i];
    }
    return res;
}
int main(){
    memset(tr,0,sizeof(tr));
    int cnt=0;
    scanf("%d",&n);
    int t1,t2;
    for(int i=0;i<n;i++)//n
    {
        scanf("%d%d",&t1,&t2);
        p[cnt].x=t1+1;//否则死循环
        p[cnt].y=t2+1;
        cnt++;
        
    }
    sort(p,p+cnt,cmp);
    ll re[n+5]{0};
    int t;
    for(int i=0;i<cnt;i++){//2nlogn
        t=p[i].y;
        re[ask(t)]++;
        change(t,1);
    }
    for(int i=0;i<n;i++){//n
        printf("%lld\n",re[i]);
    }
    
}
  • 树状数组从索引1开始,而[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5yuKIqA9-1626147807160)(…/…/…/pictures-md/image-20210518094656380.png)]处理每个都+1就行;如果有0会死循环,tle

image-20210518095053394

线段树:

image-20210518172431784

image-20210518172449469

image-20210518172510147

模板:

建树:image-20210518172555302

单点修改:

image-20210518172746601

区间查询:

image-20210518173021835

image-20210518173252222

读取单个字符。直到读取到目标:

image-20210518175041853其他读入,优先考虑cin,getline;

getline(cin,s,‘#’)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
#define M (int)1e6//范围matters
#define inf 0x7fffffff
int m,p;
ll tmp=0;
ll d[M];//若为2倍 re
char x;ll t;
ll len=0;
ll max(ll a,ll b){
    if(a>b) return a;
    return b;
}
void upd(int x,int l,int r,int loc,ll v){//x,d的索引,l-r为d表示的范围
//loc更新位置,c更新值
    if(l==r){//loc
        d[x]=v;
        return;
    }
    int mid=(l+r)/2;
    if(loc<=mid) upd(x*2,l,mid,loc,v);
    else upd(x*2+1,mid+1,r,loc,v);
    d[x]=max(d[x*2],d[x*2+1]);
    return;
}
ll ask(int x,int l,int r,int p1,int p2){//p1,p2要查询的位置
//l r所在的节点表示的区间范围
    if(l>=p1&&r<=p2) return d[x];
    int mid=(l+r)/2;
    if(p2<=mid) return ask(x*2,l,mid,p1,p2);
    else if(p1>mid) return ask(x*2+1,mid+1,r,p1,p2);
    else{
        ll lch_val=ask(x*2,l,mid,p1,p2);
        ll rch_val=ask(x*2+1,mid+1,r,p1,p2);
        return max(lch_val,rch_val);
    }
}
int main(){
    scanf("%d%d",&m,&p);
    for(int i=1;i<=m;i++) d[i]=-inf;//初始-inf很小
    for(int i=1;i<=m;i++)
    {
        // getchar();
        // scanf("%c%lld",&x,&t);
        x=getchar();//读入error
        while (x<'!')
        {
            x=getchar();
        }
        scanf("%lld",&t);
        if(x=='A'){
            t=(t+tmp)%p;
            upd(1,1,m,len+1,t);//更新线段树范围始终1-m
            len++;//len为位置

        }
        else{
            tmp=ask(1,1,m,len-t+1,len);//最后t个的最值
            printf("%lld\n",tmp);//lld输出ll
        }
    }
    return 0;
    

}

hw11 动态规划

爬台阶

image-20210610163907690

image-20210610164626218

image-20210610164636029

选数不同个数不相邻限制的dp

前缀和数组,方便计算前k个的和。

image-20210610165029429数的数量不同;

image-20210610165133667

当前选中,前面一个不能选;状态全部选择;

i表示数;cnt记录每个数的数量;

矩阵选数

image-20210610165504956给3行,罗列状态转移方程;

当前为0,前一个为012罗列,二维;

image-20210610165724077

image-20210610165748539

最长上升子序列

image-20210610165946792

image-20210610170032720

更新的前面的值越小越好,所有找到大于这个数的第一个然后更新;lower_bound二分查找。

image-20210611061953371

image-20210611062820494

最长公共子序列image-20210611062837922

image-20210611062936106

image-20210611062957424

背包问题

0-1背包:

image-20210611063051286

image-20210611063318108

image-20210611063339419

image-20210611063725037

image-20210611063559568

image-20210611063704919

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7MNVffLZ-1626147807188)(…/…/…/pictures-md/image-20210611064259535.png)]

完全背包:

image-20210611064416259

image-20210611064824739滚动数组优化:

image-20210611064859800

//0-1背包 重量数据大nw过大  
void solve(){
    for(int i=0;i<n;i++){
        for(int j=V;j>=v[i];j--){
            dp[j]=min(dp[j],dp[j-v[i]]+w[i]);
        }
    }
}
//完全背包
void solve(){
    for(int i=0;i<n;i++){
        for(int j=w[i];j<=W;j++){
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
        }
    }
}

多重背包:

image-20210611064957577

image-20210611065204721

image-20210611065802402

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jq4v5nAx-1626147807195)(…/…/…/pictures-md/image-20210611065829969.png)]

image-20210611065855996

分组背包:

image-20210611070019907

image-20210611070053215

image-20210611070133195

image-20210616205353317

超大背包:

image-20210611070246020

image-20210611070305460

image-20210611071530017

image-20210611071559521

lower找到容量小于v-sw的最大价值,对应拿当前组合物品;

hw13区间DP状压DP

石子合并区间dp

image-20210611071819827

image-20210611071905288

image-20210611071929281

环状拆环为连:image-20210611072002564

image-20210611072206386

括号列匹配:

枚举区间长度,确定左边界,右边界自然确定

image-20210611072320328

image-20210611072510807

image-20210611072548046

image-20210611072613129

image-20210611072708992

image-20210611072757832

image-20210611072816569

方格填充:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D6c2Ssji-1626147807213)(…/…/…/pictures-md/image-20210611072835389.png)]

image-20210611072935260

image-20210611073238860

判断是否连续偶数个0:

image-20210611073256708

hw14 树状dp

最长直径

image-20210611073423096最大直径问题:

image-20210611073517001

image-20210611073639245

image-20210611073700747

t5城市规划

image-20210611073752527

重要的节点中间选取:

image-20210611073932915

求每一条边的代价;

image-20210611074325597

image-20210611074429653

image-20210611074444538

image-20210611074507217

最大区间和:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d6HvaPC6-1626147807223)(…/…/…/pictures-md/image-20210611081137687.png)]

image-20210611081340224

image-20210611081410788

image-20210611081638181

维持一个单调队列保证后来的更小。

image-20210611081821371

f[i][j]表示第i次停在j的方案数;倒推 边界为f[k+1][j]=1(j!=b)

hw15矩阵快速幂

image-20210617221040711基本结构体;

快速幂:

image-20210617221119051

矩阵快速幂:

image-20210617221138233

线性递推式转化成转移矩阵;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VN1Mz8HH-1626147807230)(…/…/…/pictures-md/image-20210617221635652.png)]

image-20210617221223646

image-20210617221855787

  • image-20210617221930318

推导矩阵表达,最后根据终止的条件时候初始值给出结果。矩阵过剩没有问题。

image-20210617221945886

image-20210617224947975image-20210617224956754

矩阵快速幂转化方法:dp转移复杂度过高时使用;

转化成矩阵形式套用模板。

只需要将*运算做修改。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值