kuangbin 基础DP集合

HDU 1024
第一遍水过,没有体会到这个题的奥妙,思考了很久终于体会了。
大概意思是求把序列分成m段的子序列,并不一定要覆盖完,求子序列和的最大值
我们首先要写出基本的动态转移方程:
DP:dp[ i ] [ j ] =max ( dp[ i - 1 ] [ 1~j-1 ]+a[ j ],dp[ i ] [ j - 1 ]+a[ j ] )
其中dp[ i ][ j ]其中i,j表示,第i个集合,取前j 个数。
意思是,dp[ i ][ j ]是由两个状态转移过来的,一个是把新的数归入i集合,还有一个是把新的数作为第一
个数,归入新的集合。但是显然这个DP方程显得过于臃肿,我们想办法化简一下,我们发现,每次dp[i][j]
都只和dp[i][j-1]以及dp[i-1][1~j-1]中的最大值有关,因此我们不妨把dp化简,用两个数组表示,now[j]数组表
示当前a[j]在子序列中的最优值。pre[j]代表在上个序列中序列j最大值,在结合程序,也就比较好相通了。
相当于now维护dp[ i ] [ j ],而pre[j]维护的是dp[i-1][j] 。

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<string.h>
using namespace std;
const int maxn = 1e6+7;
const int INF = 0x3f3f3f3f;
int now[maxn];
int a[maxn];
int pre[maxn];
int main(){
  int m,n;
  while(~scanf("%d%d",&m,&n)){

     for (int i=1;i<=n;i++){
        scanf("%d",&a[i]);
     }
     int maxx;
     memset(now,0,sizeof(now));
     memset(pre,0,sizeof(pre));
     for (int i=1;i<=m;i++){
        maxx=-INF;
        for (int j=i;j<=n;j++){
            now[j]=max(now[j-1]+a[j],pre[j-1]+a[j]);
            pre[j-1]=maxx;
            if(now[j]>maxx)maxx=now[j];
        }
     }
     printf("%d\n",maxx);
  }
  return 0;
}
View Code

还做到一个很类似的题目:
把一段序列分成多个段,每个段最多k个,用每段的最大值替换每段里面的所有值。
其实也是一个DP:
maxx代表i-j内最大值,dp[j]=(dp[i-1]+maxx*(j-i+1),dp[j])
这样就非常简单的了,我们想想为什么,因为首先我们要确定一定要维护一个区间的最大值,因此也要
枚举区间,然后很容易写出转移方程。

B-不说了。。。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxx = 1e6+7;
int a[maxx];
int n;
int main(){
  while(~scanf("%d",&n)){
     for (int i=1;i<=n;i++){
        scanf("%d",&a[i]);
     }
     sort(a+1,a+1+n);
     int ans=0;
     int last=a[1];
     int cnt=0;
     for (int i=1;i<=n;i++){
        if (last==a[i]){
            cnt++;
        }else {
           last=a[i];
           cnt=1;
        }
        if (ans<cnt){
            ans=a[i];
        }
     }
     printf("%d\n",ans);
  }
  return 0;
}
View Code

C-最长递增子序列问题翻版
之间把所有可能的情况加入,按照X坐标排序后,写出限制条件,求最长的可能。
dp[i]=max(dp[i],dp[j]+1) 其中j<i

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
struct node{
  int x,y,z;
}a[200];
int tot;
void join(int x,int y,int z){
  a[++tot].x=x;
  a[tot].y=y;
  a[tot].z=z;
}
bool cmp(node a,node b){
   return a.x<b.x;
}
int dp[80];
int main(){
  int n;
  int cas=1;
  while(~scanf("%d",&n)){
      if (n==0)break;
      int tmp[5];
      tot=0;
      memset(a,0,sizeof(a));
      for (int i=1;i<=n;i++){
        scanf("%d%d%d",&tmp[1],&tmp[2],&tmp[3]);
            join(tmp[1],tmp[2],tmp[3]);
            join(tmp[1],tmp[3],tmp[2]);
            join(tmp[2],tmp[1],tmp[3]);
            join(tmp[2],tmp[3],tmp[1]);
            join(tmp[3],tmp[1],tmp[2]);
            join(tmp[3],tmp[2],tmp[1]);
      }
      dp[0]=0;
      sort(a+1,a+1+tot,cmp);
      for (int i=1;i<=tot;i++){
          dp[i]=a[i].z;
          for (int j=i-1;j>=1;j--){
            if (a[j].x<a[i].x && a[j].y<a[i].y){
            dp[i]=max(dp[i],dp[j]+a[i].z);
            }
          }
      }
      int ans=0;
      for (int i=1;i<=tot;i++){
        ans=max(ans,dp[i]);
      }
      printf("Case %d: maximum height = %d\n",cas++,ans);
  }
  return 0;
}
View Code

D-经典的时间分配问题
状压DP
n种物品,枚举每一位的情况,用1-1<<n-1表示每一位是否存在,然后计算得分,然后维护dp[]最小,同时
保存路径,由于是求字典序最小,我们需要把枚举顺序反向,然后最后反向输出结果即可,因为我们记录的
是当前情况之前的情况,因此我们需要从最终情况开始往前找,找到前面即可,

#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
using namespace std;
const int maxx = 1<<15;
const int INF = 0x3f3f3f3f;
char name[20][120];
int die[20];
int fin[20];
int dp[maxx];//到达当前状态所需要的最小得分
int t[maxx];//做作业所需要花的时间
int pre[maxx];//到达某个状态所需要完成的课程
void output(int x){
   if (!x)return;
   output(x-(1<<pre[x]));
   printf("%s\n",name[pre[x]]);
}
int main(){
  int ca;
  scanf("%d",&ca);
  int n;
  while(ca--){
    scanf("%d",&n);
    memset(t,0,sizeof(t));
    int bit=1<<n;
    for (int i=0;i<n;i++){
        scanf("%s%d%d",name[i],&die[i],&fin[i]);
    }
    for (int i=1;i<bit;i++){
        dp[i]=INF;
        for (int j=n-1;j>=0;j--){
            int temp=1<<j;//第j位的情况
            if((i&temp)==0)continue;//这一位没有被选到
            int score=t[i-temp]+fin[j]-die[j];
            if (score<0)score=0;
            if (dp[i]>dp[i-temp]+score){//把分最小化
                dp[i]=dp[i-temp]+score;//更新为更小的
                t[i]=t[i-temp]+fin[j];//达到状态t[i]所花的时间
                pre[i]=j;//到达状态i的前驱
            }
        }
    }
    printf("%d\n",dp[bit-1]);
    output(bit-1);
  }
  return 0;
}
View Code

E Super Jumping! Jumping! Jumping!
翻版最长递增子序列

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string.h>
#define LL long long
using namespace std;
int a[1008];
LL dp[1008];
int main(){
  int n;
  while(~scanf("%d",&n) && n){
     for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
     }
     memset(dp,0,sizeof(dp));
     for (int i=1;i<=n;i++){
        dp[i]=a[i];
        for (int j=i-1;j>=1;j--){
            if (a[i]>a[j])
            dp[i]=max(dp[i],dp[j]+a[i]);
        }
     }
     LL ans=0;
     for (int i=1;i<=n;i++){
        ans=max(ans,dp[i]);
     }
     printf("%lld\n",ans);
  }
  return 0;
}
View Code

F HDU - 1114
完全背包+背包装满
由于是求最小值,初始化为边界值,然后判断是否仍是边界值,从而判断是否装满,
对于每个物品,枚举每个状态,并顺序枚举,这样每个状态可能用多次,从而达到完全背包的效果,反之
01背包则不行,必须反向枚举,每个状态都只能由一个状态构成。

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<string.h>
using namespace std;
int dp[10005];
int p[505];
int w[505];
const int INF = 0x3f3f3f3f;
int main(){
  int t;
  int E,F;
  scanf("%d",&t);
  while(t--){
     scanf("%d%d",&E,&F);
     int W=F-E;
     int n;
     scanf("%d",&n);
     memset(dp,0,sizeof(dp));
     for (int i=1;i<=n;i++){
        scanf("%d%d",&p[i],&w[i]);
     }
     for(int i=1;i<=W;i++){
        dp[i]=INF;
     }
     for (int i=1;i<=n;i++){
        for (int j=w[i];j<=W;j++){
            dp[j]=min(dp[j-w[i]]+p[i],dp[j]);
        }
     }
     if (dp[W]==INF){
        printf("This is impossible.\n");
     }else {
        printf("The minimum amount of money in the piggy-bank is %d.\n",dp[W]);
     }
  }
  return 0;
}
View Code

G HDU - 1176
数塔问题,从低到高反向求解

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int a[100015][12];
int dp[100015][12];
int main(){
  int n;
  while(scanf("%d",&n)!=EOF && n){
     int tmp1,tmp2;
     int t=0;
     memset(a,0,sizeof(a));
     memset(dp,0,sizeof(dp));
     for (int i=1;i<=n;i++){
        scanf("%d%d",&tmp1,&tmp2);
        a[tmp2][tmp1]++;
        t=max(tmp2,t);
     }
     for (int i=0;i<=10;i++){
        dp[t][i]=a[t][i];
     }
     for (int i=t-1;i>=0;i--){
         for (int j=0;j<=10;j++){
            if (j==0)
            dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+a[i][j];
            else
            dp[i][j]=max(dp[i+1][j],max(dp[i+1][j-1],dp[i+1][j+1]))+a[i][j];
         }
     }
    printf("%d\n",dp[0][5]);
  }
  return 0;
}
View Code

H- HDU 1260
简单DP,给出买一张,买相邻两张的价格,可以选择买一张或者买两张相邻的。
转移方程
dp[i]=max(dp[i-1]+one[i],dp[i-2]+two[i-1])

#include<iostream>
#include<string.h>
#include<algorithm>
#include<stdio.h>
using namespace std;
int dp[2022];
int one[2022];
int two[2022];
const int INF = 0x3f3f3f3f;
int main(){
  int t;
  int n;
  scanf("%d",&t);
  while(t--){
    scanf("%d",&n);
    for (int i=1;i<=n;i++){
        scanf("%d",&one[i]);
    }
    for (int i=1;i<=n-1;i++){
        scanf("%d",&two[i]);
    }
    dp[1]=one[1];
    for (int i=2;i<=n;i++){
        dp[i]=min(dp[i-1]+one[i],dp[i-2]+two[i-1]);
    }
    int a=dp[n]/3600+8;
    int b=(dp[n]/60)%60;
    int c=dp[n]%60;
        if(a>12||(a==12&&(b>0||c>0)))
        printf("%.2d:%.2d:%.2d pm\n",a,b,c);
        else
        printf("%.2d:%.2d:%.2d am\n",a,b,c);
  }
  return 0;
}
View Code

I-HDU 1257
需要递减覆盖数目=最长递增子序列个数,因为没最长的递增子序列中的每个数,一定能把所有递减的序列
给覆盖掉。

#include<iostream>
#include<string.h>
#include<algorithm>
#include<stdio.h>
using namespace std;
int dp[10005];
int h[10005];
int main(){
   int n;
   while(~scanf("%d",&n)){

      for (int i=1;i<=n;i++){
        scanf("%d",&h[i]);
      }
      for (int i=1;i<=n;i++){
         dp[i]=1;
         for (int j=1;j<i;j++){
           if (h[i]>h[j]){
             dp[i]=max(dp[i],dp[j]+1);
           }
         }
      }
     int ans=0;
     for (int i=1;i<=n;i++){
        ans=max(ans,dp[i]);
     }
     printf("%d\n",ans);
   }
   return 0;
}
View Code

J-HDU - 1160
排序+最长递增子序列问题

#include<iostream>
#include<string.h>
#include<algorithm>
#include<stdio.h>
#include<stack>
using namespace std;
const int maxx = 1005;
struct node
{
    int w,s,id;
} a[maxx];
bool cmp1(node a,node b)
{
    return a.w<b.w;
}
int dp[1005];
int pre[1005];
int ans[1005];
int main()
{
    int n=1;
    while(~scanf("%d%d",&a[n].w,&a[n].s))
    {
        a[n].id=n;
        n++;
    }
    memset(pre,-1,sizeof(pre));
    memset(dp,0,sizeof(dp));
    sort(a+1,a+n,cmp1);
    for (int i=1; i<n; i++)
    {
        dp[i]=1;
        for (int j=1; j<i; j++)
        {
            if (a[j].w<a[i].w && a[j].s>a[i].s){
                if (dp[i]<dp[j]+1){
                    dp[i]=dp[j]+1;
                    pre[i]=j;
                }
            }
        }
    }
//    for (int i=1;i<n;i++){
//        cout<<a[i].w<<" "<<a[i].id<<endl;
//    }
    int maxx=0;
    int max_id;
    for (int i=1; i<n; i++)
    {
        if (maxx<dp[i])
        {
            maxx=dp[i];//
            max_id=i;//记录下最后面的那个的位置
        }
    }
    int p=max_id;
    printf("%d\n",maxx);
    int cnt=1;
    while(p!=-1)
    {
        ans[cnt++]=a[p].id;
        //printf("%d\n",a[p].id);
        p=pre[p];
    }
    for (int i=cnt-1;i>=1;i--){
        printf("%d\n",ans[i]);
    }
    return 0;
}
View Code

L-POJ - 1458
最长公共序列问题
用dp[i][j]表示,匹配到第i位和第j位时的最长递增公共序列的情况,
转移方程
第i位和第j位不匹配时
dp[i][j]=max(dp[i-1][j]),dp[i][j-1])
匹配时
dp[i][j]=dp[i-1][j-1]+1;

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
char a[10005];
char b[10005];
int dp[1005][1005];
int main(){
   while(~scanf("%s%s",&a,&b)){
    memset(dp,0,sizeof(dp));
    int n=strlen(a);
    int m=strlen(b);
    for (int i=1;i<=n;i++){
        for (int j=1;j<=m;j++){
          if (a[i-1]==b[j-1]){
            dp[i][j]=dp[i-1][j-1]+1;
          }else {
            dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
          }
        }
    }
    printf("%d\n",dp[n][m]);
   }
   return 0;
}
View Code

M-POJ - 1661
高级版本数塔+排序
dp[i][0]表示跑到第i层的左边,left表示下面层数的左边跑上来,right是表示下面层的右边跑上来
dp[i][1]表示跑到第i层的右边
dp[i][0]=min(dp[left][0]+a[i].l-a[left].l,dp[left][1]+a[left].r-a[i].l);
dp[i][1]=min(dp[right][0]+a[i].r-a[right].l,dp[right][1]+a[right].r-a[i].r);

#include<iostream>
#include<string.h>
#include<algorithm>
#include<stdio.h>
#define LL long long
using namespace std;
struct node
{
    int l,r,h;
} a[1005];
bool cmp(node a,node b)
{
    return a.h<b.h;
}
int dp[1005][3];
int main()
{
    int t;
    scanf("%d",&t);
    int n,x,y,maxx;
    while(t--)
    {
        scanf("%d%d%d%d",&n,&x,&y,&maxx);
        a[0].l=x;
        a[0].r=x;
        a[0].h=y;
        memset(dp,0,sizeof(dp));
        for (int i=1; i<=n; i++)
        {
            scanf("%d%d%d",&a[i].l,&a[i].r,&a[i].h);
            dp[i][0]=0x3f3f3f3f;
            dp[i][1]=0x3f3f3f3f;
        }
        sort(a,a+1+n,cmp);
        for (int i=0;i<=n;i++){
            int left=-1;
            int right=-1;
            for (int j=i-1;j>=0;j--){
               if(left==-1 && maxx>=a[i].h-a[j].h && a[j].r>=a[i].l && a[j].l<=a[i].l){
                left=j;
               };
               if(right==-1 && maxx>=a[i].h-a[j].h && a[j].r>=a[i].r && a[j].l<=a[i].r){
                right=j;
               }
            }
            if (left!=-1){
                dp[i][0]=min(dp[left][0]+a[i].l-a[left].l,dp[left][1]+a[left].r-a[i].l);
            }else {
                if (a[i].h<=maxx)dp[i][0]=0;
            }
            if (right!=-1){
                dp[i][1]=min(dp[right][0]+a[i].r-a[right].l,dp[right][1]+a[right].r-a[i].r);
            }else{
                if (a[i].h<=maxx)dp[i][1]=0;
            }
        }
        printf("%d\n",dp[n][0]+y);
    }
    return 0;
}
View Code

N-POJ 2533
最长递增子序列

#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
using namespace std;
int a[1005];
int dp[1005];
int main(){
  int n;
  while(~scanf("%d",&n)){
     for (int i=1;i<=n;i++){
        scanf("%d",&a[i]);
     }
     memset(dp,0,sizeof(dp));
     for (int i=1;i<=n;i++){
        dp[i]=1;
        for (int j=1;j<i;j++){
            if (a[i]>a[j]){
                dp[i]=max(dp[i],dp[j]+1);
            }
        }
     }
     int ans=0;
     for (int i=1;i<=n;i++){
        ans=max(ans,dp[i]);
     }
    printf("%d\n",ans);
  }
  return 0;
}
View Code

O-POJ 3186
区间DP
dp[i][j]选到代表从左往右第i个和从右往左第j个
那么dp[i][j]=max(dp[i-1][j]+(i+j)*a[i],d[i][j-1]+(i+j)*a[n-j+1]);
最后找出在dp[i][n-1]中最大值的即可。

#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
using namespace std;
int a[2006];
int dp[2006][2006];
int main(){
  int n;
  while(~scanf("%d",&n)){
     for (int i=1;i<=n;i++){
        scanf("%d",&a[i]);
     }
     for (int i=0;i<=n;i++){
        for (int j=0;j+i<=n;j++){
            if (i>0 && j>0){
                dp[i][j]=max(dp[i-1][j]+(i+j)*a[i],dp[i][j-1]+(i+j)*a[n-j+1]);
            }else if (j>0){
                dp[i][j]=dp[i][j-1]+j*a[n-j+1];
            }else if (i>0){
                dp[i][j]=dp[i-1][j]+i*a[i];
            }
        }
     }
     int ans=0;
     for (int i=0;i<=n;i++){
        ans=max(dp[i][n-i],ans);
     }
     printf("%d\n",ans);
  }
  return 0;
}
View Code

P-HDU - 1078
DFS+DP
dp[i][j]=max(dp[i][j],dfs(i,j+k)+a[i][j]);
dp[i][j]=max(dp[i][j],dfs(i+k,j)+a[i][j]);

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define rep(i,j,k) for(int i=j;i<=k;i++)
using namespace std;
int a[115][115];
int dp[115][115];
int n,k;
int dfs(int x,int y)
{
    if (dp[x][y])return dp[x][y];
    dp[x][y]=a[x][y];
    for (int i=1;i<=k;i++){
        if (x+i<=n && a[x+i][y]>a[x][y]){
            dp[x][y]=max(dp[x][y],dfs(x+i,y)+a[x][y]);
        }
        if (x-i>=1 && a[x-i][y]>a[x][y]){
            dp[x][y]=max(dp[x][y],dfs(x-i,y)+a[x][y]);
        }
        if (y+i<=n && a[x][y+i]>a[x][y]){
            dp[x][y]=max(dp[x][y],dfs(x,y+i)+a[x][y]);
        }
        if (y-i>=1 && a[x][y-i]>a[x][y]){
            dp[x][y]=max(dp[x][y],dfs(x,y-i)+a[x][y]);
        }
    }
    return dp[x][y];
}
int main()
{
    while(~scanf("%d",&n))
    {
        memset(dp,0,sizeof(dp));
        scanf("%d",&k);
        if (n==-1 && k==-1)break;
        for (int i=1; i<=n; i++)
        {
            for (int j=1; j<=n; j++)
            {
                scanf("%d",&a[i][j]);
            }
        }
        printf("%d\n",dfs(1,1));
    }
    return 0;
}
View Code

Q-HDU - 2859
最大对称矩阵
从左下角往右上角
那么dp[i][j]=(dp[i-1][j+1]+1,dp[i][j])同时两边新增加的横和竖也要对称长度大于dp[i-1][j+1]
否则就等于MIN(对称长度,dp[i-1][j+1])

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int dp[1006][1006];
int n;
char a[1006][1006];
int main(){
  int n;
  while(~scanf("%d",&n) && n){

     for (int i=0;i<n;i++){
        scanf("%s",a[i]);
     }
     int ans=0;
     for (int i=0;i<n;i++){
        for (int j=0;j<n;j++){
           dp[i][j]=1;
           int x=i;
           int y=j;
           while(x>=0 && y<n && a[x][j]==a[i][y]){
             x--;
             y++;
           }
           int len=i-x;
           if (len>dp[i-1][j+1]){
             if (dp[i][j]<dp[i-1][j+1]+1)
             dp[i][j]=dp[i-1][j+1]+1;
           }else {
             dp[i][j]=len;
           }
           ans=max(ans,dp[i][j]);
        }
     }
     printf("%d\n",ans);
  }
  return 0;
}
View Code

R-POJ 3616
翻版最长递增子序列

#include<iostream>
#include<string.h>
#include<algorithm>
#include<stdio.h>
#define LL long long
using namespace std;
struct node{
   LL s;
   LL en;
   LL ef;
}a[2006];
bool cmp(node a,node b){
  if (a.s==b.s){
    return a.en<b.en;
  }else {
    return a.s<b.s;
  }
}
LL dp[2006];
int main(){
  int n,m,r;
  while(~scanf("%d%d%d",&n,&m,&r)){
    memset(dp,0,sizeof(dp));
    for (int i=1;i<=m;i++){
        scanf("%lld%lld%lld",&a[i].s,&a[i].en,&a[i].ef);
    }
    sort(a+1,a+1+m,cmp);
    for (int i=1;i<=m;i++){
        dp[i]=a[i].ef;
        for (int j=1;j<i;j++){
         //   cout<<a[i].s<<" "<<a[j].en<<endl;
            if(a[i].s>=a[j].en+r){
                dp[i]=max(dp[i],dp[j]+a[i].ef);
            }
        }
    }
    LL ans=0;
    for (int i=1;i<=m;i++){
        ans=max(ans,dp[i]);
    }
    printf("%lld\n",ans);
  }
  return 0;
}
View Code

S-POJ 3666

变成有序数列的最小代价
dp[i][j]表示选到a[i]的时候,把a[i]变成数列中第j小的数。
首先我们容易写出基本的DP
dp[i][j]=abs(j-w[i])+min(dp[i-1][k]);(k<=j)
表示选到i时,以j为最大值。
他是由j-w[i]以及dp[i-1][k]中以k为最小值(k<=j)传来的
那么我只需了,遍历时维护dp[i-1][k]=min即可
方程也就写成了dp[i][j]=abs(a[i]-j)+min;
最后把j离散化即可
dp[i][j]=abs(a[i]-b[j])+min.其中b是a排序以后的值。
相当于dp[i][j]是把a[i]变成b[j]再加上以前dp[i-1][k]的最小值。

#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
using namespace std;
const int N = 2005;
int n;
int a[N],b[N];
long long dp[N][N];
int main(){
  while(~scanf("%d",&n)){

    for (int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        b[i]=a[i];
    }
    sort(b+1,b+1+n);
    for (int i=1;i<=n;i++){
       long long mn=dp[i-1][1];
       for (int j=1;j<=n;j++)
       {
           mn=min(mn,dp[i-1][j]);
           dp[i][j]=abs(a[i]-b[j])+mn;
       }
    }
    long long ans=dp[n][1];
    for (int i=1;i<=n;i++){
        ans=min(ans,dp[n][i]);
    }
    printf("%lld\n",ans);
  }
  return 0;
}
View Code

猜你喜欢

转载自www.cnblogs.com/bluefly-hrbust/p/10386890.html