sduacm2016级暑假集训 最短路&最小生成树

比赛地址
密码:acm2016

A - 畅通工程 (HDU1863)

题目链接
【题意】
问题意中给出的图是否能求出一颗最小生成树,若不能求出输出?否则输出最小生成树权值。
【分析】
简单的最小生成树模板题。
【Code】

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define Mx 1000+10
int cost[Mx][Mx],dis[Mx];
bool vis[Mx];
int n,m;
int Prim(int s)
{
    int sum=0;
    for (int i=1; i<=n; i++) dis[i]=cost[s][i];
    memset(vis,0,sizeof(vis));
    vis[s]=true;
    for (int i=1; i<n; i++)
    {
        int mi=INF,k;
        for (int j=1; j<=n; j++)
            if(!vis[j]&&dis[j]<mi)
            {
                mi=dis[j];
                k=j;
            }
        if (mi>=INF) {
            return -1;
        }
        sum+=mi;
        vis[k]=true;
        for(int j=1; j<=n; j++)
            if(!vis[j]&&cost[k][j]<dis[j])
               dis[j]=cost[k][j];
    }
    return sum;
}
int main()
{
    int x,y,z;
    while(scanf("%d%d",&m,&n)!=EOF && m)
    {
        memset(cost,INF,sizeof(cost));
        for(int i=1; i<=m; i++){
            scanf("%d%d%d",&x,&y,&z);
            cost[x][y] = cost[y][x] = z;
        }
        int ans = Prim(1);
        if (ans == -1) puts("?");
        else printf("%d\n",ans);
    }
    return 0;
}

B - 畅通工程再续 (HDU1875)

题目链接
【题意】
给出一个图,任意两个点之间有≥10,≤1000的边即可视为有边相连,边权为两点的距离,求图的最小生成树,若不存在输出”oh!”
【分析】
建图求最小生成树,模板题。
【Code】

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define Mx 1000+10
double cost[Mx][Mx],dis[Mx];
bool vis[Mx];
int n,m;
struct node{
    double x,y;
} p[Mx];
double Prim(int s)
{
    double sum=0;
    for (int i=1; i<=n; i++) dis[i]=cost[s][i];
    memset(vis,0,sizeof(vis));
    vis[s]=true;
    for (int i=1; i<n; i++)
    {
        double mi=INF;
        int k;
        for (int j=1; j<=n; j++)
            if(!vis[j]&&dis[j]<mi)
            {
                mi=dis[j];
                k=j;
            }
        if (mi+1e-9>=INF) {
            return -1.0;
        }
        sum+=mi;
        vis[k]=true;
        for(int j=1; j<=n; j++)
            if(!vis[j]&&cost[k][j]<dis[j])
               dis[j]=cost[k][j];
    }
    return sum;
}
double dist(int i,int j)
{
    return sqrt((p[i].x-p[j].x)*(p[i].x-p[j].x) + (p[i].y-p[j].y)*(p[i].y-p[j].y));
}
int main()
{
    int Case;
    scanf("%d",&Case);
    while(Case--)
    {
        scanf("%d",&n);
        for (int i=1;i<=n;i++) scanf("%lf%lf",&p[i].x,&p[i].y);
        for (int i=1;i<=n;i++)
            for (int j=i+1;j<=n;j++){
                double d = dist(i,j);
                if (d<10||d>1000) {
                    cost[j][i] = cost[i][j] = INF;
                    continue;
                }
                cost[i][j] = cost[j][i] = d;
            }
        double ans = Prim(1);
        if (ans<0) puts("oh!");
        else printf("%.1f\n",ans*100);
    }
    return 0;
}

C - Highways (POJ2485)

题目链接
【题意】
给定一个图,求该图最小生成树中的最长边。
【分析】
模板题,不多说。
【Code】

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define Mx 1000+10
int cost[Mx][Mx],dis[Mx];
bool vis[Mx];
int n;
int Prim(int s)
{
    int ans=0;
    for (int i=1; i<=n; i++) dis[i]=cost[s][i];
    memset(vis,0,sizeof(vis));
    vis[s]=true;
    for (int i=1; i<n; i++)
    {
        int mi=INF,k;
        for (int j=1; j<=n; j++)
            if(!vis[j]&&dis[j]<mi)
            {
                mi=dis[j];
                k=j;
            }
        ans = max(ans,mi);
        vis[k]=true;
        for(int j=1; j<=n; j++)
            if(!vis[j]&&cost[k][j]<dis[j])
               dis[j]=cost[k][j];
    }
    return ans;
}
int main()
{
    int Case;
    scanf("%d",&Case);
    while(Case--)
    {
        scanf("%d",&n);
        for(int i=1; i<=n; i++)
            for(int j=1; j<=n; j++)
                scanf("%d",&cost[i][j]);
        printf("%d\n",Prim(1));
    }
    return 0;
}

D - 确定比赛名次 (POJ1285)

题目链接
【题意】
求满足条件的队伍顺序,符合条件的排名可能不是唯一的,此时要求输出时编号小的队伍在前
【分析】
拓扑排序+优先队列,优先编号小的即可。
顺序输出即可。
【Code】

#include<cstring>
#include<cstdio>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
typedef long long LL;
#define INF 0x3f3f3f3f

const int MAX_N = 500 + 10;
const int MAX_M = 10000 + 10;
const double PI = acos(-1.0);
const double EPS = 1e-9;


vector<int> vec[MAX_N];
int d[MAX_N],p[MAX_N];
bool v[MAX_N];
int n,m,cnt;
struct cmp{
     bool operator()(int &a, int &b){
         return a > b;
     }
};

void toposort()
{
    priority_queue<int,vector<int>,cmp> pq;
    for (int i=1;i<=n;i++)
        if (d[i] == 0)
            pq.push(i);
    while (!pq.empty()){
        int x = pq.top();
        pq.pop();
        p[++cnt] = x;
        for (int i=0;i<vec[x].size();i++){
            int u = vec[x][i];
            d[u]--;
            if (d[u]==0) pq.push(u);
        }
    }
}

int main()
{
    while (~scanf("%d%d",&n,&m)){
        for (int i=0;i<=n;i++) vec[i].clear();
        memset(v,0,sizeof(v));
        memset(d,0,sizeof(d));
        int a, b;
        for (int i=0;i<m;i++){
            scanf("%d%d",&a,&b);
            d[b]++;
            vec[a].push_back(b);
        }
        cnt = 0;
        toposort();
        for (int i=1;i<n;i++) printf("%d ",p[i]);
        printf("%d\n",p[n]);
    }
}

E - Test for Job (POJ3249)

题目链接
【题意】
求图中的一条路径,该路径的点权值和最大。
图可能不联通。
【分析】
拓扑排序+DP。
拓扑排序按节点度数入队,保证更新过的点不会再次被更新,即没有后效性,然后就可以DP了。
f[i]代表到i点的最大的点权值和,那么f[i] = max(f[i],f[x] + a[i]).
f[x]为可以到该点的点。
【Code】

#include<cstring>
#include<cstdio>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
typedef long long LL;
#define INF 0x3f3f3f3f

const int MAX_N = 100000 + 10;
const int MAX_M = 1000000 + 10;
const double PI = acos(-1.0);
const double EPS = 1e-9;


vector<int> vec[MAX_N];
int d[MAX_N],p[MAX_N],f[MAX_N],a[MAX_N];
bool v[MAX_N];
int n,m,cnt;

void toposort()
{
   // priority_queue<int,vector<int>,greater<int> > pq;
    queue<int> pq;
    for (int i=1;i<=n;i++)
        if (d[i] == 0) {
            pq.push(i);
            f[i] = a[i];
        }
    while (!pq.empty()){
        int x = pq.front();//pq.top();
        pq.pop();
        for (int i=0;i<vec[x].size();i++){
            int u = vec[x][i];
            f[u] = max(f[u],f[x]+a[u]);
            //printf("%d %d %d\n",x,u,f[u]);
            d[u]--;
            if (d[u]==0) pq.push(u);
        }
    }
}

int main()
{
    while (~scanf("%d%d",&n,&m)){
        for (int i=0;i<=n;i++) vec[i].clear();
        memset(d,0,sizeof(d));
        memset(f,-INF,sizeof(f));
        int c, b;
        for (int i=1;i<=n;i++) scanf("%d",&a[i]);
        for (int i=0;i<m;i++){
            scanf("%d%d",&c,&b);
            d[b]++;
            vec[c].push_back(b);
        }
        cnt = 0;
        toposort();
        int ans = -INF;
        for (int i=1;i<=n;i++)
        if (!vec[i].size()){
            ans = max(ans,f[i]);
        }
        printf("%d\n",ans);
    }
}

F - 湫湫系列故事——设计风景线 (HDU4514)

题目链接
【题意】
问给定的图是否能构成环,如果能构成输出YES,否则输出该图中最长的一条边。
【分析】
首先用并查集判断该图是否能构成环,能就直接输出YES。
否则的话对于每个联通块,求该联通块中的最长边,最后取max即可。
求最长边的过程就是从该联通块中任意一点出发,求它到其他点的最长距离,记录这个点,然后从这个点出发,求该联通块中这个点到其他点的最长距离。
至于为什么这样就能得出最长边,是很显而易见的,就不多解释了。
【Code】

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<stack>
#include<queue>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long LL;

const double PI = acos(-1.0);
const double EPS = 1e-9;
const int MAX_N = 100000 + 10;
const int MAX_M = 1000000 + 10;

struct node{
    int from,to,v,next;
} p[MAX_M];

int head[MAX_N],d[MAX_N],f[MAX_N];
bool v[MAX_N],vis[MAX_N];
int n,m,cnt,k,tmp,ans;

void add(int u,int v,int w)
{
    p[cnt].from= u;
    p[cnt].to= v;
    p[cnt].v= w;
    p[cnt].next= head[u];
    head[u]= cnt++;
}
int findx(int x)
{
    if (f[x] == x) return x;
    return f[x] = findx(f[x]);
}
void unite(int x,int y)
{
    x = findx(x);
    y = findx(y);
    f[y] = x;
}

void dfs(int now,int cost)
{
    if (cost > tmp){
        tmp =cost;
        k = now;
    }
    v[now] = true;
    vis[now] = true;
    for (int i=head[now];i!=-1;i=p[i].next){
        int t = p[i].to;
        if (!vis[t]){
            dfs(t,cost+p[i].v);
        }
    }
}
int main()
{
    int x, y, z;
    while (~scanf("%d%d",&n,&m)){
        for (int i=1;i<=n;i++) f[i] = i;
        bool flag = false;
        cnt = 0;
        memset(head,-1,sizeof(head));
        for (int i=0;i<m;i++){
            scanf("%d%d%d",&x,&y,&z);
            if (flag) continue;
            int xx = findx(x), yy = findx(y);
            if (xx == yy) flag = true;//一开始直接 x = findx(x),y = findx(y)导致后面建边出错,GG
            else unite(x,y);
            add(x,y,z);
            add(y,x,z);
        }
        if (flag) printf("YES\n");
        else {
            ans = -1;
            memset(v,0,sizeof(v));
            for (int i=1;i<=n;i++){
                if (!v[i]){
                    tmp = -1;
                    memset(vis,0,sizeof(vis));
                    dfs(i,0);
                    ans = max(tmp,ans);
                    memset(vis,0,sizeof(vis));
                    tmp = -1;
                    dfs(k,0);
                    ans = max(tmp,ans);
                }
            }
            printf("%d\n",ans);
        }
    }
}

G - Til the Cows Come Home (POJ2387)

题目链接
【题意】
求1到N的最短路
【分析】
裸的最短路,Dijkstra 或 Spfa都可以。
【Code】

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long LL;

const double PI = acos(-1.0);
const double EPS = 1e-9;
const int MAX_N = 10000;

struct node{
    int from,to,v,next;
} p[MAX_N];
int head[MAX_N],d[MAX_N];
bool v[MAX_N];
int n,m,cnt = 0;
void add(int u,int v,int w)
{
    p[cnt].from= u;
    p[cnt].to= v;
    p[cnt].v= w;
    p[cnt].next= head[u];
    head[u]= cnt++;
}
void spfa(int s)
{
    for(int i=0;i<MAX_N;i++) d[i]=INF;
    memset(v,false,sizeof(v));
    queue<int> q;
    d[s]=0;v[s]=true;
    q.push(s);
    while (!q.empty())
    {
        int u=q.front();
        q.pop();
        v[u]=true;
        for (int i=head[u];i!=-1;i=p[i].next)
        {
            int t=p[i].to;
            if (d[t]>d[u]+p[i].v)
            {
                d[t]=d[u]+p[i].v;
                if (!v[t])
                {
                    v[t]=true;
                    q.push(t);
                }
            }
        }
        v[u]=false;
    }
}

int main()
{
    int x,y,z;
    scanf("%d%d",&m,&n);
    cnt=0;
    memset(head, -1, sizeof(head));
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }
    spfa(1);
    printf("%d\n",d[n]);
}

H - 最短路径问题 (HDU3790)

题目链接
【题意】
给你n个点,m条无向边,每条边都有长度d和花费p,给你起点s终点t,要求输出起点到终点的最短距离及其花费,如果最短距离有多条路线,则输出花费最少的。
【分析】
模板题,判断条件改一下就好。
【Code】

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long LL;

const double PI = acos(-1.0);
const double EPS = 1e-9;
const int MAX_N = 400000 + 10;

struct node{
    int from,to,v,w,next;
} p[MAX_N];
int head[MAX_N],d[MAX_N],c[MAX_N];
bool v[MAX_N];
int n, m, cnt = 0;
void add(int u,int v,int w,int x)
{
    p[cnt].from= u;
    p[cnt].to= v;
    p[cnt].v= w;
    p[cnt].w = x;
    p[cnt].next= head[u];
    head[u]= cnt++;
}
void spfa(int s)
{
    for (int i=0;i<=n;i++) d[i]=INF;
    for (int i=0;i<=n;i++) c[i]=INF;
    memset(v,false,sizeof(v));
    queue<int> q;
    d[s]=0;c[s] = 0;
    v[s]=true;
    q.push(s);
    while (!q.empty())
    {
        int u=q.front();
        q.pop();
        v[u]=true;
        for (int i=head[u];i!=-1;i=p[i].next){
            int t = p[i].to;
            if (d[t] > d[u] + p[i].v){
                d[t] = d[u] + p[i].v;
                c[t] = c[u] + p[i].w;
                if (!v[t]){
                    v[t]=true;
                    q.push(t);
                }
            }else if (d[t] == d[u] + p[i].v){
                if (c[t] > c[u] + p[i].w){
                    c[t] = c[u] + p[i].w;
                    if (!v[t]){
                        v[t] = true;
                        q.push(t);
                    }
                }
            }
        }
        v[u]=false;
    }
}

int main()
{
    int x,y,z,w;
    while (~scanf("%d%d",&n,&m)&&(m||n)){
        cnt=0;
        memset(head, -1, sizeof(head));
        for (int i=1;i<=m;i++){
            scanf("%d%d%d%d",&x,&y,&z,&w);
            add(x,y,z,w);
            add(y,x,z,w);
        }
        int s,t;
        scanf("%d%d",&s,&t);
        spfa(s);
        printf("%d %d\n",d[t],c[t]);
    }
}

I - Candies (POJ3159)

题目链接
【题意】
给n个人派糖果,给出m组数据,每组数据包含A,B,c 三个数,
意思是A的糖果数比B少的个数不多于c,即B的糖果数 - A的糖果数<= c 。
最后求n 比 1 最多多多少糖果。
【分析】
这是一题典型的差分约束题。不妨将糖果数当作距离,把相差的最大糖果数看成有向边AB的权值,
我们得到 dis[B]-dis[A]<=w(A,B)。看到这里,我们联想到求最短路时的松弛操作,
即if(dis[B]>dis[A]+w(A,B), dis[B]=dis[A]+w(A,B)。
即是满足题中的条件dis[B]-dis[A]<=w(A,B),由于要使dis[B] 最大,
所以这题可以转化为最短路来求。
这题如果用SPFA 算法的话,则需要注意不能用spfa+queue 来求,会TLE ,而是用 spfa + stack。
【Code】

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<stack>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long LL;

const double PI = acos(-1.0);
const double EPS = 1e-9;
const int MAX_N = 30000 + 10;
const int MAX_M = 200000 + 10;

struct node{
    int from,to,v,next;
} p[MAX_M];
int head[MAX_N],d[MAX_N];
bool v[MAX_N];
int n,m,cnt = 0;
void add(int u,int v,int w)
{
    p[cnt].from= u;
    p[cnt].to= v;
    p[cnt].v= w;
    p[cnt].next= head[u];
    head[u]= cnt++;
}
void spfa(int s)
{
    for(int i=0;i<MAX_N;i++) d[i]=INF;
    memset(v,false,sizeof(v));
    stack<int> q;
    d[s]=0;v[s]=true;
    q.push(s);
    while (!q.empty())
    {
        int u=q.top();
        q.pop();
        v[u]=true;
        for (int i=head[u];i!=-1;i=p[i].next)
        {
            int t=p[i].to;
            if (d[t]>d[u]+p[i].v)
            {
                d[t]=d[u]+p[i].v;
                if (!v[t])
                {
                    v[t]=true;
                    q.push(t);
                }
            }
        }
        v[u]=false;
    }
}

int main()
{
    int x,y,z;
    scanf("%d%d",&n,&m);
    cnt=0;
    memset(head, -1, sizeof(head));
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        //add(y,x,z);
    }
    spfa(1);
    printf("%d\n",d[n]);
}

J - A Walk Through the Forest (HDU1142)

题目链接
【题意】
寻找一共有多少条符合题意的路。能够从点A走到点B的要求是:点A到终点的最短路 > 点B到终点的最短路。
【分析】
从终点出发,求每一个点的最短路,然后那些最短路的值记录起来,作为能否通过的判断条件。最后用记忆化搜索来搜索出一共多少条符合要求的路。
【Code】

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long LL;

const double PI = acos(-1.0);
const double EPS = 1e-9;
const int MAX_N = 1000 + 10;
const int MAX_M = 2000000 + 10;

struct node{
    int from,to,v,next;
} p[MAX_M];
int head[MAX_N],d[MAX_N];
int f[MAX_N];
int n,m,cnt = 0;
void add(int u,int v,int w)
{
    p[cnt].from= u;
    p[cnt].to= v;
    p[cnt].v= w;
    p[cnt].next= head[u];
    head[u]= cnt++;
}
void spfa(int s)
{
    for(int i=0;i<MAX_N;i++) d[i]=INF;
    memset(f,0,sizeof(f));
    queue<int> q;
    d[s]=0;f[s]=1;
    q.push(s);
    while (!q.empty())
    {
        int u=q.front();
        q.pop();
        f[u]=1;
        for (int i=head[u];i!=-1;i=p[i].next)
        {
            int t=p[i].to;
            if (d[t]>d[u]+p[i].v)
            {
                d[t]=d[u]+p[i].v;
                if (!f[t])
                {
                    f[t]=1;
                    q.push(t);
                }
            }
        }
        f[u]=0;
    }
}
int dfs(int now)
{
    //printf("%d  %d\n",now,f[now]);
    if (f[now] != -1) return f[now];
    if (now == 2) return 1;
    f[now] = 0;
    for (int i=head[now];i!=-1;i=p[i].next)
    if (d[p[i].to]<d[now]){
         //printf("%d\n",p[i].to);
         f[now] += dfs(p[i].to);
    }
    return f[now];
}

int main()
{
    int x,y,z;
    while (~scanf("%d",&n)&&n){
        scanf("%d",&m);
        cnt=0;
        memset(head, -1, sizeof(head));
        for (int i=1;i<=m;i++){
            scanf("%d%d%d",&x,&y,&z);
            add(x,y,z);
            add(y,x,z);
        }
        spfa(2);
        //printf("%d\n",d[2]);
        memset(f,-1,sizeof(f));
        cnt = dfs(1);
        printf("%d\n",cnt);
    }
}

K - Arbitrage (HDU1217)

题目链接
【题意】
在每种钱币间进行各种交换,最后换回自己如果能赚,那么就Yes,否则No
【分析】
以汇率为边,因为是实数,所以有可能为负,用SPFA处理。
然后因为读入的是字符串,所以需要用map处理一下。
【Code】

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long LL;

const double PI = acos(-1.0);
const double EPS = 1e-9;
const int MAX_N = 1000 + 10;
const int MAX_M = 2000000 + 10;

struct node{
    int from,to,next;
    double v;
} p[MAX_M];

int head[MAX_N],t[MAX_N];
bool v[MAX_N];
double d[MAX_N];

int n,m,cnt = 0;
map<string,int> c;

void add(int u,int v,double w)
{
    p[cnt].from= u;
    p[cnt].to= v;
    p[cnt].v= w;
    p[cnt].next= head[u];
    head[u]= cnt++;
}
bool spfa(int s)
{
    memset(d,0,sizeof(d));
    memset(v,false,sizeof(v));
    memset(t,0,sizeof(t));
    queue<int> q;
    d[s]=1; v[s]=true;
    q.push(s);
    while (!q.empty())
    {
        int u=q.front();
        q.pop();
        v[u] = true;
        //printf("%d\n",head[u]);
        for (int i=head[u];i!=-1;i=p[i].next)
        {
            int t=p[i].to;
            //printf("%d --> %d\n",u,t);
            if (d[t] < d[u] * p[i].v){
                d[t]=d[u] * p[i].v;
                if (d[s] > 1.0) return true;
                if (!v[t]){
                    v[t]=true;
                    q.push(t);
                }
            }
        }
        v[u] = false;
    }
    return false;
}
int main()
{
    int Case = 0;
    string st,ed;
    double df;
    while (~scanf("%d",&n) && n){
        for (int i=1;i<=n;i++){
            cin>>st;
            c[st] = i;
        }
        scanf("%d",&m);
        memset(head,-1,sizeof(head));
        cnt = 0;
        for (int i=0;i<m;i++){
            cin>>st>>df>>ed;
            add(c[st],c[ed],df);
        }
        bool flag = false;
        for (int i=1;i<=n;i++)
        if (spfa(i)){
            flag = true;
            break;
        }
        printf("Case %d: %s\n",++Case,flag?"Yes":"No");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值