树形DP学习笔记

ps: 本文内容与蓝书一致

树的重心

  • 概念: 一颗树中的一个节点其最大子树的节点树最小
  • 解法:对与每个节点求他儿子的\(size\) ,上方子树的节点个数为\(n-size_u\) ,求对于每个节点子树的最大值,找出最小的那个就好了;

(我觉得就不需要code了)


树的直径

  • 概念:一颗带权树的最长路径
  • 解法:维护一个节点到叶子节点的最大距离\(d1[i]\)和次大距离\(d2[i]\) ,最大距离就是$max {d1[i]+d2[i] } $

code

#include<iostream>
#include<cstdio>
using namespace std;
const int N=1e4+5;
int n;
struct pp
{
int to,next;
}w[2*N];
int head[N],cnt;
int d1[N],d2[N];
int ans;
void add(int x,int y)
{
cnt++;
w[cnt].next=head[x];
w[cnt].to=y;
head[x]=cnt;
}
void dfs(int x,int fa)
{
for(int i=head[x];i;i=w[i].next)
{
int t=w[i].to;
if(t!=fa)
{
dfs(t,x);
if(d1[t]+1>d1[x])
{
d2[x]=d1[x];
d1[x]=d1[t]+1;
}
else if(d1[t]+1>d2[x]) d2[x]=d1[t]+1;
}
}
return ;
}
void find_ans(int x,int fa)
{
ans=max(ans,d1[x]+d2[x]);
for(int i=head[x];i;i=w[i].next)
{
int t=w[i].to;
if(t!=fa) find_ans(t,x);
}
return;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("diam.in","r",stdin);
freopen("diam.out","w",stdout);
#endif
scanf("%d",&n);
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
dfs(1,0);
find_ans(1,0);
printf("%d",ans);
return 0;
}

例题

P4480 逃学的小孩

  • 大概思路:求出树的直径以及其左右端点,再设\(d[i]\)为树上节点\(i\)到左右端点距离更小的那个,然后求出\(max \{d[i]\}\),然后以这个值加上直径就是\(ans\) ;

code

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int N=2e5+5;
struct pp
{
int next,to;
ll qu;
}w[N*2];
int head[N],cnt;
int n,m;
bool v[N];
ll d1[N],d2[N],dl[N],dr[N];
int f1[N],f2[N];
int r,l;
ll ans,mans;
void add(int x,int y,int z)
{
w[++cnt].next=head[x];
w[cnt].qu=z;
w[cnt].to=y;
head[x]=cnt;
}
int read()
{
int f=1;
char ch;
while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
int res=ch-'0';
while((ch=getchar())>='0'&&ch<='9') res=res*10+ch-'0';
return res*f;
}
void dfs1(int x)
{
if(v[x]) return ;
v[x]=1;
for(int i=head[x];i;i=w[i].next)
{
int t=w[i].to;
if(!v[t])
{
dfs1(t);
if(d1[t]+w[i].qu>d1[x])
{
f2[x]=f1[x];
f1[x]=f1[t];
d2[x]=d1[x];
d1[x]=d1[t]+w[i].qu;
}
else if(d1[t]+w[i].qu>d2[x]) d2[x]=d1[t]+w[i].qu,f2[x]=f1[t];
} }
return;
}
void find_ans(int x)
{
if(v[x]) return;
v[x]=1;
if(ans<d1[x]+d2[x])
{
ans=d1[x]+d2[x];
l=f1[x];
r=f2[x];
}
for(int i=head[x];i;i=w[i].next) find_ans(w[i].to);
}
void dfs2(int x)
{
if(v[x]) return;
v[x]=1;
for(int i=head[x];i;i=w[i].next)
{
int t=w[i].to;
if(!v[t])
{
dl[t]=dl[x]+w[i].qu;
dfs2(t);
}
}
return;
}
void dfs3(int x)
{
if(v[x])return;
v[x]=1; for(int i=head[x];i;i=w[i].next)
{
int t=w[i].to;
if(!v[t])
{
dr[t]=dr[x]+w[i].qu;
dfs3(t);
}
}
return;
}
void dfs_ans(int x)
{
if(v[x]) return;
v[x]=1;
mans=max(mans,min(dl[x],dr[x]));
for(int i=head[x];i;i=w[i].next) dfs_ans(w[i].to);
return;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("Chris.in","r",stdin);
freopen("Chris.out","w",stdout);
#endif
n=read();
m=read();
for(int i=1;i<=m;i++)
{
int x,y,z;
x=read(),y=read(),z=read();
add(x,y,z);
add(y,x,z);
}
for(int i=1;i<=n;i++) f1[i]=i;
dfs1(1);
memset(v,0,sizeof(v));
find_ans(1);
memset(v,0,sizeof(v));
dfs2(l);
memset(v,0,sizeof(v));
dfs3(r);
memset(v,0,sizeof(v));
dfs_ans(1);
printf("%lld",ans+mans);
return 0;
}

树的中心

  • 概念:给出一颗带权树,求一个节点,使得此节点到树中其他节点的最远距离最小;

  • 解法:如果是一颗没有负边权的树,那直接找到直径的中点就好;

    但是这里我们考虑有负边权的情况:

    有两种情况:

    1. 从\(u\)点向上的最长路径,设为\(up[u]\);
    2. 从\(u\)点向下,即\(u\)到叶节点的最远距离,设为\(d1[u]\)(次远设为\(d2[u]\));

    \(d1[u]\)和\(d2[u]\)都会求,问题是\(up[u]\)该怎么求?

    还是分类讨论,设\(u\)的父亲为\(x\),\(d1[x]\)来自于子节点\(v\);那对于\(u\):

    1. 如果\(u!=v\),那么\(up[u]=max\{d1[x],up[x]\}+dis[x][t]\);
    2. 如果\(u==v\),那么\(up[u]=max\{d2[x],up[x]\}+dis[x][t]\),这也是为什么要维护\(d2[x]\)的原因;

code

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1e5+5;
struct pp
{
int next,to;
}w[2*N];
int n,k;
int head[N],cnt;
int d1[N],d2[N],pre[N],u[N];
int root,far;
int read()
{
int f=1;
char ch;
while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
int res=ch-'0';
while((ch=getchar())>='0'&&ch<='9') res=res*10+ch-'0';
return res*f;
}
void add(int x,int y)
{
cnt++;
w[cnt].next=head[x];
w[cnt].to=y;
head[x]=cnt;
}
bool cmp(int x,int y) {return x>y;}
void dfs1(int x,int fa)
{
for(int i=head[x];i;i=w[i].next)
{
int t=w[i].to;
if(t!=fa)
{
dfs1(t,x);
if(d1[t]+1>d1[x])
{
pre[x]=t;
d2[x]=d1[x];
d1[x]=d1[t]+1;
}
else if(d1[t]+1>d2[x]) d2[x]=d1[t]+1;
}
}
return;
}
void dfs2(int x,int fa)
{
int minx=min(u[x],d1[x]);
if(far<minx)
{
root=x;
far=minx;
}
for(int i=head[x];i;i=w[i].next)
{
int t=w[i].to;
if (t!=fa)
{
if(pre[x]!=t) u[t]=max(d1[x],u[x])+1;
else u[t]=max(d2[x],u[x])+1;
dfs2(t,x);
}
}
return ;
}
int main()
{
n=read(),k=read();
for(int i=1;i<n;i++)
{
int x,y;
x=read(),y=read();
add(x,y);
add(y,x);
}
dfs1(1,0);
dfs2(1,0);
printf("%d",root);
return 0;
}

例题

P5536核心城市

  • 思路:显然其中一定会有一个城市为这颗树的中心;那找出这个中心,把这颗无根树变为以它为根的有根树;再求出除根节点以外的每个节点所能到达的最大深度\(deepfar[i]\),这就是这个节点最远所能到达的距离;然后\(sort\)一下\(deepfar[]\),答案就是\(deepfar[k+1]+1\);

code

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1e5+5;
struct pp
{
int next,to;
}w[2*N];
int n,k;
int head[N],cnt;
int d1[N],d2[N],pre[N],u[N];
int fardeep[N];
int root,far;
int read()
{
int f=1;
char ch;
while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
int res=ch-'0';
while((ch=getchar())>='0'&&ch<='9') res=res*10+ch-'0';
return res*f;
}
void add(int x,int y)
{
cnt++;
w[cnt].next=head[x];
w[cnt].to=y;
head[x]=cnt;
}
bool cmp(int x,int y) {return x>y;}
void dfs1(int x,int fa)
{
for(int i=head[x];i;i=w[i].next)
{
int t=w[i].to;
if(t!=fa)
{
dfs1(t,x);
if(d1[t]+1>d1[x])
{
pre[x]=t;
d2[x]=d1[x];
d1[x]=d1[t]+1;
}
else if(d1[t]+1>d2[x]) d2[x]=d1[t]+1;
}
}
return;
}
void dfs2(int x,int fa)
{
for(int i=head[x];i;i=w[i].next)
{
int t=w[i].to;
if (t!=fa)
{
if(pre[x]!=t) u[t]=max(d1[x],u[x])+1;
else u[t]=max(d2[x],u[x])+1;
dfs2(t,x);
}
}
return ;
}
void dfs3(int x,int fa)
{
int minx=min(u[x],d1[x]);
if(far<minx)
{
root=x;
far=minx;
}
for(int i=head[x];i;i=w[i].next) if(w[i].to!=fa) dfs3(w[i].to,x);
return;
}
void dfs4(int x,int fa)
{
for(int i=head[x];i;i=w[i].next)
{
int t=w[i].to;
if(t!=fa)
{
dfs4(w[i].to,x);
fardeep[x]=max(fardeep[x],fardeep[t]+1);
}
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("XR-3.in","r",stdin);
freopen("XR-3.out","w",stdout);
#endif
n=read(),k=read();
for(int i=1;i<n;i++)
{
int x,y;
x=read(),y=read();
add(x,y);
add(y,x);
}
dfs1(1,0);
dfs2(1,0);
dfs3(1,0);
dfs4(root,0);
sort(fardeep+1,fardeep+1+n,cmp);
printf("%d",fardeep[k+1]+1);
return 0;
}

上面都是有关树的一些经典题型,下面才是今天的主角——树型DP


背包类树型DP

(我觉得把,其实左右子树类树型DP可以归为这一类)

例题

选课

书上的是时间复杂度为\(n^3\)的算法,这里介绍一个优化,可以讲其降为\(n^2\);

没有优化前,DP方程为:

\[ dp[u][j]=max\{dp[u][j],dp[u][j-k-1]+dp[v][k]\}+v[u]
\]

这样对于每个节点都要\(n^2\)暴力枚举\(j\)和\(k\);

经过优化,我们的DP方程就变为了:

\[ \begin{cases}
dp[v][j]=dp[u][j]+v[v]\\
dp[u][j]=max\{dp[u][j],dp[v][j-1]\}
\end{cases}
\]

这也是再泛化物品优化下,树型背包的基本DP方程;这样我们只需要\(O(n)\)枚举\(j\)就好了;

code

#include<iostream>
#include<algorithm>
#include<queue>
#include<cstdio>
#include<cstring>
using namespace std; int n,m;
struct edge
{
int next,to;
}e[1000];
int rt,head[1000],tot,val[1000],dp[1000][1000];
void add(int x,int y)
{
e[++tot].next=head[x];
head[x]=tot;
e[tot].to=y;
}
void dfs(int u,int t)
{
if (t<=0) return ;
for (int i=head[u]; i; i=e[i].next)
{
int v = e[i].to;
for (int j=0; j<t; ++j) //这里j从o开始到tot-1,因为v的子树可以选择的节点是u的子树的节点数减一;
dp[v][j] = dp[u][j]+val[v];
dfs(v,t-1);
for (int j=1; j<=t; ++j)
dp[u][j] = max(dp[u][j],dp[v][j-1]);//u必须选,所以u选择j个点v只能选择j-1个点;
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
int a;
scanf("%d%d",&a,&val[i]);
if(a)
add(a,i);
if(!a)add(0,i);
}
dfs(0,m);
printf("%d",dp[0][m]);
}

选择类树型DP

基本DP方程:

\[v\in{son(u)}
\begin{cases}
f[u][0]=\sum f[v][1] \\
f[u][1]=min\{f[v][1],f[v][0]\}+1
\end{cases}
\]

例题

P2016战略游戏

直接套DP方程就好了;

code

#include<iostream>
#include<cstdio>
using namespace std;
int n;
int dp[1605][2];
struct pp
{
int next,to;
}w[1600<<1];
int head[1600],cnt;
void add(int x,int y)
{
cnt++;
w[cnt].to=y;
w[cnt].next=head[x];
head[x]=cnt;
}
void dfs(int x,int fa)
{
dp[x][1]=1;
for(int i=head[x];i;i=w[i].next)
{
int t=w[i].to;
if(t==fa) continue;
dfs(t,x);
dp[x][0]+=dp[t][1];
dp[x][1]+=min(dp[t][0],dp[t][1]);
}
return;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int a,k;
scanf("%d%d",&a,&k);
for(int i=1;i<=k;i++)
{
int b;
scanf("%d",&b);
add(a,b);
add(b,a);
}
}
dfs(0,0);
printf("%d",min(dp[0][1],dp[0][0]));
return 0;
}

普通树型DP

这种树型DP更加灵活,就不像前两种有基本固定的DP方程,所以还是直接来几道例题;(滑稽

例题

LOJ #10157. 皇宫看守

乍一看题,啊哈,模板选择树型DP,开开心心打个代码,恭喜你0分;

仔细一看这道题其实不是什么没有上司的舞会,而是一道覆盖DP题,区别在哪呢?

这道题一条边两端至少要有一个点,可以有两个,而没有上司我舞会是一条边两端至多有一个点,可以没有;

那好,这样的话一个节点u的最少经费就不能像选择DP一样单纯的由儿子选不选的而转移过来,因为他们本来互不冲突,而是必须被覆盖到(这里每个节点的覆盖半径为1),这样对于一个节点u的最少经费就可以由覆盖它的节点转移过来,这样的话就需要考虑三种情况:

首先设\(dp[u][0]\)表示被节点\(u\)被父亲覆盖且\(u\)不选,\(dp[u][1]\)表示被自己的子节点覆盖且\(u\)不选,\(dp[u][2]\)表示被自己覆盖;

所以有状态转移方程:

  • 对于\(dp[u][0]\),因为\(u\)不选,所以对于\(u\)的子节点\(v\),要么被\(son(v)\)所覆盖,要么被\(v\)自己覆盖:
\[dp[u][0]=\sum min\{dp[v][1],dp[v][2]\} +a[f[u]];
\]
  • 对于\(dp[u][1]\),要保证\(u\)必须被一个子节点所覆盖到,还要保证\(u\)的子节点\(v\)在不被父亲覆盖的前提下被覆盖到,那显然\(dp[u][1]\),是由\(dp[v][1]\)和\(dp[v][2]\)转移过来的,但是如何保证\(dp[u][1]\)的转移中一定包含\(dp[v][2]\)呢?

    这时候有个巧妙的办法,设个参数:

    \[d=min\{d,dp[v][2]-min\{dp[v][1],dp[v][2]\}\}
    \]

    \(d\)的初始值为\(0x7fffffff\);

    这样对于\(dp[u][1]\)就有状态转移方程:

    \[dp[u][1]=\sum min\{dp[v][1],dp[v][2]\}+d
    \]
  • 对于\(dp[u][2]\),那很显然它可以由子节点任意三种状态转移过来,但是对于\(dp[v][0]\),它已经加过一遍\(a[u]\),而对于\(dp[u][2]\),只能且必须加一遍\(a[u]\),那怎么办呢?单独特判由\(dp[v][0]\)转移过来的情况,控制\(a[u]\)只加一遍?显然是可以的,但是太麻烦了,那么另外考虑,这里可以看到\(dp[v][0]\)只会往\(dp[u][2]\)上转移,那么可以根据\(dp[u][2]\)需求对\(dp[v][0]\)状态转移方程改一改:

    \[dp[u][0]=\sum min\{dp[v][1],dp[v][2]\}
    \]

    (这里的\(u\)是对于\(v\)来说的)

    感性理解一下就是如果\(dp[u][2]\)不由\(dp[v][0]\)转移过来那要\(dp[v][0]\)也没有什么用,那由\(dp[v][0]\)转移过来,那在\(dp[u][2]\)这加一遍\(a[u]\)就够了,因为\(dp[u][2]\)已经保证了\(u\)被选,所以不需要\(dp[v][0]\)再保证一遍;

    这样对于\(dp[u][2]\),就有状态转移方程:

    \[dp[u][2]=\sum min\{dp[v][1],dp[v][2],dp[v][0]\} +a[u]
    \]

总结下来就有三个状态转移方程:

\[\begin{cases}
dp[u][0]=\sum min\{dp[v][1],dp[v][2]\};\\

dp[u][1]=\sum min\{dp[v][1],dp[v][2]\}+d ;(d=min\{d,dp[v][2]-min\{dp[v][1],dp[v][2]\}\})\\

dp[u][2]=\sum min\{dp[v][1],dp[v][2],dp[v][0]\} +a[u]
\end{cases}
\]

(所以,显然书上的状态转移方程是错的)

不难发现,修改后的\(dp[v][0]\)一定小于等于\(dp[v][1]\);所以写代码的时候我顺手把\(dp[u][2]\)的转移方程改成了:

\[dp[u][2]=\sum min\{dp[v][2],dp[v][0]\} +a[u]
\]

虽然题目早已经解决了,但我还是想再深究一下;这个方程啥意思?

以我的感性理解就是\(v\)既然已经一定会被它爹\(u\)覆盖到,那就可以不需要保证\(v\)一定被它的儿子所覆盖,修改后的\(dp[v][0]\)刚好就是这种情况;

(好了,bb了这么多废话,就一点有用的东西,直接上代码吧)

code

#include <iostream>
#include <cstdio>
using namespace std;
const int N = 1500 + 5;
int dp[N][3];
int v[N], n, root;
struct pp {
int next, to;
} w[N];
int head[N], cnt, du[N];
void add(int x, int y) {
cnt++;
w[cnt].next = head[x];
w[cnt].to = y;
head[x] = cnt;
}
void dfs(int x) {
int d = 0x7fffffff;
for (int i = head[x]; i; i = w[i].next) {
int t = w[i].to;
dfs(t);
dp[x][0] += min(dp[t][1], dp[t][2]);
dp[x][1] += min(dp[t][1], dp[t][2]);
d = min(d, dp[t][2] - min(dp[t][1], dp[t][2]));
dp[x][2] += min(dp[t][2], dp[t][0]);
}
dp[x][1] += d;
dp[x][2] += v[x];
}
int main() {
#ifndef ONLINE_JUDGE
freopen("guard.in", "r", stdin);
freopen("guard.out", "w", stdout);
#endif
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
int x, m;
scanf("%d", &x);
scanf("%d", &v[x]);
scanf("%d", &m);
for (int j = 1; j <= m; j++) {
int y;
scanf("%d", &y);
add(x, y);
du[y]++;
}
}
for (int i = 1; i <= n; i++)
if (!du[i])
root = i;
dfs(root);
printf("%d", min(dp[root][1], dp[root][2]));
return 0;
}

好了,差不多就结束了,虽然写这个一点耗时,但对于我这个蒟蒻来说加深了对于DP的理解,收获也不小,也不算浪费时间了吧(逃);

树形DP 学习笔记的更多相关文章

  1. 树形$dp$学习笔记

    今天学习了树形\(dp\),一开始浏览各大\(blog\),发现都\(TM\)是题,连个入门的\(blog\)都没有,体验极差.所以我立志要写一篇可以让初学树形\(dp\)的童鞋快速入门. 树形\(d ...

  2. 树形DP学习笔记

    树形DP 入门模板题 poj P2342 大意就是一群职员之间有上下级关系,每个职员有一个快乐值,但是只有在他的直接上级不在场的情况下才会快乐.求举行一场聚会的快乐值之和的最大值. 求解 声明一个数组 ...

  3. 树形DP 学习笔记(树形DP、树的直径、树的重心)

    前言:寒假讲过树形DP,这次再复习一下. -------------- 基本的树形DP 实现形式 树形DP的主要实现形式是$dfs$.这是因为树的特殊结构决定的——只有确定了儿子,才能决定父亲.划分阶 ...

  4. 数位DP学习笔记

    数位DP学习笔记 什么是数位DP? 数位DP比较经典的题目是在数字Li和Ri之间求有多少个满足X性质的数,显然对于所有的题目都可以这样得到一些暴力的分数 我们称之为朴素算法: for(int i=l_ ...

  5. DP学习笔记

    DP学习笔记 可是记下来有什么用呢?我又不会 笨蛋你以后就会了 完全背包问题 先理解初始的DP方程: void solve() { for(int i=0;i<;i++) for(int j=0 ...

  6. [总结] 动态DP学习笔记

    学习了一下动态DP 问题的来源: 给定一棵 \(n\) 个节点的树,点有点权,有 \(m\) 次修改单点点权的操作,回答每次操作之后的最大带权独立集大小. 首先一个显然的 \(O(nm)\) 的做法就 ...

  7. 树形dp学习

    学习博客:https://www.cnblogs.com/qq936584671/p/10274268.html 树的性质:n个点,n-1条边,任意两个点之间只存在一条路径,可以人为设置根节点,对于任 ...

  8. 动态dp学习笔记

    我们经常会遇到一些问题,是一些dp的模型,但是加上了什么待修改强制在线之类的,十分毒瘤,如果能有一个模式化的东西解决这类问题就会非常好. 给定一棵n个点的树,点带点权. 有m次操作,每次操作给定x,y ...

  9. 斜率优化DP学习笔记

    先摆上学习的文章: orzzz:斜率优化dp学习 Accept:斜率优化DP 感谢dalao们的讲解,还是十分清晰的 斜率优化$DP$的本质是,通过转移的一些性质,避免枚举地得到最优转移 经典题:HD ...

随机推荐

  1. 【趣味设计模式系列】之【代理模式4--ASM框架解析】

    1. 简介 ASM是assemble英文的简称,中文名为汇编,官方地址https://asm.ow2.io/,下面是官方的一段英文简介: ASM is an all purpose Java byte ...

  2. 《spring源码解读》 - IoC 之解析 import 标签

    在上一文中我们分析了注册 BeanDefinition 的过程,在其中我们了解到在解析跟节点和子节点时分两种情况,对于默认名称空间的标签我们通过 DefaultBeanDefinitionDocume ...

  3. vue 中PDF实现在线浏览,禁止下载,打印

    需求:在线浏览pdf文件,并且禁止掉用户下载打印的效果. 分析:普通的iframe.embed标签都只能实现在线浏览pdf的功能,无法禁止掉工具栏的下载打印功能.只能尝试使用插件,pdfobject. ...

  4. 2020最新Servlet+form表单实现文件上传(图片)

    servlet实现文件上传接受 这几天学了一点文件上传,有很多不会,在网查了许多博客,但是最新的没有,都比较久了 因为我是小白,版本更新了,以前的方法自己费了好久才弄懂,写个随笔方便以后查找 代码奉上 ...

  5. Typed Lua

    https://the-ravi-programming-language.readthedocs.io/en/latest/ravi-overview.html https://github.com ...

  6. 深入了解Netty【一】BIO、NIO、AIO简单介绍

    引言 在Java中提供了三种IO模型:BIO.NIO.AIO,模型的选择决定了程序通信的性能. 1.1.使用场景 BIO BIO适用于连接数比较小的应用,这种IO模型对服务器资源要求比较高. NIO ...

  7. 消息队列之-RocketMQ入门

    简介 RocketMQ是阿里开源的消息中间件,目前已经捐献个Apache基金会,它是由Java语言开发的,具备高吞吐量.高可用性.适合大规模分布式系统应用等特点,经历过双11的洗礼,实力不容小觑. 官 ...

  8. JS中有趣的内置对象-JSON

    前言 在以前的web开发中,我们多数选择纯文本或XML作为我们的提交的数据格式,大多数是XML,少数纯文本.其实从AJAX(Asynchronous JavaScript and XML)的命名我们也 ...

  9. .NET Core 下使用 Apollo 配置中心

    Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境.不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限.流程治理等特性,适用于微服务配置管理场景.服务 ...

  10. CTF-WeChall-第三天下午

    2020.09.11 哈哈哈,中午改了博客背景,添加了背景音乐,verygood,有种小窝的感觉了,下午继续努力 做题 第一题 Shadowlamb - Chapter I 题目地址 Ugah做游戏. ...