SWUST ACM 训练题部分题解 hdu1384 && hdu3666 && hdu 4786 &&uva 1395 && uva 1151

来源:互联网 发布:java.lang.instrument 编辑:程序博客网 时间:2024/05/16 23:38

训练题网址:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=99765#problem/A 密码:acm2015

B - Intervals (hdu1384)

题意:给你n个区间每个区间的范围为[l,r],让你确定num个整数,使这num个整数在第i个区间[li,ri]至少有Ci个共同的数。题目先给你一个数n,接下来n行告诉你三个数li,ri,Ci,输出num的最小值。n<=50000,0<=li,ri<=50000,1<=Ci<=ri-li+1;

分析:由于区间最大才到50000,在0到50000这些数中,我们用0表示不选这个数,1表示选择这个数,那么就可以用sum[i]表示在0~i之间有多少个1(直白的说就是选择了几个数,可以笼统看成0~i的距离),根据题中描述,可以得到以下关系:

(1) sum[bi]-sum[ai-1]>=Ci,其中ai,bi代表区间[ai,bi]

(2) 0<=sum[i]-sum[i-1]<=1

即:sum[i]-sum[i-1]>=0

       sum[i-1]-sum[i]>=-1

知道上面的不等式,就可以建图了,ai-1到bi有一条值为Ci的边,i到i-1有一条值为-1的边,i-1到i有一条值为0的边。

此题代码://由于此题没有环的情况,所以不用判环也行

#include<stdio.h>#include<string.h>#include<queue>#include<algorithm>#define maxn 50010#define inf 1e9using namespace std;int num,p[maxn],n;struct node{    int en,va,next;}E[maxn*10];void init(){    num=0;    memset(p,-1,sizeof(p));}void add(int st,int en,int va){    E[num].en=en;    E[num].va=va;    E[num].next=p[st];    p[st]=num++;}int dis[maxn];bool inq[maxn];void spfa(int st,int en){    for(int i=0;i<=en;i++)    {        dis[i]=-inf;        inq[i]=false;    }    queue<int> q;    q.push(st);    dis[st]=0;    inq[st]=true;    while(q.size())    {        int x=q.front();        q.pop();        inq[x]=false;        for(int i=p[x];i!=-1;i=E[i].next)        {            int y=E[i].en;            int len=dis[x]+E[i].va;            if(len>dis[y])            {                dis[y]=len;                if(!inq[y])                {                    q.push(y);                    inq[y]=true;                }            }        }    }}int main(){    while(scanf("%d",&n)==1)    {        init();        int st=inf,en=-1;//由于起点终点未知,所以要自己找出来        for(int i=1;i<=n;i++)        {            int a,b,c;            scanf("%d%d%d",&a,&b,&c);            a++,b++;///由于最小值可能为0,所以防止我的起点为负数            st=min(st,a-1);///加入最小值为minn,那么我们的起点为minn-1,所以直接记录a-1的最小值作为起点            en=max(en,b);            add(a-1,b,c);        }        for(int i=st;i<=en;i++)        {            add(i,i-1,-1);            add(i-1,i,0);        }        spfa(st,en);        printf("%d\n",dis[en]);    }    return 0;}

C - THE MATRIX PROBLEM (hdu 3666)

题意:给你一个n*m的矩阵,和一个区间上下界L,U,问你是否能找出n个数a1,a2,a3,......,an和m个数b1,b2,b3,......,bm,使得矩阵中的第i行的数乘以ai得到的值范围在[L,U]内,而且矩阵中第j列的数除以bj得到的值范围也在[L,U]内,能找到你就输出YES,否则NO。

输入第一行为n,m,L,U

接下来一个n*m的矩阵Aij

分析:做差分约束的题首先我们得找到一种关系(两个不定数相减大于等于或者小于等于某个值)。根据题意我们可以简单得到下面的关系:

(1) L<=Aij * ai <=U (第一个要求)

(2) L<=Aij / bj <=U (第二个要求)

看到上面两个式子,似乎和差分约束的不等式关系不符,但是我们可以通过取对数使其变成两数相加或者两数相减,通过取对数可以得到下面变形式:

(1) log(L) <= log(Aij) +log(ai) <=log(U)

(2) log(L) <= log(Aij) - log(bj) <=log(U)

哈哈哈,两式相加可以得到: 2*log(L) <= 2*log(Aij) +log(ai) -log(bj) <=2*log(U)

                            移项可得: 2*log(L)-2*log(Aij) <=log(ai) - log(bj) <= 2*log(U)-2*log(Aij)

那么就可以在i到j+n之间建边了,边长就是上式中的值,具体看下代码就是了,但是此题卡队列,用一般的方法入队次数大于点数判环会TLE,在这里可以用以下两种方法优化:

1. 如果点数为n,那么其中一个点的入队次数大于sqrt(n) 就有环;

2.所有点的入队次数大于k*n,那么有环,k一般为2.....

代码如下:

#include<stdio.h>#include<string.h>#include<math.h>#include<queue>#include<vector>#include<algorithm>#define maxn 50010#define inf 1e5using namespace std;int num,p[maxn],n,m;struct node{    int en,next;    double va;}E[maxn*10];void init(){    num=0;    memset(p,-1,sizeof(p));}void add(int st,int en,double va){    E[num].en=en;    E[num].va=va;    E[num].next=p[st];    p[st]=num++;}double dis[maxn];int cnt[maxn];bool inq[maxn];bool spfa(){    for(int i=0;i<=n+m;i++)    {        dis[i]=inf;        cnt[i]=0;        inq[i]=false;    }    queue<int> q;    q.push(1);    dis[1]=0.0;    cnt[1]++;    inq[1]=true;    while(q.size())    {        int x=q.front();        q.pop();        inq[x]=false;        for(int i=p[x];i!=-1;i=E[i].next)        {            int y=E[i].en;            double len=dis[x]+E[i].va;            if(len<dis[y])            {                dis[y]=len;                if(!inq[y])                {                    q.push(y);                    cnt[y]++;                    if(cnt[y]>(int)sqrt(n+m)) return false;                    inq[y]=true;                }            }        }    }    return true;}int main(){    double L,U;    while(scanf("%d%d%lf%lf",&n,&m,&L,&U)!=EOF)    {        init();        double l=2.0*log(L),u=2.0*log(U);        for(int i=1;i<=n;i++)        {            for(int j=1;j<=m;j++)            {                double x;                scanf("%lf",&x);                x=2.0*log(x);                add(j,i+m,u-x);                add(i+m,j,x-l);            }        }        if(spfa()) puts("YES");        else puts("NO");    }    return 0;}


F - Fibonacci Tree (hdu 4786 )

题意:给你一个图,图中边颜色为白色(1表示)或者黑色(0表示),问你是否能找出一棵树,使这棵树中白边的总数量的值为Fib数。点数n和变数m均为不大于1e5的数。

分析:这是最小生成树的变形题,用Kruskal求解,我们可以通过求两次最小生成树得到白边数量的上下限,再判断这个区间内有没有fib数即可,第一次我们以黑边为主排序求最小生成树,得到白边数量下限,再以白边为主排序求最小生成树得到白边数量上限。注意如果一开始图不连通,那么输出No。

代码如下:

#include<stdio.h>#include<string.h>#include<algorithm>#define inf 1e8using namespace std;int fa[110000];bool Fi[110000];struct node{    int st,en,co;}E[110000];int find(int x){    if(fa[x]==x) return x;    else return fa[x]=find(fa[x]);}bool cmp1(node a,node b)//以黑白为主排序{    return a.co<b.co;}bool cmp2(node a,node b)//以白边为主排序{    return a.co>b.co;}void init()//预处理fib{    memset(Fi,false,sizeof(Fi));    int a=1,b=2,c;    Fi[a]=Fi[b]=true;    while(1)    {        c=a+b;        if(c>100000) break;        Fi[c]=true;        a=b,b=c;    }}int main(){    int T;    scanf("%d",&T);    init();//预处理fib    for(int ca=1;ca<=T;ca++)    {        int n,m;        scanf("%d%d",&n,&m);        for(int i=1;i<=m;i++)            scanf("%d%d%d",&E[i].st,&E[i].en,&E[i].co);        int ans1=0,ans2=0;        sort(E+1,E+m+1,cmp1);///以黑边为主的最小生成树        for(int i=1;i<=n;i++) fa[i]=i;        for(int i=1;i<=m;i++)        {            int fx=find(E[i].st),fy=find(E[i].en);            if(fx!=fy)            {                if(E[i].co) ans1++;                fa[fx]=fy;            }        }        int fg=1,tem=find(1);        for(int i=2;i<=n;i++)        {            if(find(i)!=tem)            {                fg=0;                break;            }        }        if(!fg)        {            printf("Case #%d: No\n",ca);            continue;        }///这是为了判断图不连通的情况        sort(E+1,E+m+1,cmp2);//以白边为主的最小生成树        for(int i=1;i<=n;i++) fa[i]=i;        for(int i=1;i<=m;i++)        {            int fx=find(E[i].st),fy=find(E[i].en);            if(fx!=fy)            {                if(E[i].co) ans2++;                fa[fx]=fy;            }        }        fg=0;        for(int i=ans1;i<=ans2;i++)        {            if(Fi[i])            {                fg=1;                break;            }        }        if(fg) printf("Case #%d: Yes\n",ca);        else printf("Case #%d: No\n",ca);    }    return 0;}

G - Slim Span (uva 1395)

题意:给出一个n(n<=100)结点的图,求苗条度(最大边减最小边的值)尽量小的生成树,输出这个值。
分析:根据Kruskal算法,我们可以枚举最小边权,然后求最小生成树,就可以暴力算出最大边权减去最小边权的最小值。

代码:

#include<stdio.h>#include<string.h>#include<algorithm>#define inf 1e8using namespace std;int fa[110];int n,m;struct node{    int st,en,len;}E[110000];int find(int x){    if(fa[x]==x) return x;    else return fa[x]=find(fa[x]);}bool cmp(node a,node b){    return a.len<b.len;}int Kruskal(int pos){    for(int i=1;i<=n;i++) fa[i]=i;    int minn=E[pos].len,maxx=0;    for(int i=pos;i<=m;i++)    {        int fx=find(E[i].st),fy=find(E[i].en);        if(fx!=fy)        {            maxx=max(maxx,E[i].len);            fa[fx]=fy;        }    }    int fg=1,tem=find(1);///下面的代码判断图是否连通,不连通就不要这个答案,返回-1    for(int i=2;i<=n;i++)    {        if(find(i)!=tem)        {            fg=0;            break;        }    }    if(!fg) return -1;    return maxx-minn;}int main(){    while(scanf("%d%d",&n,&m),n||m)    {        for(int i=1;i<=m;i++)            scanf("%d%d%d",&E[i].st,&E[i].en,&E[i].len);        sort(E+1,E+m+1,cmp);        int ans=1e9;        int x=Kruskal(1);//先求一次看看图是否连通        if(x==-1)        {            puts("-1");            continue;        }        ans=min(ans,x);        for(int i=2;i<=m-n+2;i++)///因为最小生成树边数为n-1,那么少于n-1条边肯定没有最小生成树        {            int x=Kruskal(i);            if(x!=-1) ans=min(ans,x);        }        printf("%d\n",ans);    }    return 0;}

H - Buy or Build (UVA 1151)

题意:二维平面上有n个点,告诉你每个点的坐标,你的任务是让每个点都联通,那么,你可以新建一些边,每条边的费用为两点欧几里得距离的平方,另外还给你q(0<=q<=8)个套餐,如果你选择了第i个套餐,此套餐中的所有点将变为相互连通,第i个套餐费用为ci。

分析:看看q的范围就很容易想到二进制枚举选取的套餐是哪些,根据Kruskal算法可知,如果我们选择了第i个套餐,那么第i个套餐中的所有点的father可以全部统一到其中一个点。注意用long long

代码如下:

#include<stdio.h>#include<string.h>#include<vector>#include<algorithm>#define inf 1e8#define LL long longusing namespace std;vector<int> v[10];int w[10];int fa[1100];int n,m;struct node{    int st,en,len;}E[1100000];struct Node{    int x,y;}V[1100];int find(int x){    if(fa[x]==x) return x;    else return fa[x]=find(fa[x]);}bool cmp(node a,node b){    return a.len<b.len;}int get(Node a,Node b)//求花费{    return ((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));}LL Kruskal(){    LL ans=0;    for(int i=1;i<=m;i++)    {        int fx=find(E[i].st),fy=find(E[i].en);        if(fx!=fy)        {            ans+=E[i].len;            fa[fx]=fy;        }    }    return ans;}int main(){    int q,T;    scanf("%d",&T);    while(T--)    {        scanf("%d%d",&n,&q);        for(int i=0;i<q;i++) v[i].clear();        for(int i=0;i<q;i++)        {            int num,x;            scanf("%d%d",&num,&w[i]);            while(num--)            {                scanf("%d",&x);                v[i].push_back(x);            }        }        for(int i=1;i<=n;i++)            scanf("%d%d",&V[i].x,&V[i].y);        m=0;        for(int i=1;i<=n;i++)        {            for(int j=i+1;j<=n;j++)            {                m++;                E[m].st=i,E[m].en=j,E[m].len=get(V[i],V[j]);            }        }        sort(E+1,E+m+1,cmp);///只用排序一次就行了        for(int i=1;i<=n;i++) fa[i]=i;        LL ans=Kruskal();///预先处理出一个答案        for(int i=1;i<(1<<q);i++)        {            LL tem=0;///选择套餐所用的总花费            for(int j=1;j<=n;j++) fa[j]=j;            for(int j=0;j<q;j++)            {                if(!(i&(1<<j))) continue;                tem+=w[j];                int x=find(v[j][0]);                for(int k=1;k<v[j].size();k++)///把这些点放在一个连通块中,Kruskal在跑到这些边时就会忽略他们原来的费用,这也是排序一次的原因                {                    int y=find(v[j][k]);                    if(y!=x)                    {                        fa[y]=x;                    }                }            }            ans=min(ans,tem+Kruskal());///因为最小生成树跑出来的答案没包括套餐所需费用,所以加上就行        }        printf("%lld\n",ans);        if(T) printf("\n");    }    return 0;}



1 0
原创粉丝点击