ABC 422 E-G题解

E - Colinear

想到了随机枚举命中率很高,没想到真让我分析概率,开眼界了()

题目要求询问是否存在一条直线能够穿过一半所给出的点,对于任意两个点组成的一条直线,若这条直线符合要求,则选取到这两个点的概率为:

\[\frac {\frac{n+1}{2}}{n} \cdot \frac {\frac{n+1}{2}-1}{n-1} = \frac{n+1}{4n} > \frac{1}{4} \]

所以随机选取两点,该直线不是答案的概率差不多 \(\frac{3}{4}\),对答案的判断可以 \(O(n)\) 解决,所以我们随机选 \(T\) 次 失败率不会大于 \((\frac{3}{4})^{T}\) 多跑几组也能行。

点击查看代码
const int N=5e5+5;
int n,x[N],y[N];
int T=200;
void xpigeon(){
    rd(n);
    for(int i=1;i<=n;i++){
        rd(x[i],y[i]);
    }
    mt19937_64 rnd(time(0));
    for(int t=1;t<=T;t++){
        int i,j;
        do{
            i=rnd()%n+1,j=rnd()%n+1;
        }while(i==j);
        int a=y[i]-y[j];
        int b=x[j]-x[i];
        int c=x[i]*y[j]-x[j]*y[i];
        int tmp=0;
        for(int k=1;k<=n;k++) tmp+=(a*x[k]+b*y[k]+c==0);
        if(tmp*2>n){
            cout<<"Yes\n";
            cout<<a<<" "<<b<<" "<<c<<"\n";
            return ;
        }
    }
    cout<<"No\n"<<'\n';
}

F - Eat and Ride

以为秒了,结果还是我唐啊()
这种转化是怎么想到的....
好吧后来根据_kenma_的教学了解到,思路方向是:题意原有的贡献是无法直接计算的,想要求最短路还是要把贡献拆到每个点上变成可计算的形式。

给的图是一个无向图,所以很多方法都受到了限制,我们重新考虑 “体重” 带来的贡献,对于每一个终点 \(i\) ,我们把一路上“体重”的持续贡献拆到刚增加这一部分“体重”时的单点上,于是重新表述本题问题:

高桥开始了一次旅行,他有几张车票。

如果他有 \(n\) 张车票,那么每当他到达顶点 \(v\) 时,就会消耗 \(nW_v\) 单位的燃料。
每当他经过一条边时,他会释放一张门票。
通过适当地确定他的初始门票数量和路径,使他从到达顶点 \(1\) 到顶点 \(i\) 所消耗的燃料最小。

设一个点 \((u,n)\) 表示在点 \(v\) 时,可能的剩余车票数为 \(n\),其可以向所有可到达的 \((v,n-1)\) 连一条 \(nW_v\) 的边,根据这个标准建图跑 \(dij\) 即可解决本题,答案是 \((1,n)\)\((i,0)\) 的最短距离。

时间复杂度是 \(O((nm+n^2) \log {n})\) 其实有点难过,我们考虑一个更优一点的写法。

考虑设计状态 \(dp_{u,k}\) 表示到达点 \(u\) 还剩 \(k\) 张票的最小代价,转移只需要枚举出边到达点 \(v\) 并将车票减一再取 \(min\) 即可,最终车票为 \(0\) 时,每个点的 \(dp\) 值一定取到最小代价,因为可以将这个过程看作有向无环图的形式,故答案肯定是从最优的路径上一步步取得的。

时间复杂度来到 \(O(nm)\)

点击查看代码
const int inf=1e18;
int dp[5005][5005];
int n,m,w[5005];
vector<int> e[5005];
void xpigeon(){
    rd(n,m);
    for(int i=1;i<=n;i++){
        rd(w[i]);
    }
    for(int i=1,a,b;i<=m;i++){
        rd(a,b);
        e[a].pb(b);
        e[b].pb(a);
    }
    for(int i=2;i<=n;i++) dp[i][m]=1e18;
    for(int i=m;i;i--){
        for(int j=1;j<=n;j++) dp[j][i-1]=1e18;
        dp[1][i]=0;
        for(int j=1;j<=n;j++) dp[j][i]+=w[j]*i;
        for(int j=1;j<=n;j++){
            for(auto y:e[j]){
                dp[y][i-1]=min(dp[y][i-1],dp[j][i]);
            }
        }
    }
    dp[1][0]=0;
    for(int i=1;i<=n;i++) cout<<dp[i][0]<<'\n';
}

G - Balls and Boxes

官方题解太神秘,差点没敢改,卷积咋做我也不会,但是我学到了 link 的根号分治做法。
我操根号咋这么牛逼

第一问

球是相同的,可以直接 \(dp\)
考虑 \(dp\) 状态 \(f(i,j)\) 表示前 \(i\) 个盒子里共放了 \(j\) 个球的方案数。

\(O(n)\) 就能做。

第二问

相当于求:

\[\sum _{ax+by+cz=n} \frac{n!}{(ax)!(by)!(cz)!} \]

我们可以给出两种做法:

  1. 直接枚举 \(x,y\) 再得到 \(z\) 最后直接算贡献,时间复杂度 \(O(\frac{n^2}{ab})\)
  2. 可以先固定前两个盒子的分配方案数,这样可以用一个 \(dp\) 来做,设 \(f(i,x,y)\) 表示前 \(i\) 个球分配到两个盒子中,在模 \(a,b\) 的情况下分别等于 \(x,y\) 时的方案数,转移时考虑下一个球可以选择放到两者其中一个盒子里。最后再单独枚举放进第三个盒子里多少个,每次的贡献是 \(\binom{n}{cz} \cdot f(n-cz,0,0)\)。时间复杂度 \(O(nab)\)

考虑根号分治,当 \(ab \geq \sqrt n\) 用做法 \(1\),否则用做法 \(2\) ,时间复杂度变为 \(O(n \sqrt n)\)

posted @ 2025-09-07 21:04  香香的鸽子  阅读(59)  评论(4)    收藏  举报