• NOIp 图论基础


    从最害怕的专题到最喜欢的专题……嗯果然多刷题是最有用的w

    目录

    • 存图
      • 邻接矩阵
      • 链式前向星
      • vector
    • 最短路
      • SPFA(死了)
      • Floyed
      • 堆优化Dijkstra
      • 最短路计数
      • 严格次短路
      • 分层图最短路
    • 最小生成树
      • Kruskal
      • Prim
    • Tarjan
      • 强连通分量
      • 缩点
      • 割点
      • 离线LCA
    • 倍增LCA
    • 拓扑排序
    • 树的直径
      • dfs法
      • dp法
    • 差分约束
    • 网络流
      • 二分图匹配
      • 最大流
      • 费用流
    • 负环
    • 最小环
    • 欧拉回路

    一、存图

    1、邻接矩阵存图

    memset(mapp,128/3,sizeof mapp);
    	for(int i=1;i<=n;i++){
    		int u,v,w;
    		scanf("%d%d%d",&u,&v,&w);
    		mapp[u][v]=mapp[v][u]=min(mapp[u][v],w);
    	}
    

    2、链式前向星

    struct sth{
    	int to,nxt,dis;
    };
    int tot,head[MAXN];
    void add(int u,int v,int w){
    	a[++tot].to=v;a[tot].nxt=head[u];
    	a[tot].dis=w;head[u]=tot;
    }
    

    3、vector

    vector<int>P[MAXN];
    for(int i=1;i<=n;i++){
    	int u,v;
    	P[u].push_back(v);
    	P[v]=push_back(u);
    }
    

    以下内容均省去建图过程,一般是前向星存的。

    二、最短路

    1、SPFA (它曾活过啊)

    void SPFA(){
    	queue<int>q;	
    	memset(d,0x7f7f7f,sizeof d);
    	q.push(s);d[s]=0;vis[s]=1;
    	while(!q.empty()){
    		int noww=q.front();q.pop();
    		for(int j=head[noww];j;j=a[j].nxt){
    			int vv=a[j].to;
    			if(d[vv]>d[noww]+a[j].dis){
    				d[vv]=d[noww]+a[j].dis;
    				if(!vis[vv])q.push(vv),vis[vv]=1;
    			}
    		}vis[noww]=0;
    	}
    }
    

    2、Floyed

    void Floyed(){
    	for(int k=1;k<=n;k++)
    	  for(int i=1;i<=n;i++)
    	    for(int j=1;j<=n;j++){
    	      if(mapp[i][j]>mapp[i][k]+mapp[k][j])
    	        mapp[i][j]=mapp[i][k]+mapp[k][j];
    		}
    }
    

    3、堆优化Dijkstra

    struct node{
        int to,dis;
        bool operator <(const node &t) const{
            return dis>t.dis;
        }
    }a[MAXM<<1];
    priority_queue<node>q;
    void dij(){
    	for(int i=1;i<=m;i++)d[i]=2147483647;
    	d[s]=0;node qwq;qwq.to=s;qwq.dis=0;
        q.push(qwq);
        while(!q.empty()){
            node P=q.top();q.pop();
            int noww=P.to;
            if(visit[noww])continue;
            visit[noww]=1;
            for(int j=head[noww];j;j=nxt[j]){
                int vv=a[j].to;
                if(d[vv]>d[noww]+a[j].dis){
    			    d[vv]=d[noww]+a[j].dis;
                    node qwq;qwq.to=vv;qwq.dis=d[vv];
                    q.push(qwq);
                }
            }
        }
    }
    

    四、最短路计数

    void SPFA(){
        q.push(1);dis[1]=0;
    	t[1]=1;vis[1]=1;
        while(!q.empty()){
            int noww=q.front();q.pop();
            if(noww==n)continue;//重要 
            for(int i=1;i<=n;i++){
                if(dis[noww]+a[noww][i]==dis[i])
                t[i]+=t[noww];
                if(dis[noww]+a[noww][i]<dis[i]){
                    dis[i]=dis[noww]+a[noww][i];
                    t[i]=t[noww];
                }
    		    if(t[i]&&!vis[i])vis[i]=1,q.push(i);
            }
    		t[noww]=0,vis[noww]=0;//重要 
        }
    }
    

    五、严格次短路

    priority_queue<P,vector<P>,greater<P> >q;
    void dij(){
        for(int i=1;i<=n;i++)d1[i]=d2[i]=inf;
        d1[1]=0;
        q.push(P(1,0));
        while(!q.empty()){
            int noww=q.top().first;
            int d=q.top().second;q.pop();
            for(int j=head[noww];j;j=a[j].nxt){
                int vv=a[j].to;int disv=a[j].dis+d;
                if(disv<d1[vv]){
                    swap(d1[vv],disv);//重要 
                    q.push(P(vv,d1[vv]));
                }
                if(disv>d1[vv]&&disv<d2[vv]){
                    d2[vv]=disv;
                    q.push(P(vv,d2[vv]));
                }
            }
        }
    }
    

    六、分层图最短路

    了解了算法就一眼能瞪出正解的题型...

    例题

    建一个k+1层的图,层内连边,层之间连0边即可。(其实是dp思想)

    #include<bits/stdc++.h>
    using namespace std;
    const int MAXN=200005;
    const int MAXM=6000005;
    int n,m,k,tot,head[MAXN],s,t;
    int d[MAXN];
    struct sth{
    	int to,nxt,dis;
    }a[MAXM];
    struct node{
    	int qwq,diss;
    	bool operator <(const node& t)const{
    	    return diss>t.diss;
    	}
    }; 
    priority_queue<node>q;
    void add(int u,int v,int w){
    	a[++tot].dis=w;a[tot].to=v;
    	a[tot].nxt=head[u];head[u]=tot;
    }
    void dij(){
    	memset(d,128/3,sizeof d);d[s]=0;
    	node ss;ss.diss=0;ss.qwq=s;q.push(ss);
    	while(!q.empty()){
    		node noww=q.top();q.pop();
    		int nowq=noww.qwq;
    		for(int j=head[nowq];j;j=a[j].nxt){
    			int vv=a[j].to;
    			if(d[vv]>d[nowq]+a[j].dis){
    				d[vv]=d[nowq]+a[j].dis;
    				node orz;orz.qwq=vv;orz.diss=d[vv];
    				q.push(orz);
    			}
    		}
    	}
    	
    }
    int main(){
    	scanf("%d%d%d",&n,&m,&k);
    	scanf("%d%d",&s,&t);s++;t++;
    	for(int i=1;i<=m;i++){
    		int u,v,w;scanf("%d%d%d",&u,&v,&w);
    		u++;v++;
    		for(int j=0;j<=k;j++){
    			add(u+j*n,v+j*n,w);add(v+j*n,u+j*n,w);
    			if(j!=k){
    				add(u+j*n,v+(j+1)*n,0);
    				add(v+j*n,u+(j+1)*n,0);
    			}
    		}
    	}
    	for(int j=0;j<k;j++){
    		add(t+n*j,t+n*(j+1),0);
    		add(t+n*(j+1),t+n*j,0);
    	}
    	dij();
    	printf("%d
    ",d[t]);
    	return 0;
    }
    

    七、最小生成树

    1、适合稀疏图的Kruskal

    void qsort(int l,int r){
        int i=l,j=r;node mid=a[(i+j)/2];
        while(i<=j){
            while(cmp(a[i],mid))i++;
            while(cmp(mid,a[j]))j--;
            if(i<=j){
                t=a[i];a[i]=a[j];a[j]=t;
                i++;j--;
            }
        }
        if(i<r)qsort(i,r);
        if(l<j)qsort(l,j);
    }
    int findx(int x){return (x==fa[x])?fa[x]:fa[x]=findx(fa[x]);}
    void solve(){
        qsort(1,m);
        for(int i=1;i<=n;i++)fa[i]=i;
        for(int i=1;i<=m;i++){
            if(findx(a[i].x)!=findx(a[i].y)){
                ans+=a[i].w;
                fa[findx(a[i].x)]=a[i].y;
                p++;
                if(p==n)return ;
            }
        }
    }
    

    2、适合稠密图的Prim

    (都8012年了我才会Prim)

    void Prim(){
    	for(int i=1;i<=n;i++){
    		mincost[i]=mapp[1][i];
    	}
    	use[1]=1;//重要 
    	for(int i=1;i<n;i++){
    		int minn=99999999;
    		int j;
    		for(int k=1;k<=n;k++){
    			if(!use[k]&&mincost[k]<minn){
    				j=k;
    				minn=mincost[k];
    			}
    		}
    		use[j]=1;
    		ans+=minn;
    		for(int k=2;k<=n;k++){
    			if(!use[k]&&mincost[k]>mapp[j][k]){
    				mincost[k]=mapp[j][k];
    			}
    		}
    	}
    }
    
    

    八、Tarjan

    1、强联通分量

    int dfs(int x){
    	dfn[x]=low[x]=++cntt;
    	vis[x]=1;s.push(x);
    	for(int j=head[x];j;j=a[j].nxt){
    		int vv=a[j].to;
    		if(!dfn[vv]){
    			int lowv=dfs(vv);
    			low[x]=min(low[x],lowv);
    		}
    		else{
    			if(vis[vv]){
    				low[x]=min(low[x],low[vv]); 
    			}
    		}
    	}
    	if(dfn[x]==low[x]){
    		cntcol++;colour[x]=cntcol;cnt[cntcol]++;
    		vis[x]=0;
    		sta[cntcol][1]=x;
    		while(s.top()!=x){
    			colour[s.top()]=cntcol;cnt[cntcol]++;
    			vis[s.top()]=0;sta[cntcol][cnt[cntcol]]=s.top();
    			s.pop();
    		}s.pop();
    		maxcnt=max(maxcnt,cnt[cntcol]);
    	}
    	return low[x];
    }
    void tarjan(){
    	for(int i=1;i<=n;i++){
    		if(!dfn[i])dfs(i);
    	}
    }
    

    2、缩点

    int tarjan(int u){
        dep++;vis[u]=1;
        s.push(u);
        int lowu=dep;dfn[u]=low[u]=dep;
        for(int j=head[u];j;j=a[j].nxt){
            int vv=a[j].to;
            if(!dfn[vv]){
                int lowv=tarjan(vv);
                lowu=min(lowu,lowv);
            }else{
                if(vis[vv])
                lowu=min(lowu,low[vv]);
            }
        }
        low[u]=lowu;
        if(low[u]==dfn[u]){
            cnt++;
            colour[u]=cnt;vis[u]=0;
            newq[cnt]+=q[u];
            while(s.top()!=u){
                colour[s.top()]=cnt;
                newq[cnt]+=q[s.top()];
                vis[s.top()]=0;
                s.pop();
            }s.pop();
        }
        return lowu;
    }
    void work(){
        for(int i=1;i<=n;i++){
            if(!dfn[i])tarjan(i);
        }
        for(int i=1;i<=m;i++)
            if(colour[x[i]]!=colour[y[i]])add(colour[x[i]],colour[y[i]]);
    }
    

    3、割点

    void tarjan(int u,int fa){
    	int ch=0;dep++;
    	dfn[u]=low[u]=dep;
    	for(int j=head[u];j;j=a[j].nxt){
    		int vv=a[j].to;
    		if(!dfn[vv]){
    			ch++;
    			tarjan(vv,u);
    			low[u]=min(low[u],low[vv]);
    			if(low[vv]>=dfn[u])iscut[u]=1;
    		}else{
    			if(vv!=fa&&dfn[vv]<dfn[u])
    			low[u]=min(low[u],low[vv]);
    		}
    	}
    	if(fa==-1&&ch==1)iscut[u]=0;
    	if(fa==-1&&ch>=2)iscut[u]=1;
    }
    void work(){
    	for(int i=1;i<=n;i++){
    		if(!dfn[i])tarjan(i,-1);
    	}
    }
    

    4、桥

    void tarjan(int u,int fa){
    	int ch=0;dep++;
    	dfn[u]=low[u]=dep;
    	for(int j=head[u];j;j=a[j].nxt){
    		int vv=a[j].to;
    		if(!dfn[vv]){
    			ch++;
    			tarjan(vv,u);
    			low[u]=min(low[u],low[vv]);
    			if(dfn[u]<low[vv])printf("%d %d
    ",u,vv); 
    		}else{
    			if(vv!=fa&&dfn[vv]<dfn[u])
    			low[u]=min(low[u],low[vv]);
    		}
    	}
    }
    void work(){
    	for(int i=1;i<=n;i++){
    		if(!dfn[i])tarjan(i,-1);
    	}
    }
    

    5、离线求LCA

    #include<bits/stdc++.h>
    using namespace std;
    const int MAXN=505;
    int n,m,root;
    struct sth{
    	int nxt,to,lca;
    }a[MAXN],b[MAXN];
    int tot,toot,head[MAXN],head2[MAXN];
    void add(int u,int v){
    	a[++tot].to=v;a[tot].nxt=head[u];
    	head[u]=tot;
    }
    void add2(int u,int v){
    	b[++toot].to=v;b[toot].nxt=head2[u];
    	head2[u]=toot;
    }
    bool vis[MAXN];
    int fa[MAXN];
    int find(int x){return x==fa[x]?fa[x]:fa[x]=find(fa[x]);}
    void dfs(int x){
    	fa[x]=x;
    	vis[x]=1;
    	for(int j=head[x];j;j=a[j].nxt){
    		int vv=a[j].to;
    		if(!vis[vv]){
    			dfs(vv);
    			fa[vv]=x;
    		}
    	}
    	for(int j=head2[x];j;j=b[j].nxt){
    		int vv=b[j].to;
    		if(vis[vv]){
    			b[j].lca=find(vv);
    			if(j&1)
    			   b[j+1].lca=b[j].lca;
    			else b[j-1].lca=b[j].lca;
    		}
    	}
    }
    int main(){
    	scanf("%d%d%d",&n,&m,&root);
    	for(int i=1;i<n;i++){
    		int u,v;
    		scanf("%d%d",&u,&v);
    		add(u,v);add(v,u);
    	}
    	for(int i=1;i<=m;i++){
    		int u,v;
    		scanf("%d%d",&u,&v);
    		add2(u,v);add2(v,u);
    	}
    	dfs(root);
    	for(int i=1;i<=toot;i+=2)printf("%d
    ",b[i].lca);
    	return 0;
    }
    

    九、倍增求LCA

    void dfs(int u,int f,int d){
    	fa[u][0]=f;deep[u]=d;
    	for(int i=head[u];i;i=a[i].nxt){
    		int vv=a[i].to;
    		if(vv!=f)dfs(vv,u,d+1);
    	}
    }
    int lca(int u,int v){
    	if(deep[u]>deep[v])swap(u,v);
    	for(int k=0;k<19;k++){
    		if(((deep[v]-deep[u])>>k)&1)v=fa[v][k];
    	}if(u==v)return u;
    	for(int k=18;k>=0;k--){
    		if(fa[v][k]!=fa[u][k]){
    			u=fa[u][k];v=fa[v][k];
    		}
    	}return fa[u][0];
    }
    void work(){
    	dfs(root,-1,0);
    	for(int k=0;k+1<19;k++){
    		for(int j=1;j<=n;j++)
    		if(fa[j][k]<0)fa[j][k+1]=-1;
    		else fa[j][k+1]=fa[fa[j][k]][k];	
    	}
    } 
    

    十、拓扑排序

    void tuopu(){//不断找入度为0的点
    	for(int i=1;i<=n;i++)if(!rudu[i])q.push(i);
    	while(!q.empty()){
    		int noww=q.front();q.pop();
    		tp[++cnt]=noww;
    		for(int j=head[noww];j;j=a[j].nxt){
    			int vv=a[j].to;rudu[vv]--;
    			if(!rudu[vv])q.push(vv);
    		}
    	}if(cnt!=n){
    		printf("No Solution!
    ");return;
    	}
    	for(int i=1;i<=cnt;i++)printf("%d ",tp[i]);
    	printf("
    ");
    }
    

    十一、树的直径

    “你知道吗,树的直径有两种求法。”
    (好像还可以bfs求,然而我没写过)

    1、两遍dfs求法

    void dfs(int noww){
    	if(dis[noww]>maxn){
    		maxn=dis[noww];
    		t=noww;
    	}
        vis[noww]=1;
        for(int j=head[noww];j;j=a[j].nxt){
            int vv=a[j].to;
            if(!vis[vv]){
                dis[vv]=dis[noww]+a[j].to;
                dfs(vv);
            }
        }
    }
    void work(){
    	maxn=-99999999;
    	memset(vis,0,sizeof vis);
    	memset(dis,128/3,sizeof dis);
    	dis[1]=0;
    	dfs(1);
    	s=t;
    	maxn=-99999999;
    	memset(vis,0,sizeof vis);
    	memset(dis,128/3,sizeof dis);
    	dis[s]=0;
    	dfs(s);
    }
    
    

    2、dp求法

    void dfs(int noww,int from) {
        dp[noww][0]=0;dp[noww][1]=0;
        for (int j=head[noww];j;j=a[j].nxt){
            int vv=a[j].to;
            if(vv!=from){
                dfs(vv,i);
                if (dp[vv][0]+a[j].dis>dp[i][0]){
                    dp[i][1]=dp[i][0];
                    dp[i][0]=dp[vv][0]+a[j].dis;
                }
                else if(dp[to[j]][0]+a[j].dis>dp[i][1])
                        dp[i][1]=dp[vv][0]+a[j].dis;
            }
        }
        ans=max(ans,dp[i][0]+dp[i][1]);
    }//dfs(1,-1);
    

    十二、差分约束

    差分约束并没有固定的模板,但有固定的套路——

    将条件中的差值不等式转换成最短路松弛时的不等式即可。

    以洛谷P3275糖果一题为例,将五种要求转化成连边,跑SPFA。

    不能成立的条件是出现环。

    这里给出建图和最短路部分

    bool SPFA(){
        vis[0]=1,q.push(0);
        while(!q.empty()){
            int u=q.front();q.pop();vis[u]=0;
            if(tot[u]==n-1){cout<<-1;return 0;}
            tot[u]++;
            for(int i=head[u];i;i=a[i].nxt)
                if(d[a[i].to]<d[u]+a[i].dis){
                    d[a[i].to]=d[u]+a[i].dis;
                    if(!vis[a[i].to])vis[a[i].to]=1,q.push(a[i].to);
                }
        }return 1;
    }
    void init(){
    	for(ll i=1;i<=k;i++){
            ll opt,u,v;
            cin>>opt>>u>>v;
            if(opt==1){
                add(u,v,0);add(v,u,0);
            }
            if(opt==2){
                add(u,v,1);
                if(u==v){
                    cout<<"-1";return 0;
                }
            }
            if(opt==3){
                add(v,u,0);
            }
            if(opt==4){
                add(v,u,1);if(u==v){
                    cout<<"-1";return 0;
                }
            }
            if(opt==5){
                add(u,v,0);
            }
        }
        for(ll i=n;i>=1;i--)add(0,i,1);
    }
    

    十三、网络流

    1、二分图匹配

    当然可以跑网络流,然而二分图匹配更好写。(算法名字我分不清....)

    bool dfs(int x){
    	for(int j=1;j<=m;j++){
    		if(!mapp[x][j])continue;
    		if(use[j])continue;
    		use[j]=1;
    		if(!match[j]||dfs(match[j])){
    			match[j]=x;
    			return 1;
    		}
    	}
    	return 0;
    }
    void work(){
    	for(int i=1;i<=n;i++){
    	    memset(use,0,sizeof use);
    		if(dfs(i))ans++;
    	}
    }
    

    2、最大流

    int bfs(){
        memset(deep,0,sizeof(deep));
        queue<int>q;
        deep[s]=1;
        q.push(s);
        while(!q.empty()){
            int u=q.front();q.pop();
            for(int i=head[u];i!=-1;i=e[i].nxt){
                if(e[i].dis&&!deep[e[i].to]){
                    deep[e[i].to]=deep[u]+1;
                    q.push(e[i].to);
                }
            }
        }
        if(deep[t])return 1;
        return 0;
    }
    int dfs(int u,int d) {
        if(u==t)return d;
        for(int i=head[u];i!=-1;i=e[i].nxt){
            if((deep[e[i].to]==deep[u]+1)&&(e[i].dis)){
                int di=dfs(e[i].to,min(d,e[i].dis)); 
                if(di){
                    e[i].dis-=di;
                    e[i^1].dis+=di;
                    return di;
                }
            }
        }
        return 0;
    }
    int dinic(){
        int ret=0;
        while(bfs())
            while(int di=dfs(s,INF)) 
                ret+=di;
        return ret;
    }
    
    

    还有最大流最小割定理。我不会证也懒得copy了...

    3、费用流

    把最大流的bfs改成SPFA就好啦

    int spfa(){
        memset(d,0x7f,sizeof(d));
        d[s]=0;vis[s]=1;q.push(s);
        while(!q.empty()){
        	int u=q.front();q.pop();vis[u]=0;
            for(int i=head[u];i;i=a[i].nxt){
                int vv=a[i].to;
                if(a[i].dis&&d[u]+a[i].cost<d[vv]){
                    d[vv]=d[u]+a[i].cost;
                    if(!vis[vv])vis[vv]=1,q.push(vv);
                }
            }
        }
        return d[t];
    }
    int dfs(const int u,const int f){
        if(f==0||u==t)return f;
        int ret=0;vis[u]=1;
        for(int i=cur[u];i;i=a[i].nxt){
            cur[u]=i;
            int v=a[i].to;
            if(d[v]!=d[u]+a[i].cost||!a[i].dis)continue;
            if(!vis[v]){
                int ans=dfs(v,min(f-ret,a[i].dis));
                ret+=ans;
    			a[i].dis-=ans,a[i^1].dis+=ans;
                mincost+=a[i].cost*ans;
            }
        }
        vis[u]=0;
        return ret;
    }
    void dinic(){
        while(spfa()!=0x7f7f7f7f){
            for(int i=1;i<=n;i++)cur[i]=head[i];
            while(int x=dfs(s,0x7f7f7f7f)){
                maxflow+=x;
                if(mincost>=0x7f7f7f2b)return;
            }
        }
    }
    

    十四、负环

    如果有一个点入队次数大于(n),那么一定有负环

    bool SPFA(int s){
    	queue<int>q;
    	memset(vis,0,sizeof vis);
    	memset(d,128/3,sizeof d);
    	d[s]=0;vis[s]=1;cnt[s]=1;q.push(s);
    	while(!q.empty()){
    		int noww=q.front();q.pop();vis[noww]=0;
    		for(int j=head[noww];j;j=a[j].nxt){
    		int vv=a[j].to;
    		if(d[vv]>d[noww]+a[j].dis){
    			d[vv]=d[noww]+a[j].dis;
    			cnt[vv]=cnt[noww]+1;
    			if(cnt[vv]>n)return 0;
    			if(!vis[vv])vis[vv]=1,q.push(vv);	
    		}
    		}
    	}return 1;
    }
    
    

    十五、最小环

    只见过一道求最小环的题,感觉很有意思,值得一做。(虽然这道题的精髓在于建图)

    for(int k=1;k<=a;k++){
    	for(int i=1;i<=k-1;i++)
    		for(int j=i+1;j<=k-1;j++){
    		  	int tmp=qwq[i][j]+dis[i][k]+dis[k][j];
    		  	if(tmp<ans)ans=tmp;	
    		    }		  
    	for(int i=1;i<=a;i++)
    		for(int j=1;j<=a;j++){
    		    if(qwq[i][j]>qwq[i][k]+qwq[k][j])
    		    qwq[i][j]=qwq[i][k]+qwq[k][j];
    	    }
    }
    

    正确性证明:在枚举环上的(ij)时,因为这两个点没有被(k)松弛过,所以必定不会经过(k)

    十六、欧拉回路

    void dfs(int x){
        for (int i=1;i<=n;i++)
        if (mapp[x][i]){
            mapp[x][i]--;
    		mapp[i][x]--;
            dfs(i);
        }
    	S.push(x);
    }
    void work(){
    	for(int i=1;i<=m;i++){
            int u,v;scanf("%d%d",&u,&v);
            mapp[u][v]++;
    		mapp[v][u]++;
            b[u]++;b[v]++;
            n=max(n,max(u,v));
        }
        s=1;int cnt=0;
        for(int i=n;i>=1;i--)
    	if(b[i]&1)s=i,cnt++;
        if(cnt&&cnt!=2){
            printf("No Solution!
    ");
            exit(0);
    	}
    	dfs(s);
    } 
    
    
    
  • 相关阅读:
    从针对接口编程到依赖注入
    DataRow 数组转化成DataTable
    Math 类的方法概要
    .net控件
    字符串反转
    DataTable
    Enabled设置为False时,前景色和背景色也不改变的TextBox 并居中
    C# 四舍五入 (解决四舍六入五成双的问题)
    查询最后一条数据
    C# toString()
  • 原文地址:https://www.cnblogs.com/erutsiom/p/9923640.html
Copyright © 2020-2023  润新知