【Fortuna OJ】10/25 Contest / 题解

也许是这几天以来最毒瘤的一场比赛

naive 的瓶子

题目描述

众所周知,小 naive 有 $n$ 个瓶子,它们在桌子上排成一排。第 $i$ 个瓶子的颜色为 $c_i$,每个瓶子都有灵性,每次操作可以选择两个相邻的瓶子,消耗他们颜色的数值乘积的代价将其中一个瓶子的颜色变成另一个瓶子的颜色。

现在 naive 要让所以瓶子的颜色都一样,操作次数不限,但要使得操作的总代价最小。

输入输出

输入文件为 colour.in。
一个测试点内多组数据。
第一行,一个正整数 $T$,表示数据组数。

每组数据内:
第一行一个整数 $n$,为瓶子的个数。
第二行共 $n$ 个整数,第 $i$ 个整数为第 $i$ 个瓶子的颜色 $c_i$。

输出文件为 colour.out。
共 $T $行,每行一个整数,为最小的总代价。

思路

不多说,直接 $O(n^3)$ 大力 DP,看代码。

代码

#include <bits/stdc++.h>
#define ll long long
#define inf 1ll<<60
#define maxn 301
using namespace std;
ll f[maxn][maxn][maxn];
int n,T,a[maxn];
void chkmin(ll &x,ll y){x=x<y?x:y;}
void Solve(){
    memset(f,0x3f,sizeof(f));
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
        scanf("%d",a+i),f[i][i][i]=0;
    for (int len=1;len<n;len++){
        for (int i=1,j=len;j<=n;i++,j++){
            for (int k=i;k<=j;k++){
                int bx=(a[i-1]!=a[k]),by=(a[j+1]!=a[k]);
                if (i>1){
                    chkmin(f[k][i-1][j],f[k][i][j]+1ll*a[i-1]*a[k]*bx);
                    chkmin(f[i-1][i-1][j],f[k][i][j]+1ll*a[i-1]*a[k]*(j-i+1)*bx); 
                }
                if (j<n){
                    chkmin(f[k][i][j+1],f[k][i][j]+1ll*a[j+1]*a[k]*by);
                    chkmin(f[j+1][i][j+1],f[k][i][j]+1ll*a[j+1]*a[k]*(j-i+1)*by); 
                }
            }
        }
    }
    ll ans=inf;
    for (int i=1;i<=n;i++) chkmin(ans,f[i][1][n]);
    printf("%lld\n",ans);
}
int main(){
    freopen("colour.in","r",stdin);
    freopen("colour.out","w",stdout);
    scanf("%d",&T);
    while(T--) Solve();
}

naive 的图

题目描述

众所周知,小 naive 有一张 $n$ 个点,$m$ 条边的带权无向图。第 $i$ 个点的颜色为 $c_i$。$d(s, t)$ 表示从点 $s$ 到点 $t$ 的权值最小的路径的权值,一条路径的权值定义为路径上权值最大的边的权值。

求所有满足 $u < v, |c_u − c_v| ≥ L$ 的点对 $(u, v)$ 的 $d(u, v)$ 之和。

输入输出

输入文件为 graph.in。
第一行,三个整数 $n, m, L$,表示点数,边数和参数 $L$。
第二行,$n$ 个整数,第 $i$ 个数为第 $i$ 个点的颜色 $c_i$。接下来 $m$ 行,每行三个整数 $u_i, v_i, w_i$,描述了一条边。

输出文件为 graph.out。
共一行,一个整数,表示答案。

思路

可以发现原图的 $d(s, t)$ 等于最小生成树上的 $d(s, t)$,只有在最小生成树上的边才有贡献。我们按照长度递增将边加入生成树中,此时以这条边端点为根的两颗子树之间路径的权值就是这条边的长度。可以发现 $\sum \min(size(left \ child_i), size(right \ child_i)) = O(n\log_2 n)$,所以我们直接枚举更小的子树中的每个点,在另一个子树中询问满足题目要求的点的个数。发现这是个经典的双重限制查询,主席树可以很方便地解决这个问题。

代码

#include <bits/stdc++.h>
#define ll long long
#define maxn 500100
#define mid ((l+r)>>1)
using namespace std;
struct Edge{
    int from,to,val;
    Edge(int a=0,int b=0,int c=0){
        from=a,to=b,val=c;
    }
    bool operator < (const Edge &x) const{
        return val<x.val;
    }
}e[maxn],a[maxn];
int n,m,L,fa[maxn],size[maxn],c[maxn];
int root[maxn],ls[maxn<<6],rs[maxn<<6],tree[maxn<<6];
int cnt,tot,mn=INT_MAX,mx;
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
void kruskal(){
    sort(e+1,e+1+m);
    for (register int i=1;i<=n;++i) fa[i]=i;
    for (register int i=1;i<=m;++i){
        int fx=find(e[i].from),fy=find(e[i].to);
        if (fx==fy) continue;
        fa[fy]=fx,a[++tot]=e[i];
        if (tot==n-1) break;
    }
}
void update(int &p,int l,int r,int x){
    if (!p) p=++cnt;tree[p]++;
    if (l==r) return;
    if (x<=mid) update(ls[p],l,mid,x);
    else update(rs[p],mid+1,r,x);
}
int query(int l,int r,int L,int R,int p){
    if (!p||L>R) return 0;
    if (l>=L&&r<=R) return tree[p];
    int res=0;
    if (L<=mid) res+=query(l,mid,L,R,ls[p]);
    if (R>mid) res+=query(mid+1,r,L,R,rs[p]);
    return res;
}
vector<int>que[maxn];
int main(){
    freopen("graph19.in","r",stdin);
    freopen("graph.out","w",stdout);
    scanf("%d%d%d",&n,&m,&L);
    for (register int i=1;i<=n;++i)
        scanf("%d",c+i),
        mn=min(mn,c[i]),
        mx=max(mx,c[i]);
    for (register int i=1;i<=m;++i)
        scanf("%d%d%d",&e[i].from,&e[i].to,&e[i].val);
    sort(e+1,e+1+m);
    kruskal();
    for (register int i=1;i<=n;++i){
        fa[i]=i,size[i]=1;
        update(root[i],mn,mx,c[i]);
        que[i].push_back(i);
    }
    ll ans=0;
    for (register int i=1;i<n;++i){
        int x=find(a[i].from),y=find(a[i].to);
        if (size[x]>size[y]) swap(x,y);
        fa[x]=y,size[y]+=size[x];
        ll res=0;
        for (register int j=0;j<que[x].size();++j){
            int pos=que[x][j];
            res+=query(mn,mx,mn,c[pos]-L,root[y]);
            res+=query(mn,mx,c[pos]+L,mx,root[y]);
            if (!L) res-=query(mn,mx,c[pos],c[pos],root[y]);
        }
        ans+=res*a[i].val;
        for (register int j=0;j<que[x].size();++j){
            int pos=que[x][j];
            update(root[y],mn,mx,c[pos]);
            que[y].push_back(pos);
        }
    }
    printf("%lld\n",ans);
    return 0;
}

naive 的游戏

题目描述

众所周知,小 naive 有一个游戏。游戏地图是一个无限的一维数轴,游戏的目标是从起点s 到终点 t,除了起点其他每个点上都有小怪。有 n 条线段,每条线段上的相邻点之间有桥相连。

他有两种操作:

  1. 走到相邻的有桥相连的点上,因为上面有小怪,所以要消耗 1 的代价;
  2. 使用技能跳一跳,跳到距离为 L 的点上,由于把该点上的小怪踩死了,所以不消耗代价。

这个游戏有两种模式,简单和困难。

简单模式:任意点都为可停留点。
困难模式:只有在线段上出现过的点为可停留点,不可停留点不可到达。

现求从起点到终点所消耗的最小代价。

输入输出

输入文件为 game.in。
一个测试点内多组数据。
第一行,一个正整数 $T$,表示数据组数。
每组数据内:
第一行,五个整数 $type, n, L, s, t$,分别表示游戏模式,线段数,跳一跳的距离,起点坐标和终点坐标。$type = 0$ 表示为简单模式,$type = 1$ 表示为困难模式。
接下来 $n$ 行,每行两个正整数 $l_i, r_i$,表示线段 $[l_i, r_i]$。

输出文件为 game.out。
共 $T$ 行,每行为该组数据的答案。若不可达输出 $−1$。

思路

本题只适合口胡。

type = 0
把模 $L$ 意义下相同的点缩成一个点,那么就成了一个环。时间复杂度为 $O(nlog^2n)$。

type = 1
把模 $L$ 意义下相同的并且可达的点缩成一个点。有一个显然的结论,只有包含起点或终点或某条线段的端点的点在途中是具有决策意义的,也就是只保留这些点在图中即可。这样点数是 O(n) 的,边数也可以优化至 $O(n)$,跑最短路即可。

代码不放。整整 15k,好像没人愿意写,显然也包括我自己。