GMOJ3860地壳运动

Posted stoorz

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GMOJ3860地壳运动相关的知识,希望对你有一定的参考价值。

题目

题目链接:https://gmoj.net/senior/#main/show/3860
(n)个点,(m)条边,每一条边可以用((x,y,u,v))表示,表示一条连接(x,y)的边的长度为(k_1x+k_2y)。接下来(q)组询问,每次询问给出(k_1,k_2),求此时的最小生成树。
(nleq 35,mleq 25000,qleq 200000)

思路

写的是暴力(O(m^2+qn^2))的算法。鉴于这道题时限(5s)(GMOJ)很快,而且复杂度远远跑不满,这样的暴力算法是可以过的。
先把一条边的长度(=k_1x+k_2y)全部除以(k_1),变成(x+frac{k_2}{k_1}y)。显然这样两条边之间的大小关系不变。
我们考虑连接两个点(x,y)的所有边,求出(k1,k2)在什么区间内时满足这条边是连接(x,y)的所有边中最小的。
如果两条边((x,y,u_1,v_1)(x,y,u_2,v_2))满足前者比后者短,那么

  • 如果(u_1leq u_2)(v_1leq v_2),那么(k_1,k_2)取任意正数均成立。
  • 如果(u_1<u_2,v_1>v_2),那么有(u_1+v_1frac{k_2}{k_1}<u_2+v_2frac{k_2}{k_1}),即(frac{k_2}{k_1}<frac{u_1-u_2}{v_2-v_1})
  • 如果(u_1>u_2,v_1<v_2),那么同理有(frac{k_2}{k_1}>frac{u_1-u_2}{v_2-v_1})
    那么这样就可以在均摊(O(m^2))的时间复杂度内求出(k1,k2)在什么区间内时满足这条边是连接(x,y)的所有边中最小的。

接下来我们把询问按(frac{k_2}{k_1})从大到小排序,然后枚举两个点(x,y),利用上文求出来的对应区间,指针扫描出最短的路,然后跑(Prim)即可。
千万注意求最小生成树不能使用(Kruskal),因为(Kruskal)需要将道路长度排序,这样就多了一个(log (n^2))的复杂度,使用(Prim)可以稳定(O(n^2))
时间复杂度(O(m^2+qn^2))

代码

#include <cstdio>
#include <cctype>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=41,M=25011,Q=200011;
int n,m,T,tot,pos[N][N],father[N],head[N][N],maxxx;
double k1,k2,ans[Q],dis[N][N];
bool vis[N];

inline int read()
{
    int d=0; char ch=getchar();
    while (!isdigit(ch)) ch=getchar();
    while (isdigit(ch)) d=(d<<3)+(d<<1)+ch-48,ch=getchar();
    return d;
}

struct node
{
    double u,v,l,r;
    int next;
}road[M]; 

struct Query
{
    int id;
    double k,k2,k1;
}ask[Q];

struct edge
{
    int from,to,u,v;
    double minn,maxn;
}e[M];

inline bool cmp1(edge x,edge y)
{
    if (x.from<y.from) return 1;
    if (x.from>y.from) return 0;
    return x.to<y.to;
}

inline bool cmp2(Query x,Query y)
{
    return x.k<y.k;
}

inline int find(int x)
{
    return x==father[x]?x:father[x]=find(father[x]);
}

inline double mst()
{
    double sum=0.0;
    for (int i=1;i<=n;i++)
        dis[0][i]=dis[1][i],vis[i]=0;
    dis[0][0]=1000000000000.0; vis[1]=1;
    for (int j=1;j<n;j++)
    {
        int Pos=0;
        for (int i=2;i<=n;i++)
            if (!vis[i] && dis[0][i]<dis[0][Pos]) Pos=i;
        vis[Pos]=1; sum+=dis[0][Pos];
        for (int i=1;i<=n;i++) 
            if (!vis[i]) dis[0][i]=min(dis[0][i],dis[Pos][i]);
    }
    return sum;
}

int main()
{
    n=read(); m=read(); T=read();
    for (register int i=1;i<=m;++i)
    {
        int x=read(),y=read(),u=read(),v=read();
        if (x>y) swap(x,y);
        e[i].from=x; e[i].to=y; e[i].u=u; e[i].v=v;
        e[i].maxn=1000000000000.0; e[i].minn=0.0; 
    }
    sort(e+1,e+1+m,cmp1);
    memset(head,-1,sizeof(head));
    for (register int l=1,r=1;r<=m;l=r=r+1)
    {
        while (e[r+1].from==e[l].from && e[r+1].to==e[l].to) r++;
        for (register int i=l;i<=r;++i)
            for (register int j=i+1;j<=r;++j)
            {
                if (e[i].u<=e[j].u && e[i].v<e[j].v)
                    e[j].minn=1000000000000.0,e[j].maxn=0.0;
                else if (e[i].u>=e[j].u && e[i].v>e[j].v)
                    e[i].minn=1000000000000.0,e[i].maxn=0.0;
                else if (e[i].v<=e[j].v && e[i].u<e[j].u)
                    e[j].minn=1000000000000.0,e[j].maxn=0.0;
                else if (e[i].v>=e[j].v && e[i].u>e[j].u)
                    e[i].minn=1000000000000.0,e[i].maxn=0.0;
                else if (e[i].u<e[j].u)
                {
                    e[i].maxn=min(e[i].maxn,1.0*(e[i].u-e[j].u)/(e[j].v-e[i].v));
                    e[j].minn=max(e[j].minn,1.0*(e[i].u-e[j].u)/(e[j].v-e[i].v));
                }
                else if (e[i].u>e[j].u)
                {
                    e[i].minn=max(e[i].minn,1.0*(e[i].u-e[j].u)/(e[j].v-e[i].v));
                    e[j].maxn=min(e[j].maxn,1.0*(e[i].u-e[j].u)/(e[j].v-e[i].v));
                }
            }
        for (register int i=l;i<=r;++i)
            for (register int j=i+1;j<=r;++j)
                if (e[i].minn<e[j].minn) swap(e[i],e[j]);
        for (register int i=l;i<=r;++i)
            if (e[i].maxn>=e[i].minn)
            {
                road[++tot].l=e[i].minn; road[tot].r=e[i].maxn;
                road[tot].u=e[i].u; road[tot].v=e[i].v;
                road[tot].next=head[e[i].from][e[i].to];
                head[e[i].from][e[i].to]=tot;
            }
    }
    for (int i=1;i<=n;i++)
        for (int j=i+1;j<=n;j++)
            pos[i][j]=head[i][j];
    for (register int i=1;i<=T;++i)
    {
        scanf("%lf%lf",&k1,&k2);
        ask[i].k=k2/k1; ask[i].id=i;
        ask[i].k1=k1; ask[i].k2=k2;
    }
    sort(ask+1,ask+1+T,cmp2);
    for (register int k=1;k<=T;k++)
    {
        for (int i=1;i<=n;i++)
            for (int j=1;j<=n;j++)
                dis[i][j]=1000000000000.0;
        tot=0; k1=ask[k].k1; k2=ask[k].k2;
        for (register int i=1;i<=n;++i)
            for (register int j=i+1;j<=n;++j)
            {
                for (;~pos[i][j];pos[i][j]=road[pos[i][j]].next)
                    if (road[pos[i][j]].r>=ask[k].k) break;
                if (road[pos[i][j]].l<=ask[k].k && road[pos[i][j]].r>=ask[k].k)
                    dis[i][j]=dis[j][i]=road[pos[i][j]].u*k1+road[pos[i][j]].v*k2;
            }
        ans[ask[k].id]=mst();
    }
    for (register int i=1;i<=T;++i)
        printf("%0.3lf
",ans[i]);
    return 0;
}

以上是关于GMOJ3860地壳运动的主要内容,如果未能解决你的问题,请参考以下文章

Bailian3860 unix纪元日期时间

Bailian3860 unix纪元日期时间

[ 模拟退火 ] bzoj3860 平衡点

GMOJ6293迷宫

GMOJ4015数列

GMOJ5057炮塔