[SDOI2017]天才黑客

一、题目

点此看题

二、解法

真的毒瘤,我 TM 搞了三个小时,调起来太 TM 爽了。

言归正传,这道题很容易想到一个最短路解法,我们把每条边拆成两个点,它们之间的边权值都是原来的边权,然后对于每个点(原图),我们把入点和出点暴力连边,边权为lcplcplcp(字典树上lcalcalca的深度1-1−1),这个做法可以拿到000分的高分,因为入点和出点相连时边数最多是m2m^2m2,然后时间复杂度就无力回天了。


考虑优化吧,现在的问题在于边数过多,我们能否减少一些边呢?

官方解法用到了权值是 字典树的lca算的 这一性质,我们可以用虚树的思想,我们先用字典树的dfndfndfn将入点和出点排序,然后有一个结论:lcp(al,ar)=mini=lr1lcp(ai,ai+1)lcp(a_l,a_r)=\min_{i=l}^{r-1} lcp(a_i,a_{i+1})lcp(al​,ar​)=mini=lr−1​lcp(ai​,ai+1​)

我们考虑到最短路会选择最短的,那么我们可以用dfndfndfn相邻的lcplcplcp表示区间的lcplcplcp,具体实现如下:

假设我们已经得到了一些入点和出点(这个例子编号的大小代表dfndfndfn的大小),我们先把相邻的入点和出点连边,边权为000:
[SDOI2017]天才黑客
我们要用dfndfndfn相邻的连边代表所有边,上图应该这样连:
[SDOI2017]天才黑客
总结一下方法,就是先排序,将相邻的点连边,设这两个点为(x,y)(x,y)(x,y),找到dfndfndfn小于等于xxx并且最右边的,dfndfndfn大于等于yyy的最左边的,将他们连边,边权为lcp(x,y)lcp(x,y)lcp(x,y)

上面举的例子只讲了入点小的向出点大的连的方法,事实上还有处理反向的(比如我们上面能处理1>21->21−>2,但是处理不了6>26->26−>2),所以我们还有把这个部分扩大两倍,用类似的方法再做一遍。

然后就直接跑dijkstradijkstradijkstra(事实证明我写不来dij,一直调不出来 ),贴个代码,有注释。

#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 20005;
const int M = 200005;
#define LL long long
#define inf (1ll<<60)
int read()
{
 int x=0,flag=1;char c;
 while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
 while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
 return x*flag;
}
int T,n,m,k,Index,tot,id,t,f[M],tmp[M],pos[M],dfn[N],dep[N],fa[N][20];
vector<int> rr[M],cc[M],g[M];LL dp[M];
struct edge
{
 int v,c,next;
 edge(int V=0,int C=0,int N=0) : v(V) , c(C) , next(N) {}
}e[M*10];
struct node
{
    int u;LL c;
    node(int U=0,LL C=0) : u(U) , c(C) {}
    bool operator < (const node &B) const
    {
        return c>B.c;
    }
};
void add(int u,int v,int c)
{
 e[++tot]=edge(v,c,f[u]),f[u]=tot;
}
void ins(int u,int v,int c,int d)
{//分两部分,要拆成4个点,13出,24入
 pos[++id]=d;add(t+1,t+2,c);add(t+1,t+4,c);add(t+3,t+2,c);add(t+3,t+4,c);
 rr[v].push_back(id);cc[u].push_back(id);t+=4;
}
int Abs(int x)
{
    return x>0?x:-x;
}
bool cmp(int a,int b)
{//dfn排序,加了abs是因为下面打了负数标记
    return dfn[pos[Abs(a)]]<dfn[pos[Abs(b)]];
}
void dfs(int u,int p)//字典树的预处理
{
    dfn[u]=++Index;
 fa[u][0]=p;
 dep[u]=dep[p]+1;
 for(int i=1;i<20;i++)
  fa[u][i]=fa[fa[u][i-1]][i-1];
 for(int i=0;i<g[u].size();i++)
        if(g[u][i]^p)
            dfs(g[u][i],u);
}
int lca(int u,int v)
{
 if(dep[u]<=dep[v]) swap(u,v);
 for(int i=19;i>=0;i--)
  if(dep[fa[u][i]]>=dep[v])
   u=fa[u][i];
 if(u==v) return u;
 for(int i=19;i>=0;i--)
  if(fa[u][i]^fa[v][i])
   u=fa[u][i],v=fa[v][i];
 return fa[u][0];
}
int get(int u,int v)//lcp
{
 return dep[lca(u,v)]-1;
}
void build(int x)
{
 if(rr[x].empty() || cc[x].empty()) return ;
    sort(rr[x].begin(),rr[x].end(),cmp);
 sort(cc[x].begin(),cc[x].end(),cmp);
 int len=0,tt;
 for(int i=rr[x].size()-1;i>0;i--) add(rr[x][i-1]*4-2,rr[x][i]*4-2,0),add(rr[x][i]*4,rr[x][i-1]*4,0);//相邻的接一起
 for(int i=cc[x].size()-1;i>0;i--) add(cc[x][i-1]*4-3,cc[x][i]*4-3,0),add(cc[x][i]*4-1,cc[x][i-1]*4-1,0);
 for(int i=0;i<cc[x].size();i++) tmp[++len]=-cc[x][i];//存下来排序,打负数标记
 for(int i=0;i<rr[x].size();i++) tmp[++len]=rr[x][i];
 sort(tmp+1,tmp+len+1,cmp);
 for(int t=1,i=0,j=0;t<len;t++)
    {
        if(tmp[t]<0) j++,tmp[t]*=-1;else i++;//用打的标记移动下标(i是入,j是出,都是未达到的)
        tt=get(pos[tmp[t]],pos[Abs(tmp[t+1])]);
        if(i!=0 && j!=cc[x].size()) add(rr[x][i-1]*4-2,cc[x][j]*4-3,tt);//分成两个部分
        if(j!=0 && i!=rr[x].size()) add(rr[x][i]*4,cc[x][j-1]*4-1,tt);
    }
}
void dijkstra()
{
    priority_queue<node> q;
    t++;//多建一个出发点,连1的所有出点
    for(int i=cc[1].size()-1;i>=0;i--) add(t,cc[1][i]*4-3,0),add(t,cc[1][i]*4-1,0);//一开始没打等号。。。
    memset(dp,0x3f,sizeof dp);
    dp[t]=0;
    q.push(node(t,0));
    while(!q.empty())
    {
        node t=q.top();q.pop();
        if(t.c>dp[t.u]) continue ;
        for(int i=f[t.u];i;i=e[i].next)
        {
            int v=e[i].v,c=e[i].c;
            if(dp[v]>dp[t.u]+c)
            {
                dp[v]=dp[t.u]+c;
                q.push(node(v,dp[v]));//这里写成c了,还得了25分,样例也没被卡。。。
            }
        }
    }
    for(int i=2;i<=n;i++)
    {
        LL ans=inf;
        for(int j=0;j<rr[i].size();j++)
            ans=min(ans,dp[rr[i][j]*4]);
        printf("%lld\n",ans);
    }
}
int main()
{
 T=read();
 while(T--)
 {
  n=read();m=read();k=read();
  tot=id=t=Index=0;
  memset(f,0,sizeof f);
  for(int i=1;i<=k;i++) g[i].clear();
  for(int i=1;i<=n;i++) rr[i].clear(),cc[i].clear();
  for(int i=1;i<=m;i++)
  {
   int u=read(),v=read(),c=read(),d=read();
   ins(u,v,c,d);
  }
  for(int i=1;i<k;i++)
        {
            int u=read(),v=read();read();
            g[u].push_back(v);
            g[v].push_back(u);
        }
        dfs(1,0);
        for(int i=2;i<=n;i++)
            build(i);//不用建1,因为1直接出发即可,它的字串是最好的
        dijkstra();
 }
}
[SDOI2017]天才黑客[SDOI2017]天才黑客 C202044zxy 发布了192 篇原创文章 · 获赞 12 · 访问量 3247 私信 关注
上一篇:ZC序列 DMRS Hls 仿真学习


下一篇:@loj - 2004@ 「SDOI2017」硬币游戏