SWUST ACM 訓練題部分題解 hdu1384 && hdu3666 && hdu 4786 &&uva 1395 && uva 1151

訓練題網址: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 1e9
using 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 1e5
using 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 1e8
using 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 1e8
using 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 long
using 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;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章