牛客挑战赛48 D-牛奶 题解

牛奶

https://ac.nowcoder.com/acm/contest/11161/D

大家应该普遍用的是州区划分那题的做法,毕竟这题求一个集合的贡献是很容易的,再套一个子集卷积优化递推就做完了。

这里提供一种不同的做法。假设求出来集合贡献的生成函数为 ,那么答案其实就是子集卷积意义下的

update: 被评论区大佬教育了,事实上这个等比数列不需要恰好求到第 项, 以上的不影响答案(并且 也不影响答案),所以可以写成 然后用求逆解决。

有一种常用的集合幂级数转化为形式幂级数的方法,也就是先用子集卷积的做法求一次FMT,设 表示做子集卷积的数组,那么对于一个 ,令 ,我们就可以对 这个形式幂级数求上面这个式子了,求出来再填回 数组内,逆变换回去就是答案。

求上面的式子需要多项式快速幂以及多项式求逆,这个存在 递推的小常数做法,不需要多项式全家桶板子。

然而事实上这个做法不仅难写,难调,速度也比另一种做法慢,我赛时兴致大发冲了一顿没调出来(赛时还得急急忙忙写另一种做法来补救,还因为没判重边WA穿了),赛后调出来了还得卡常……这个做法看着一乐就好了,以后比赛还是不建议搞这种奇怪的操作。

update: 换成一次求逆做法后速度接近另一种做法,并且也好写很多qwq……

时间复杂度 ,代码如下(现在是update后一次求逆的写法):

#include <bits/stdc++.h>
using namespace std;
#define maxn 300010
#define bin(x) (1<<(x))
#define mod 998244353

int n,m;
void reduce(int &x){x+=x>>31&mod;}
void FMT(int *f,int type=1){
    for(int mid=1;mid<bin(n);mid<<=1)
        for(int j=0;j<bin(n);j+=(mid<<1))
            for(int i=0;i<mid;i++){
                if(type)reduce(f[j+i+mid]+=f[j+i]-mod);
                else reduce(f[j+i+mid]-=f[j+i]);
            }
}
int ksm(int x,int y){int re=1;for(;(y&1?re=1ll*re*x%mod:0),y;y>>=1,x=1ll*x*x%mod);return re;}
int A[20];long long B[20];
void getinv(int *f,long long *g,int ln){
    g[0]=ksm(f[0],mod-2);
    for(int i=1;i<=ln;i++){
        g[i]=0;for(int j=1;j<=i;j++)
            g[i]+=1ll*f[j]*g[i-j]%mod;
        g[i]=1ll*(mod-g[i]%mod)*g[0]%mod;
    }
}
int cnt[maxn],F[20][maxn],G[20][maxn];
void solve(int *f,int *g,int lg){
    for(int i=1;i<bin(lg);i++)cnt[i]=cnt[i-(i&-i)]+1;
    for(int i=1;i<bin(lg);i++)F[cnt[i]][i]=f[i];for(int i=1;i<=lg;i++)FMT(F[i]);
    for(int i=0;i<bin(lg);i++){
        for(int j=0;j<=lg;j++)reduce(A[j]=-F[j][i]);
        reduce(A[0]+=1-mod);getinv(A,B,lg);
        for(int j=0;j<=lg;j++)G[j][i]=B[j];
    }
    for(int i=0;i<=lg;i++)FMT(G[i],0);
    for(int i=0;i<bin(lg);i++)g[i]=G[cnt[i]][i];
}
int f[20][20];
int s[1<<19][19],g[1<<19],ans[1<<19];

int main()
{
    cin>>n>>m;
    memset(f,63,sizeof(f));
    for(int i=1,x,y,z;i<=m;i++){
        cin>>x>>y>>z;x--;y--;
        f[x][y]=f[y][x]=min(z,f[x][y]);
    }
    for(int i=0;i<n;i++)f[i][i]=0;
    for(int k=0;k<n;k++)
        for(int i=0;i<n;i++)if(i!=k)
            for(int j=0;j<n;j++)if(j!=k&&j!=i)
                f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
    static int cnt[1<<18|1];
    for(int i=0;i<18;i++)cnt[1<<i]=i;
    for(int S=1;S<(1<<n);S++)
        for(int i=0;i<n;i++)
            s[S][i]=s[S-(S&-S)][i]+f[cnt[S&-S]][i];
    for(int S=1;S<(1<<n);S++){
        g[S]=2e9;
        for(int i=0;i<n;i++)if(S>>i&1)
            g[S]=min(g[S],s[S][i]);g[S]++;
    }
    solve(g,ans,n);
    printf("%d\n",ans[(1<<n)-1]);
}
全部评论
事实上你可以不使用快速幂,写成 1/(1-F) 的形式,然后用求逆解决即可。
点赞 回复
分享
发布于 2021-03-20 15:14
/jk /jk
点赞 回复
分享
发布于 2021-03-22 11:03
阅文集团
校招火热招聘中
官网直投

相关推荐

9 收藏 评论
分享
牛客网
牛客企业服务