21.10.14 test
阅读原文时间:2023年07月10日阅读:2

题目 WOJ5078WOJ5081

T1 Problem A \(\color{green}{100}\)

由于每轮要选择尽量多的边删除,所以想到无向图的生成树,因为在生成树上再加一条边就会形成环。然后要求边权和最大的最大的。那么题意就是求 \(k\) 颗最大生成树。

先把边按边权从大到小排,考虑朴素 Kruscal ,维护 \(k\) 次并查集,复杂度 \(O(km)\) 过不去。考虑同时维护 \(k\) 个并查集,那么枚举每条边,将其加入到第一个 \(u,v\) 不连通的并查集中。由于每次都要求加入到最前面的并查集,那么任意 \(u,v\) 的连通性一定是单调的,也就是前面不连通,后面联通,所以每次二分找这个并查集。

下面是我赛时的做法。

我赛时写的神秘优化也跑的飞快,估计是出题人没有想到这种优化,没有刻意卡。是对每次 \(O(k)\) 找加入的并查集的优化。有点像 Dinic 的当前弧优化,就是从上一次对 \(u,v\) 边找到的并查集的下一个开始找,显然重边越多跑的越快。

code:

#include<bits/stdc++.h>
using namespace std;
#define in read()
inline int read(){
    int p=0,f=1;
    char c=getchar();
    while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){p=p*10+c-'0';c=getchar();}
    return p*f;
}
const int N=1e3+5;
const int M=3e5+5;
const int K=1e4+5;
struct edge{
    int u,v,w,o;
}e[M];
int n,m,k;
inline bool cmp(edge a,edge b){
    return a.w>b.w;
}
int vis[M];
int fa[N][K];
inline int getf(int x,int tk){
    if(fa[x][tk]==x)return x;
    return fa[x][tk]=getf(fa[x][tk],tk);
}
int cur[N][N];
signed main(){
//    freopen("a.in","r",stdin);
//    freopen("a.out","w",stdout);
    register int i;
    n=in,m=in,k=in;
    for(i=1;i<=m;++i)
        e[i].u=in,e[i].v=in,
        e[i].w=in,e[i].o=i;
    sort(e+1,e+1+m,cmp);
    for(i=1;i<=n;++i)
        for(int j=1;j<=k;j++)
            fa[i][j]=i;
    for(i=1;i<=m;++i){
        int u=e[i].u,v=e[i].v;
        int t=cur[u][v]+1,f1=getf(u,t),f2=getf(v,t);
        while(f1==f2&&t<k)
            t++,f1=getf(u,t),f2=getf(v,t);
        if(f1==f2)continue;
        vis[e[i].o]=t;
        fa[f2][t]=f1;
        cur[u][v]=cur[v][u]=t;
    }
    for(i=1;i<=m;++i)
        cout<<vis[i]<<'\n';
    return 0;
}

T2 Problem B \(\color{red}{98}\)

由于操作只能用行来更新列,所以正确的构造方案一定是先构造出一整行黑色,然后再逐列修改。

先考虑无解,当且仅当图中没有黑色时无解。因为只要有一个黑色,将黑色对应行修改每列一定能构造出一整行黑。

判断无解之后图中一定有黑色。

考虑将每行作为要构造的整行黑之后取最小值。

如果第 \(i\) 列有黑,那么那么用这个黑一定可以构造出全黑的第 \(i\) 行;如果第 \(i\) 列没有黑,那么随便找一个有黑的行操作一次就可以让第 \(i\) 列有黑。

然后考虑第 \(i\) 行的操作数,按上述操作让第 \(i\) 列有黑。然后遍历一行,有一个白就算一次操作。将整行染黑之后再遍历每列,如果这一列全是黑就不操作,否则算一次操作。

#include<bits/stdc++.h>
using namespace std;
const int N=505;
int n;
int mp[N][N];
int line[N];
bool OK[N];
bool cisb[N];
int tans=0x7fffffff;
bool NO=true;
signed main(){
//    freopen("b.in","r",stdin);
//    freopen("b.out","w",stdout);
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++){
            char c;cin>>c;
            mp[i][j]=(c=='#');
            if(mp[i][j])OK[j]=1;
            if(mp[i][j])NO=false;
        }
    if(NO){cout<<-1;return 0;}
    for(int i=1;i<=n;i++){
        cisb[i]=1;
        for(int j=1;j<=n;j++)
            if(!mp[j][i])cisb[i]=0;
    }
    for(int i=1;i<=n;i++){
        int ans=n;
        if(!OK[i])ans++;
        for(int j=1;j<=n;j++){
            if(cisb[j])ans--;
            if(!mp[i][j])ans++;
        }
        tans=min(ans,tans);
    }
    cout<<tans;
    return 0;
}

T3 Problem C \(\color{red}{60}\)

想了一堆关于 \(\sqrt n\) 的无用性质,终究没有注意到正解的性质。

乘积大于 \(n^m\) 的方案数等于乘积小于 \(n^m\) 的方案数。

考虑一个序列 \(p_1,p_2,\cdots,p_{2m}\),且\(p_1p_2\cdots p_{2m}=t<n^m\)

那么有 \(\frac{n}{p_1},\frac{n}{p_2},\cdots,\frac{n}{p_{2m}}\),且\(\frac{n}{p_1}\frac{n}{p_2}\cdots\frac{n}{p_{2m}}=\frac{n^{2m}}{t}>n^m\)

所以每一个乘积小于 \(n^m\) 的序列对应一个乘积大于 \(n^m\) 的序列,所以我们只需要算出乘积等于 \(n^m\) 的序列数就好了。

对于乘积等于 \(n^m\) 的数量,可以用 dp 做。题解曰,经典背包

考虑求出 \(n^m\) 的质因子也就是 \(n\) 的质因子且个数乘以 \(m\)。那么问题就是这些质因子分配到 \(2m\) 个位置的方案数。

考虑将每个质因子单独做最后乘起来就是答案。

记 \(n\) 的一个质因子为 \(p\),有 \(c\) 个。

设 \(f[i][j]\) 表示前 \(i\) 个位置一共分配了 \(j\) 个 \(p\)。显然 \(f\) 的范围是 \(2m*c*m\),\(c\) 的期望个数是 \(\log n\) ,所以状态数是 \(m^2\log n\)。

由于要求每个位置整除 \(n\),所以每个位置最多分配 \(c\) 个\(p\) ,于是转移时枚举当前位置分配了几个 \(p\),方程为:

\[f[i][j]=\sum\limits_{k=0}^{c}f[i-1][j-k]
\]

可以滚动数组滚掉第一维。

时间复杂度分析:\(p\) 的种类约为 \(\log n\),个数约为 \(\log n\),外层循环每个 \(p\),内层 dp 状态数 \(m^2\log n\),转移 \(p\) 的个数,所以总复杂度大概是 \(O(m^2(\log n)^3)\)。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
inline int read(){
    int p=0,f=1;
    char c=getchar();
    while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){p=p*10+c-'0';c=getchar();}
    return p*f;
}
const int mod=998244353;
int inv2=499122177;
int qpow(int a,int b){
    int ans=1;
    while(b){
        if(b&1)ans=ans*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ans;
}
int n,m;
int p[100],pn;
int cnt[100];
void getp(int x){
    int ans=x;
    for(int i=2;i*i<=x;i++){
        if(x%i==0)p[++pn]=i;
        while(x%i==0)cnt[pn]++,x/=i;
    }
    if(x!=1)p[++pn]=x,cnt[pn]=1;
}
int f[205][6005];
int ans=1,res=1;
signed main(){
    cin>>n>>m;
    getp(n);
    for(int i=1;i<=pn;i++)
        res=res*(cnt[i]+1)%mod;
    for(int l=1;l<=pn;l++){
        memset(f,0,sizeof(f));
        for(int i=0;i<=cnt[l];i++)
            f[1][i]=1;
        for(int i=2;i<=m<<1;i++){
            for(int j=0;j<=cnt[l]*m;j++){
                for(int k=0;k<=cnt[l];k++){
                    f[i][j]=(f[i][j]+f[i-1][j-k])%mod;
                }
            }
        }
        ans=ans*f[m<<1][cnt[l]*m]%mod;
    }
    ans=(qpow(res,(m<<1))+ans+mod)%mod*inv2%mod;
    cout<<ans;
    return 0;
}

有个想法是用经典 dp 求出把 \(n\) 个数分配到 \(m\) 个方案数,然后容斥掉不合法的分配,但是写挂了。


T4 Problem D \(\color{purple}{0}\)

freoepn,虽然改过来也是挂的

明天改。

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章