dp

这里记录了一些自己写过的dp题,自己在这方面一直有所欠缺,做题的思路总是不好,那么就开一个专题来记录一下写过的题,希望能有所长进呢。

lougu P1121 环状最大两段子段和

这个题目需要注意到两点:

1.题目中的序列是环状的

2.我们需要找到两段不重合的子段

那么这两点如何解决呢,第一点的话比较难想,最先开始以为是把序列拼在后面去,结果会出现重复,那么这里的方法是:把每一个序列的值变为负数,然后靠dp方程求出这个序列此时的最大两段子段和 。此时求出的是什么呢,是原来子段中找到的最小两个不重合子段,那么这两个子段把整个序列分为三个部分,因为环状的原因,可以把它拼为两个子段,这样减去两个最小不重合子段就是最大的啦。

对于第二点,如何用dp求解最大不重合子段呢,我们只需要写两个数组f1[ ],f2[ ],定义f1[i]为在区间[1,i]之间能找到的最大子段,f2[i]为在[i+1,n]之间能找到的最大子段,即可求出呐。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44


#define INF 0x3f3f3f3f
using namespace std;

int (){
int res=0,f=1;char ch=' ';
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){res=res*10+ch-'0';ch=getchar();}
return res*f;
}
const int N=550005;
int n;
int f1[N],f2[N],ans1,ans2,sum,tot,a[N];
int DP(){
int ans=-INF;
for(register int i=1;i<=n;i++)f1[i]=max(f1[i-1],0)+a[i];
for(register int i=1;i<=n;i++)f1[i]=max(f1[i],f1[i-1]);
for(register int i=n;i>=1;i--)f2[i]=max(f2[i+1],0)+a[i];
for(register int i=n;i>=1;i--)f2[i]=max(f2[i+1],f2[i]);
for(register int i=1;i<n;i++)ans=max(ans,f1[i]+f2[i+1]);
return ans;
}//正着处理一遍,反着处理一遍,得出此时最大的不重合两段子段和
int main(){
n=read();
memset(f1,-INF,sizeof(f1));
memset(f2,-INF,sizeof(f2));//因为负数,记得预处理哦
for(register int i=1;i<=n;i++){
a[i]=read();
if(a[i]>0) sum++;
tot+=a[i];
}
ans1=DP();
if(sum==1) printf("%d",ans1);//当比一大的数只有一个的时候,之间求解即可
else{
for(register int i=1;i<=n;i++){
a[i]*=-1;
}
ans2=DP()+tot;//求出的最短两个不重合子段和,减掉就行
if(ans2==0) ans2=-INF;
printf("%d",max(ans1,ans2));//比较一个段求出的值与环状情况下的比较,输出最大的
}
return 0;
}

[HNOI2012]集合选数

这真的是一道很神奇的题目呢, 思路很清奇,咋一看感觉完全不会写,看了题解才知道需要构建一个矩阵,横边为前一个的两倍,竖边为前一个的三倍,那么我们只需要在每一个图里面找到可行的,就如x选了,那么2x,3x不可选,而4x,6x可选,因此求出每一个图的情况数,因为图是独立的,那么情况数应该乘起来。

解释都写在代码里面啦:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

using namespace std;

int (){
int res=0,f=1;char ch=' ';
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){res=res*10+ch-'0';ch=getchar();}
return res*f;
}
const int N=550005;//19 11
const int mod=1000000001;
int pos[30],n,mt[30][3005],vis[N],limit[30];//mt指的是每个位置的值,limit指的是当前这一排的边界
long long dp[30][3005];//dp统计答案
long long check(int x){
mt[1][1]=x;memset(limit,0,sizeof(limit));
for(register int i=2;i<=18;i++){
if(mt[i-1][1]*2<=n) mt[i][1]=mt[i-1][1]*2;
else mt[i][1]=n+1;
}
for(register int i=1;i<=18;i++){
for(register int j=2;j<=11;j++){
if(mt[i][j-1]*3<=n) mt[i][j]=mt[i][j-1]*3;
else mt[i][j]=n+1;
}
}//预处理出整个图
for(register int i=1;i<=18;i++){
for(register int j=1;j<=11;j++){
if(mt[i][j]<=n) limit[i]+=pos[j-1],vis[mt[i][j]]=1;
}
}//通过图来判断每个图的边界
for(register int i=0;i<=18;i++){
for(register int j=0;j<=limit[i];j++){
dp[i][j]=0;
}
}
dp[0][0]=1;//清零
for(register int i=0;i<=17;i++){
for(register int j=0;j<=limit[i];j++){
if(dp[i][j]){
for(register int k=0;k<=limit[i+1];k++){
if(!(k&j)&&!(k&(k>>1))){//这个把图分为变成染完色的01图,x选了2x,3x不能选择
dp[i+1][k]=(dp[i][j]+dp[i+1][k])%mod;
}
}
}
}
}
return dp[18][0];
}
int main(){
n=read();
pos[0]=1;
for(register int i=1;i<=18;i++) pos[i]=pos[i-1]*2;
long long ans=1;
for(register int i=1;i<=n;i++){
if(!vis[i]) ans=ans*check(i)%mod;//每个图的情况数相乘
}
printf("%lldn",ans);
return 0;
}

[HAOI2011]problem a

这个思路也较为灵活,一个人说有a个人比他菜 pzh比所有人菜 ,b个人比他强,那么在[a+1,n-b]之间的每个人实力是相同的,那么题目意思就化为了找到不相交的子段集合,使总和最大。

这题细节有点多:

1.因为会出现相同的区间,这样的话是满足条件的,但是他们重合了dp时不能算在一起怎么办呢,那么就以l边sort一遍,把相同的集合并在一块,增加其个数就行

2.dp方程如何转移,我们只需要找到离当前[l,r]区间最近的区间即可,也就是二分法找出一个区间,其r‘比当前区间的l小,且最近,所以:dp[i]=max(dp[i-1],dp[k]+b[i].w); b[i].w为当前集合的个数,dp[k]为k∈[1,i−1]时满足Rk < Li的最大的值.

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

using namespace std;

int (){
int res=0,f=1;char ch=' ';
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){res=res*10+ch-'0';ch=getchar();}
return res*f;
}
const int N=150005;
struct node{
int w,l,r;
}a[N],b[N];
int n,ans,num1,num2,dp[N];
bool cmp1(node x,node y){
if(x.l==y.l) return x.r<y.r;
return x.l<y.l;
}
bool cmp2(node x,node y){
if(x.r==y.r) return x.l<y.l;
return x.r<y.r;
}
int _find(int x){
int l=1,r=x-1;
while(l<=r){
int mid=(l+r)/2;
if(b[mid].r<b[x].l) l=mid+1;
else r=mid-1;
}
return r;
}//
int main(){
n=read();
for(register int i=1;i<=n;i++){
int x=read(),y=read();
if(x+y>=n) continue;
a[++num1].l=x+1;a[num1].r=n-y;
}
sort(a+1,a+1+num1,cmp1);
for(register int i=1;i<=num1;i++){
if(i==1||a[i].l!=a[i-1].l||a[i].r!=a[i-1].r) b[++num2]=a[i],b[num2].w=1;
else if(b[num2].w<b[num2].r-b[num2].l+1) b[num2].w++;
}//去除重复的区间
sort(b+1,b+1+num2,cmp2);
dp[1]=b[1].w;
for(register int i=2;i<=num2;i++){
int k=_find(i);
dp[i]=max(dp[i-1],dp[k]+b[i].w);//dp[i]表示排名在i前的说真话最多的数量
}
printf("%d",n-dp[num2]);//因为要求说谎话,减掉就行
return 0;
}

[ZJOI2005]午餐

这题运用了贪心+dp的思路来解决。

题目中给出了两个量,一个是打饭时间,一个是吃饭时间,那么很显然,打饭是必须要一个一个来的,而吃饭则没有这样的限制,那么首先排一遍序,将吃饭时间较长的放在前面,那么最后一个吃的肯定是吃饭时间最少的,下面dp一下即可。

因为排队时间是固定的,那么只需要用前缀和记录下前i个人总的排队时间。

定义一个f[ i ] [ j ]代表走到第i个点,甲窗口排队时间为j时吃完饭的最短时间,那么转移方程为下面两个:

如果为max前一个条件时,此时第i个人吃完了还有人在吃,故为前i-1个人吃完饭所需的最小时间。

如果为后一个条件时,第i个人吃完了就全部人吃完了,所以最后的时间点应该是第i个人打完饭的时间加上吃饭的时间。

因为f[i] [j]中的j代表的是在甲窗口排队时间,那么a[ i ] - j就是此时乙窗口排队的时间。其他方面类似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <bits/stdc++.h>
using namespace std;

int (){
int res=0,f=1;char ch=' ';
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){res=res*10+ch-'0';ch=getchar();}
return res*f;
}
const int N=1005;
int n;
struct node{
int x,y;
}t[N];
int f[N][N*15];
int a[N];
bool cmp(node A,node B){
return A.y>B.y;
}
int main(){
n=read();
for(register int i=1;i<=n;i++) t[i].x=read(),t[i].y=read();
sort(t+1,t+1+n,cmp);
memset(f,63,sizeof(f));
for(register int i=1;i<=n;i++){
a[i]=a[i-1]+t[i].x;
}
f[0][0]=0;
for(register int i=1;i<=n;i++){
for(register int j=0;j<=a[i];j++){
if(j>=t[i].x) f[i][j]=min(f[i][j],max(f[i-1][j-t[i].x],j+t[i].y));
f[i][j]=min(f[i][j],max(f[i-1][j],a[i]-j+t[i].y));
}
}
int ans=1<<30;
for(register int i=0;i<=a[n];i++){
ans=min(ans,f[n][i]);
}
printf("%dn",ans);
return 0;
}

[USACO5.4]周游加拿大Canada Tour

这种多线程的路径规划问题还挺少见的,记个博客增强一下记忆。

这题的第一A我是拿费用流水过去的(费用流还挺好想,拆了点之后把点内赋为流量1,费用1,路径赋为流量1,费用0,因为要走两次,那么就把第1和第n点拆点内多一条费用0,流量1的边。)

咳咳,这是正经的DP博客,还是讲讲这题DP的思路。

记得之前有一道传纸条的题,它需要两个人从起点走到终点,那么那道题用了一个二维数组f[i] [j]代表两个人分别走到的点,这道题也一样,那么考虑如何转移。

1
2
3
4
5
6
7
8
for(register int i=1;i<n;i++){
for(register int j=i+1;j<=n;j++){
for(register int k=1;k<j;k++){
if(l[j][k]&&f[i][k]) f[i][j]=max(f[i][j],f[i][k]+1);
}
f[j][i]=f[i][j];
}
}

第一个转移较为的好想,我们令此刻所在点为 j 点,那么它必由前面与其联通的一个点走过来。我们可以看出,如果只是有这一个转移并不行,因为我们只移动了 j 点,而i点的值并未更新。仔细一看你可以发现,i 必比j小,那么我们调换这两个人,即为 f[i] [j]和 f[j] [i]是必相等的,那么我们只需要用f[i] [j]更新f[j] [i]即可。

下面为代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <bits/stdc++.h>
using namespace std;

int (){
int res=0,f=1;char ch=' ';
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){res=res*10+ch-'0';ch=getchar();}
return res*f;
}
const int N=1005;
int n,m;
map<string,int>z;
int f[N][N],l[N][N];
int main() {
n=read();m=read();
for(register int i=1;i<=n;i++){
string s;cin>>s;
z[s]=i;
}
for(register int i=1;i<=m;i++){
string a,b;cin>>a>>b;
l[z[a]][z[b]]=l[z[b]][z[a]]=1;
}
f[1][1]=1;
for(register int i=1;i<n;i++){
for(register int j=i+1;j<=n;j++){
for(register int k=1;k<j;k++){
if(l[j][k]&&f[i][k]) f[i][j]=max(f[i][j],f[i][k]+1);
}
f[j][i]=f[i][j];
}
}
int ans=1;
for(register int i=1;i<=n;i++){
if(l[i][n]){
ans=max(ans,f[i][n]);
}
}
printf("%dn",ans);
return 0;
}

[ZJOI2008]骑士

前置姿势 没有上司的舞会

这题就是舞会的一个升级版,在没有上司的舞会中,数据保证会成一棵树,而这道题中,每一名骑士都有其痛恨的骑士,我们可以理解为有n个节点,n条边,那么在这个图中必然成环。

我们此时引入一个叫做基环树的东西,你可以理解为一棵树加上了一条边,使它成环。那么一棵基环树有什么神奇的性质呢?拆掉环中一条边,其必定成树

那么我们这道题目中,我们就可以考虑每次拆去环中的一条边,使其成为一棵树,再按照没有上司的舞会那样跑一遍树形dp即可。

我们遍历每一个环,我们将该环拆开,分别用拆的点两边的节点当作根节点统计答案,因为我们此时这两个节点不能都选,我们强制要求不选根节点,取两种情况中较大的答案,即为此时这个图的答案,那么最后将所有图的答案加起来即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <iostream>
#include <bits/stdc++.h>
using namespace std;

int read(){
int res=0,f=1;char ch=' ';
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){res=res*10+ch-'0';ch=getchar();}
return res*f;
}
const int N=5e6+5;
long long f[N][2];
int n,fa[N];
int findf(int x){
if(fa[x]==x) return x;
else return fa[x]=findf(fa[x]);
}
int l[N],r[N],line[N],tot;
int head[N],to[N],cnt=1,nxt[N];
void qxx(int x,int y){
cnt++;nxt[cnt]=head[x];head[x]=cnt;to[cnt]=y;
}
int flag[N];
long long val[N];
void dfs(int x,int fa){
f[x][0]=0;f[x][1]=val[x];
for(register int i=head[x];i;i=nxt[i]){
int k=to[i];
if(!flag[i]&&k!=fa){
dfs(k,x);
f[x][0]+=max(f[k][1],f[k][0]);
f[x][1]+=f[k][0];
}
}
}
int main() {
n=read();
for(register int i=1;i<=n;i++) fa[i]=i;
for(register int i=1;i<=n;i++){
val[i]=read();
int x=i,y=read();
qxx(x,y);qxx(y,x);
int fx=findf(x),fy=findf(y);
if(fx!=fy) fa[fx]=fy;
else {
l[++tot]=x;r[tot]=y;line[tot]=cnt-1;
}
}
long long ans=0,sum1=0,sum2=0;
for(register int i=1;i<=tot;i++){
flag[line[i]]=flag[line[i]^1]=1;
dfs(l[i],0); sum1=f[l[i]][0];
dfs(r[i],0); sum2=f[r[i]][0];
ans+=max(sum1,sum2);
flag[line[i]]=flag[line[i]^1]=0;
}
printf("%lldn",ans);
return 0;
}

奶牛家谱 Cow Pedigrees

毒瘤蓝题,第一次发现dp的蓝题这么难做。%%%kanekik(这个神仙写的题又猛又劲)

如果稍有树型dp经验的,很容易就知道这个题需要一层一层的处理,那么该如何转移呢。

我们定义f[i] [j]表示为深度为 ij 个节点一共的情况数,不难相出一共有三种情况:

1.左子树深度小于 i ,右子树深度等于 i 2.左子树深度等于 i ,右子树深度小于 i 3.左子树右子树深度都等于i

当一个子树深度小于 i 的时候,那么其深度有多种情况,我们另记一个s[i] [j]数组,表示深度小于等于 i 节点个数选择 j 的情况总数

那么转移方程就很简单了

另外,如果枚举的左右节点个数不相等时,所增加的情况应乘以2

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <bits/stdc++.h>
using namespace std;

int read(){
int res=0,f=1;char ch=' ';
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){res=res*10+ch-'0';ch=getchar();}
return res*f;
}
const int N=1e5+5;
const int mod=9901;
int n,m;
int f[205][205],s[205][205];
int main(){
n=read();m=read();
f[1][1]=1;
for(register int i=2;i<=m;i++){
for(register int j=1;j<=n;j+=2)
for(register int k=1;k<=j-1-k;k+=2){
int x=2;
if(k==j-1-k) x=1;
f[i][j]=f[i][j]%mod+x*(s[i-2][k]*f[i-1][j-1-k]%mod+s[i-2][j-1-k]*f[i-1][k]%mod+f[i-1][j-1-k]*f[i-1][k]%mod)%mod;
}
for(register int k=0;k<=n;k++){
s[i-1][k]+=s[i-2][k]+f[i-1][k];//统计小于i的所有情况
s[i-1][k]%=mod;
}
}
printf("%dn",f[m][n]);
return 0;
}

但题解中给出了另一个更巨的写法,我们只考虑以上所说的s数组,那么求n个节点k的深度我们只需要 s[n] [k]-s[n] [k-1]

并且按照乘法原理,转移方程为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <bits/stdc++.h>
using namespace std;

int read(){
int res=0,f=1;char ch=' ';
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){res=res*10+ch-'0';ch=getchar();}
return res*f;
}
const int N=1e5+5;
const int mod=9901;
int n,k;
int f[205][105];
int main(){
n=read();k=read();
for(register int i=1;i<=k;i++) f[1][i]=1;
for(register int i=1;i<=k;i++){
for(register int j=3;j<=n;j+=2){
for(register int g=1;g<j;g+=2){
f[j][i]=(f[j][i]+1ll*f[g][i-1]*f[j-g-1][i-1]%mod)%mod;
}
}
}
printf("%dn",(f[n][k]-f[n][k-1]+mod)%mod);
return 0;
}

Noip2014 飞扬的小鸟

要预先知道这种有关游戏的题目怎么做,很显然要多看几遍题目 玩几遍游戏

刚看到该题的时候我心中一想,这不是dp水题吗?题目中有清晰的转移方程,适当的数据范围,看起来非常的好写。那么我第一时间想出的dp方程即为:

前面这个直接转移,后面那个用背包搞一搞就行。

我快乐的打了一发,样例都没过让我感到的这题的毒瘤之处,我在此记录一下我这题吃的苦。

1.题目中给的范围为0~n-1。

2.所给的管道上下界需要加一减一,因为鸟遇到管道就死了。

3.上界为m并不会死,而下界为0却会死亡。

4.写方程转移时,需要先从之前的一个状态通过上升转移过来,然后用背包背一下,最后转移啥都不点掉下来,为此我搞了一个数组来操作。

事实证明,敲代码之前多思考还是最重要的哇,以后看到这种题还是要多考虑细节。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <bits/stdc++.h>
using namespace std;

int read(){
int res=0,f=1;char ch=' ';
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){res=res*10+ch-'0';ch=getchar();}
return res*f;
}
const int N=10005;
int n,m,k,X[N],Y[N],dp[N],sum[N],pos[N];
int l[N],h[N],f[10005][2005],inf;
int main(){
n=read();m=read();k=read();
l[0]=1;h[0]=m;
for(register int i=0;i<=n-1;i++){
l[i]=1;h[i]=m;
X[i]=read();Y[i]=read();
}
for(register int i=1;i<=k;i++){
int x=read();pos[x]++;
l[x]=read()+1;h[x]=read()-1;
}
for(register int i=0;i<=n-1;i++) sum[i]=sum[i-1]+pos[i];
memset(f,63,sizeof(f));inf=f[0][0];
for(register int i=1;i<=m;i++) f[0][i]=0;
for(register int i=0;i<=n;i++){
for(register int j=0;j<=m;j++) dp[j]=inf;
for(register int j=l[i];j<=h[i];j++){
dp[min(m,j+X[i])]=min(dp[min(m,j+X[i])],f[i][j]+1);
}
for(register int j=1;j<=m;j++){
dp[min(m,j+X[i])]=min(dp[min(m,j+X[i])],dp[j]+1);
}
for(register int j=l[i];j<=h[i];j++){
if(j-Y[i]>0) dp[j-Y[i]]=min(dp[j-Y[i]],f[i][j]);
}
for(register int j=1;j<=m;j++){
f[i+1][j]=dp[j];
}
}
int minv=inf;
for(register int i=1;i<=m;i++){
minv=min(minv,f[n][i]);
}
if(minv==inf){
int ans=0;
for(register int i=n-1;i>=0;i--){
int judge=0;
for(register int j=1;j<=m;j++){
if(f[i][j]!=inf){
judge=1;break;
}
}
if(judge){
puts("0");
printf("%dn",sum[i]-1);
return 0;
}
}
}
puts("1");
printf("%dn",minv);
return 0;
}

关路灯

好久没更新这个dp题目专题啦,最近写了个题,就搬到这边来水水。

很明显题目中所说的贪心思路是错误的,我们考虑区间dp,定义此时f[i] [j] [k]当前区间为i,j,并且当前点在左端点还是右端点,k=0代表的是在左端点,k=1代表的是在右端点。现在考虑转移,如果考虑每次加上一个点就加上这个点所需要的花费,花费=持续时间*当前路灯的功耗,但仔细一想,这个持续时间如果求出呢?我最先开始准备另外记下一个数组代表当前这个状态,但后来发现当前状态最优的时间不一定对后面状态最优,我们考虑其他转移。我们考虑每次这个人移动一段距离,其他未关的点总花费,这样就成功避免了时间这一毒瘤状态。

那么转移方程即为:

下面是代码啦:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

#include <bits/stdc++.h>
using namespace std;
int read(){
int res=0,f=1;char ch=' ';
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){res=res*10+ch-'0';ch=getchar();}
return res*f;
}
const int N=1e2+5;
int n,c,sum[N];
struct node{
int pos,val;
}t[N];
bool cmp(node a,node b){return a.pos<b.pos;}
int f[N][N][2];
int get_dis(int l,int r){
return sum[n]-sum[r]+sum[l-1];
}
int main(){
n=read();c=read();
for(register int i=1;i<=n;i++) t[i].pos=read(),t[i].val=read();
sort(t+1,t+1+n,cmp);
for(register int i=1;i<=n;i++) sum[i]=sum[i-1]+t[i].val;
memset(f,63,sizeof(f));
f[c][c][0]=f[c][c][1]=0;
for(register int len=2;len<=n;len++){
for(register int l=1;l<=n-len+1;l++){
int r=l+len-1;
f[l][r][0]=min(f[l][r][0],min(f[l+1][r][0]+(t[l+1].pos-t[l].pos)*get_dis(l+1,r),f[l+1][r][1]+(t[r].pos-t[l].pos)*get_dis(l+1,r)));
f[l][r][1]=min(f[l][r][1],min(f[l][r-1][0]+(t[r].pos-t[l].pos)*get_dis(l,r-1),f[l][r-1][1]+(t[r].pos-t[r-1].pos)*get_dis(l,r-1)));
}
}
printf("%dn",min(f[1][n][1],f[1][n][0]));
return 0;
}