Mystia 的居酒屋 题解

· · 个人记录

### 做法一 全部输出 `impossib1e`,由于出题人凉心,本做法能得 $4$ 分。 ### 做法二 暴力搜索枚举每个木桩上放的圆盘种类,判断是否存在通路,时间复杂度 $\rm O\it( N^{M+{\rm1}}N^{\rm2})$,期望得分 $20$。 ```cpp #include<bits/stdc++.h> using namespace std; int ans,T,n,m,w,t,x[251],y[251],c[251],r[251],f[251]; inline double ddis(long long x,long long y,long long xx,long long yy) { return sqrt((x-xx)*(x-xx)+(y-yy)*(y-yy)); } void dfs(int pos,int use,int cost) { if(cost>=ans)return; if(w-y[pos]<=r[use]) { ans=cost; return; } for(int i=1; i<=n; i++) { if(f[i])continue; f[i]=1; for(int j=1; j<=m; j++) { if(pos) { if(ddis(x[i],y[i],x[pos],y[pos])<=r[use]+r[j]) dfs(i,j,cost+c[j]); } else { if(y[i]<=r[j]) dfs(i,j,cost+c[j]); } } f[i]=0; } } int main() { scanf("%d",&T); while(T--) { scanf("%d%d%d",&n,&m,&w); ans=123456789; for(int i=1; i<=n; i++)scanf("%d%d",&x[i],&y[i]); for(int i=1; i<=m; i++)scanf("%d%d",&r[i],&c[i]); memset(f,0,sizeof(f)); dfs(0,0,0); if(ans!=123456789)printf("%d\n",ans); else puts("impossib1e"); } } ``` ### 做法三 对于 $X_k$ 全相同的情况,所有木桩都在一条直线上,问题转化成一个一维问题,则我们可以用动态规划来解决这部分问题: $\rm dp_{\it i,j}$ 表示覆盖到第 $i$ 个木桩的第 $j$ 个圆盘时的最小花费,转移从小到大枚举木桩上圆盘的大小,可以加上前缀和优化。 $$\rm dp_{\it i,j}=\min(dp_{\it k,l}|\it Y_k+R_l≥Y_i-R_j+C_j)$$ 时间复杂度 $\rm O(\it N^{\rm2}M)$,不结合其他做法的情况下,期望得分 $20$。结合其他做法期望可以得到至少 $48$ 分。 其实这个动态规划做法比正解还要复杂,这里就不详细说明了。 ### 做法四 将本题转化为图论模型: 全图共 $N×M+2$ 个点,$0$ 号点表示起点,即 $y=0$;$N×M+1$ 号点表示终点,即 $y=W$。其他的点表示每个点每种圆盘的选择情况。若记为 $(i,j)$,则表示点 $i$ 上放第 $j$ 种圆盘。 建边: - 第一类边:点 $(i,j)$ 向点 $(p,q)$ 连边,当且仅当 $\rm dis_{\it i,p}≤\it R_j+R_q$,权值为 $C_q$。 - 第二类边:起点向 $(i,j)$ 连边,当且仅当 $Y_i≤R_j$,权值为 $R_j$。 - 第三类边:$(i,j)$ 向终点连边,当且仅当 $Y_i+R_j≥W$,权值为 $0$。 然后从起点向终点跑最短路就可以了。点数 $\rm O(\it NM)$,边数 $\rm O(\it N^{\rm2}M^{\rm2})$,期望得分 $48$。 ```cpp #include<cstdio> #include<queue> #include<cstring> #include<cmath> #include<map> using namespace std; priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q; int T,n,m,w,t,x[251],y[251],c[251],r[251],d[250*250+3],head[250*250+3],dis[250*250+3],cnt; bool f[50000000]; struct node { int to,val,nxt; } e[50000000]; inline int toNum(int i,int j) { return (i-1)*m+j; } inline double ddis(long long x,long long y,long long xx,long long yy) { return sqrt((x-xx)*(x-xx)+(y-yy)*(y-yy)); } inline void add(int xx,int yy,int zz) { e[++cnt].to=yy; e[cnt].val=zz; e[cnt].nxt=head[xx]; head[xx]=cnt; } int main() { scanf("%d",&T); while(T--) { memset(f,0,sizeof(f)); cnt=0; memset(head,0,sizeof(head)); scanf("%d%d%d",&n,&m,&w); for(int i=1; i<=n; i++)scanf("%d%d",&x[i],&y[i]); for(int i=1; i<=m; i++)scanf("%d%d",&r[i],&c[i]); for(int i=1; i<=n; i++) { for(int j=1; j<=m; j++) { if(y[i]<=r[j])add(0,toNum(i,j),c[j]); if(w-y[i]<=r[j])add(toNum(i,j),n*m+1,0); } } for(int i=1; i<=n; i++) { for(int j=1; j<=m; j++) { for(int ii=i+1; ii<=n; ii++) { for(int jj=1; jj<=m; jj++) { if(ddis(x[i],y[i],x[ii],y[ii])<=r[j]+r[jj]) { add(toNum(i,j),toNum(ii,jj),c[jj]); add(toNum(ii,jj),toNum(i,j),c[j]); } } } } } memset(d,0x3f,sizeof(d)); d[0]=0; q.push(make_pair(0,0)); while(!q.empty()) { t=q.top().second; q.pop(); if(f[t])continue; f[t]=1; for(int i=head[t]; i; i=e[i].nxt) { if(d[t]+e[i].val<d[e[i].to]) { d[e[i].to]=d[t]+e[i].val; q.push(make_pair(d[e[i].to],e[i].to)); } } } if(d[n*m+1]>1e9)puts("impossib1e"); else printf("%d\n",d[n*m+1]); } } ``` ### 做法五 我们发现边的数量太大了,所以考虑减少边的数量。 首先我们去掉一些“又小又贵”肯定没用的圆盘,即盘子 $i$ 与 $j$,若$R_i≤R_j$ 且 $C_i≥C_j$,便可去掉盘子 $i$。 去盘操作我们可以直接两重循环来进行,因为经过观察,当盘数为 $2×10^4$,$R$ 与 $C$ 分别小于 $200$ 时,最后留下的盘子不会超过 $200$ 个,也就是绝大多数第二重循环都被 `break` 掉了,因此不会消耗太多时间。 我们将剩下的盘子排序,这些盘子半径递增,价格也是递增的。 对于第一类边,对于以下两条边: - $(i,j)$ 到 $(p,k_1)

其中 k_1<k_2i≠p

经过观察,我们没有必要连 (p,k_2) 的边,只连 (i,j)(p,k_1) 的边就好。也就是说 (i,j)(p,k) 连边,只连 k 最小的那一条。我们可以二分找出连哪一条。

这样还不够,我们必须额外加上一类边以弥补少连的那部分:即从 (i,j)(i,j+1) 连权值为 C_{j+1}-C_j 的边。可以简单地理解为允许我们在过桥的时候实现“换盘”的操作。

这样之前省掉的从 (i,j)(p,k_2) 的边就可以由从 (i,j)(p,k_1) 的边,加上一系列从 (p,k_3)(p,k_3+1) 的边替代了。

预处理加边的复杂度 \rm O(\it N^{\rm2}M),点数还是 \rm O(\it NM),边数 \rm O(\it N^{\rm2}M)

最后跑最短路,总复杂度为 \rm O(\it N^{\rm2}M \log{(N^{\rm2}M)}),本题开启 O2 优化且时限为两秒,期望得分 100 分。

#include<cstdio>
#include<queue>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<map>
using namespace std;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
int T,n,m,tpm,w,t,tp,x[101],y[101],d[20102],head[20102],dis[20102],cnt,toNumCnt;
bool f[20102];
map<pair<int,int>,int>M;
struct node {
    int to,val,nxt;
} e[1000001];
inline int read(){
    int o=0;
    char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9'){
        o=o*10+ch-'0';
        ch=getchar();
    }
    return o;
}
struct Node {
    int r,c;
    bool invalid;
} p[20102];
inline bool cmp(Node xx,Node yy) {
    if(xx.invalid!=yy.invalid)return xx.invalid<yy.invalid;
    return xx.r<yy.r;
}
int binarySearch(int l,int r,double target) {
    if(l>r)return 0;
    if(l==r) {
        if(p[l].r>=target)return l;
        else return 0;
    }
    int mid=((l+r)>>1);
    if(p[mid].r==target)return mid;
    if(p[mid].r>target)return binarySearch(l,mid,target);
    return binarySearch(mid+1,r,target);
}
inline int toNum(int i,int j) {
    pair<int,int>pp=make_pair(i,j);
    if(M.find(pp)==M.end()) {
        M.insert(make_pair(pp,++toNumCnt));
        return toNumCnt;
    }
    return M[pp];
}
inline double ddis(long long X,long long Y,long long xx,long long yy) {
    return sqrt((X-xx)*(X-xx)+(Y-yy)*(Y-yy));
}
inline void add(int xx,int yy,int zz) {
    e[++cnt].to=yy;
    e[cnt].val=zz;
    e[cnt].nxt=head[xx];
    head[xx]=cnt;
}
int main() {
    T=read();
    while(T--) {
        memset(f,0,sizeof(f));
        cnt=0;
        toNumCnt=1;
        M.clear();
        memset(head,0,sizeof(head));
        memset(p,0,sizeof(p));
        n=read();
        m=read();
        w=read();
        tpm=m;
        for(int i=1; i<=n; i++){
            x[i]=read();
            y[i]=read();
        }
        for(int i=1; i<=m; i++){
            p[i].r=read();
            p[i].c=read();
        }
        for(int i=1; i<=m; i++) {
            for(int j=1; j<=m; j++) {
                if(p[i].r<=p[j].r&&p[i].c>=p[j].c&&i!=j&&!p[j].invalid) {
                    p[i].invalid=true;
                    tpm--;
                    break;
                }
            }
        }
        sort(p,p+m+1,cmp);
        m=tpm;
        for(int i=1; i<=n; i++) {
            tp=binarySearch(1,m,y[i]);
            if(tp)add(0,toNum(i,tp),p[tp].c);
            tp=binarySearch(1,m,w-y[i]);
            if(tp)for(int j=tp;j<=m;j++)add(toNum(i,j),1,0);
        }
        for(int i=1; i<=n; i++) {
            for(int j=1; j<=m; j++) {
                if(j!=m)add(toNum(i,j),toNum(i,j+1),p[j+1].c-p[j].c);
                for(int ii=1; ii<=n; ii++) {
                    if(ii==i)continue;
                    tp=binarySearch(1,m,ddis(x[i],y[i],x[ii],y[ii])-p[j].r);
                    if(tp)add(toNum(i,j),toNum(ii,tp),p[tp].c);
                }
            }
        }
        memset(d,0x3f,sizeof(d));
        d[0]=0;
        q.push(make_pair(0,0));
        while(!q.empty()) {
            t=q.top().second;
            q.pop();
            if(f[t])continue;
            f[t]=1;
            for(int i=head[t]; i; i=e[i].nxt) {
                if(d[t]+e[i].val<d[e[i].to]) {
                    d[e[i].to]=d[t]+e[i].val;
                    q.push(make_pair(d[e[i].to],e[i].to));
                }
            }
        }
        if(d[1]>1e9)puts("impossib1e");
        else printf("%d\n",d[1]);
    }
}