zoukankan      html  css  js  c++  java
  • 10.18 正睿模拟赛题解

    T1

    根据排序不等式,正序最大,所以这个值就是排序后相加即可。而交换次数可以用置换环个数来实现。这是一个经典套路。

    注意,只有当数两两不同的时候,我们才能够数置换环。一开始出题人没有说两两不同,我多耽误了 (30) 分钟。

    代码:

    #include<bits/stdc++.h>
    #define dd double
    #define ld long double
    #define ll long long
    #define uint unsigned int
    #define ull unsigned long long
    #define N 300010
    #define M number
    using namespace std;
    
    const int INF=0x3f3f3f3f;
    const int mod=998244353;
    
    template<typename T> inline void read(T &x) {
        x=0; int f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        x*=f;
    }
    
    inline int TwoPow(int x){return 1ll*x*x%mod;}
    
    int n,T,a[N],b[N],c[N],d[N];
    int li[N];
    bool vis[N];
    
    int main(){
        // freopen("my.in","r",stdin);
        // freopen("my.out","w",stdout);
        read(n);read(T);
        for(int i=1;i<=n;i++){read(a[i]);c[i]=a[i];}
        for(int i=1;i<=n;i++){read(b[i]);d[i]=b[i];}
        sort(c+1,c+n+1);sort(d+1,d+n+1);
        ll ans=0;
        for(int i=1;i<=n;i++){
            ans=(ans+TwoPow(c[i]-d[i]))%mod;
        }
        printf("%lld ",ans);
        if(T==0) exit(0);
        int len=unique(c+1,c+n+1)-c-1;
        for(int i=1;i<=n;i++) a[i]=lower_bound(c+1,c+len+1,a[i])-c;
        len=unique(d+1,d+n+1)-d-1;
        for(int i=1;i<=n;i++) b[i]=lower_bound(d+1,d+len+1,b[i])-d;
        for(int i=1;i<=n;i++){
            li[a[i]]=b[i];
        }
        int cnt=0;
        for(int i=1;i<=n;i++){
            if(vis[i]) continue;
            cnt++;int now=i;
            while(!vis[now]){
                vis[now]=1;now=li[now];
            }
        }
        printf("%d
    ",n-cnt);
        return 0;
    }
    

    T2

    对于所有主教,我们先计算其主对角线的覆盖区间,对于副对角线,不难发现其与主对角线的焦点形成了一个区间,直接维护前缀和即可。

    这个题考试的时候没有想出来,思考过如何维护交点,没有发现后面的性质,考试的时候错误的用线段树维护了交点,不仅提高了代码复杂度,而且还写假了。一定要充分考虑对错之后在动笔。

    代码:

    #include<bits/stdc++.h>
    #define dd double
    #define ld long double
    #define ll long long
    #define int long long
    #define uint unsigned int
    #define ull unsigned long long
    #define N 1000100
    #define M number
    using namespace std;
    
    const int INF=0x3f3f3f3f;
    const int base=1000000;
    
    template<typename T> inline void read(T &x) {
        x=0; int f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        x*=f;
    }
    
    int sum[N<<1],n,m;
    ll ans;
    typedef pair<int,int> P;
    P a[N];
    bool vis[N<<1];
    
    //id=0 是上面,id=1 是下面。
    inline P GetPoint(int x,int y,int id){
        int R=x-y;
        if(R<0){
            if(id==0) return make_pair(1,1-R);
            else if(id==1) return make_pair(n+R,n);
        }
        else{
            if(id==0) return make_pair(R+1,1);
            else if(id==1) return make_pair(n,n-R);
        }
        assert(0);
    }
    
    signed main(){
        // freopen("my.in","r",stdin);
        // freopen("my.out","w",stdout);
        read(n);read(m);
        for(int i=1;i<=m;i++){
            // printf("i=%lld
    ",i);
            int x,y;read(x);read(y);
            if(!sum[x+y]){
                sum[x+y]=1;
                if(x+y>n+1) ans+=(2*(n+1)-x-y-1);
                else ans+=x+y-1;
            }
            a[i]=make_pair(x,y);
            // printf("ans=%lld
    ",ans);
        }
        // printf("ans=%lld
    ",ans);
        for(int i=2;i<=(n<<1);i++){
            sum[i]+=sum[i-2];
        }
        for(int i=1;i<=m;i++){
            if(vis[a[i].first-a[i].second+base]) continue;
            vis[a[i].first-a[i].second+base]=1;
            P up=GetPoint(a[i].first,a[i].second,0);
            P down=GetPoint(a[i].first,a[i].second,1);
            int l=up.first+up.second,r=down.first+down.second;
            ans-=sum[r]-sum[l-2];
            ans+=down.first-up.first+1;
        }
        printf("%lld",1ll*n*n-ans);
        return 0;
    }
    

    T3

    这个概率题比较巧妙,是属于那种看起来转移带环,但实际上不带环的那种。

    首先考虑这张图是基环树森林,首先考虑树上的情况:

    [f_i=p_i+(1-p_i)(1-prodlimits_{(j,i)in E}f_jq_j) ]

    这个式子一开始没有理解,认为后面不需要乘上 (1-p_i),但事实上,我们需要把情况分的很清楚,可以通过划分集合来理解,第一个集合是陨石能感染 (i),第二种情况是陨石感染不了 (i),这就是上面的转移。

    也就是说,我们需要把情况分的很细,必须要划分清除。

    实际上在做的时候我们也可以这样转移:

    [f_i:=1-(1-f_i)(1-f_jq_j) ]

    这样正确性也是显然的,并且更好转移,(f_i) 的初始值就是 (p_i)

    然后我们考虑环,不难发现,这个转移是不带环的。我们考虑一个环:

    [1-2-3-4-...-n-1 ]

    我们关注 (1) 号节点感染的概率,显然在做完树形 dp 之后我们可以把 (f_1) 看做 (1) 号节点被自身感染(即不被环上的点感染的概率)的概率直接代入转移。

    转移式为:

    [f_1:=f_1+(1-f_1)f_2q_1+(1-f_1)(1-f_2)f_3q_2q_1+.... ]

    这个转移是正确性比较显然,只需要注意我们上面所说的集合划分,我们要考虑优先级,不难理解为什么要乘上 ((1-f_1),(1-f_1)(1-f_2)),如果暴力对每个点做的话,复杂度将会是 (O(n^2)),这是因为环的长度可以到达 (O(n))。我们考虑快速对每个点做上面这个事情。

    不难发现,上面这个式子其实是一个迭代形式,它可以转化成:

    [((f_n imes (1-f_{n-1})q_{n-1}+f_{n-1}) imes (1-f_{n-2})+f_{n-3}) imes ...+f_1 ]

    如果我们把 (f_2,f_3) 的式子也写出来,不难发现也是一个迭代形式,只不过顺序不同而已,而且我们发现,这个顺序好像是有规律的。

    我们考虑这个迭代顺序怎么做。

    不难发现,乘法和加法可以用矩阵乘法来维护,只要适当构造即可,那么所谓顺序不同,就是矩阵乘法不同而已。

    我们维护矩阵乘法后缀积,前缀积,就可以做完这道题了。

    今天学到了矩阵乘法加速迭代,可以方便改变迭代顺序。

    #include<bits/stdc++.h>
    #define dd double
    #define ld long double
    #define ll long long
    #define int long long
    #define uint unsigned int
    #define ull unsigned long long
    #define N 100010
    #define M number
    using namespace std;
    
    const int INF=0x3f3f3f3f;
    const int mod=998244353;
    
    template<typename T> inline void read(T &x) {
        x=0; int f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        x*=f;
    }
    
    inline int ksm(int a,int b,int mod){
        int res=1;while(b){if(b&1) res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;}return res;
    }
    
    struct Matrix{
        int a[2][2];
        inline void Clear(){memset(a,0,sizeof(a));}
        inline Matrix operator * (const Matrix &b)const{
            Matrix c;c.Clear();
            for(int i=0;i<=1;i++)
                for(int j=0;j<=1;j++)
                    for(int k=0;k<=1;k++) c.a[i][j]=(c.a[i][j]+1ll*a[i][k]*b.a[k][j]%mod)%mod;
            return c;
        }
    }ma[N],E;
    
    inline int Frac(int a,int b){
        return 1ll*a*ksm(b,mod-2,mod)%mod;
    }
    
    int p[N],q[N],t[N],n,rd[N];
    bool vis[N];
    
    queue<int> qu;
    
    inline void Init(){
        read(n);
        for(int i=1;i<=n;i++){
            int a,b;read(a);read(b);
            p[i]=Frac(a,b);
        }
        for(int i=1;i<=n;i++){read(t[i]);rd[t[i]]++;}
        for(int i=1;i<=n;i++){
            int a,b;read(a);read(b);
            q[i]=Frac(a,b);
        }
    }
    
    inline void Bfs(){
        for(int i=1;i<=n;i++) if(!rd[i]) qu.push(i);
        while(qu.size()){
            int top=qu.front();qu.pop();
            vis[top]=1;
            rd[t[top]]--;if(!rd[t[top]]) qu.push(t[top]);
            p[t[top]]=(1-1ll*(1-1ll*p[t[top]])*(1-1ll*p[top]*q[top]%mod)%mod)%mod;
        }
    }
    
    inline Matrix Dfs(int k,Matrix val){
        if(vis[k]) return E;vis[k]=1;
        Matrix now=ma[k]*Dfs(t[k],val*ma[k]);
        p[k]=(now*val).a[1][0];return now;
    }
    
    signed main(){
        // freopen("my.in","r",stdin);
        // freopen("my.out","w",stdout);
        // cout<<(ll)1ll*11*ksm(16,mod-2,mod)%mod;
        Init();Bfs();
        for(int i=1;i<=n;i++){
            ma[i].a[0][0]=1ll*(1-(ll)p[t[i]])*q[i]%mod;
            ma[i].a[0][1]=0;ma[i].a[1][1]=1;
            ma[i].a[1][0]=p[t[i]];
        }E.a[0][0]=E.a[1][1]=1;
        for(int i=1;i<=n;i++) if(!vis[i]) Dfs(i,E);
        for(int i=1;i<=n;i++){
            printf("%lld ",(p[i]%mod+mod)%mod);
        } 
        return 0;
    }
    

    T4

    首先我们发现正着不好做,我们考虑做补集,于是问题转化成了给定一张有向图,求点集的导出子图为 (DAG) 的方案数。

    点数很少,边数很多,考虑状压。因为是 (DAG),我们考虑其度数为 (0) 节点构成的集合 (T)

    (f_S) 为当点集为 (S) 的时候的构成 (DAG) 方案数。

    如果我们强制钦定 (T) 集合中的点度数都为 (0),那么从集合 (T)(S-T) 可以有边,但是从 (S-T)(T) 不能有边,所以我们有一个式子:

    [f_S=sumlimits_{Tsubseteq S,T ot=varnothing}2^{way(T,S-T)}f_{S-T} ]

    但是不难发现上面这个方案会算重,因为 (f_{S-T}) 中的某些方案会让 (S-T) 中的点的度数为 (0),这并不是我们想要的。我们考虑容斥。

    (q_T=2^{way(T,S-T)}f_{S-T}),令 (S) 点集中恰好有集合 (R) 中的点为 (S) 中度数为 (0) 的点,即 (S) 中度数为 (0) 的点恰好是 (R) 中的点的方案数为 (p_R),那么我们有:

    [q_T=sumlimits_{Tsubseteq R}p_R ]

    注意这里需要满足 (Rsubseteq S)

    稍微解释一下上面的这个式子,也就是说,对于 (q_T) 中的每一种方案,我们把 (T)(S-T) 中的 (0) 度点全部放进 (R),这样,每一种方案就会在 (p_R) 中计数一次。这个式子的正确性是显然的,注意 (T) 是不会变的。

    运用二项式反演的子集集合,我们可以得到:

    [p_T=sumlimits_{Tsubseteq R}q_R imes (-1)^{|R|-|T|} ]

    而在本题中,我们要求的相当于:

    [sumlimits_{Tsubseteq S,T ot=varnothing} p_T=sumlimits_{Tsubseteq S,T ot=varnothing}sumlimits_{Tsubseteq Rsubseteq S}q_R imes (-1)^{|R|-|T|}\ =sumlimits_{Rsubseteq S,R ot= varnothing}sumlimits_{Tsubseteq R,T ot= varnothing}q_R imes (-1)^{|R|-|T|}\ =sumlimits_{Rsubseteq S,R ot= varnothing}q_Rsumlimits_{Tsubseteq R,T ot= varnothing} (-1)^{|R|-|T|}\ =sumlimits_{Rsubseteq S,R ot= varnothing}q_Rsumlimits_{k=1}^{|R|} (-1)^{|R|-k}dbinom{|R|}{k}\ =sumlimits_{Rsubseteq S,R ot= varnothing}q_Rsumlimits_{k=0}^{|R|-1} (-1)^{k}dbinom{|R|}{k}\ =sumlimits_{Rsubseteq S,R ot= varnothing}q_R([|R|=varnothing]+(-1)^{|R|-1})\ =sumlimits_{Rsubseteq S,R ot= varnothing}q_R imes (-1)^{|R|-1} ]

    所以我们有:

    [f_S=sumlimits_{Rsubseteq S,R ot=varnothing}f_{S-R} imes 2^{way(R,S-R)} imes (-1)^{|R|-1} ]

    然后就可以转移 (S) 了。注意我们如何预处理 (way(R,S-R)) 这个东西,我们考虑对每个 (S) 都重新预处理,不难发现,我们只需要每次增加一个点就可以转移。因为 (way(R,S-R)) 的预处理,我们需要注意一下 dp 顺序,我们令 (g_R=way(R,S-R)),我们考虑 (R) 要从小到大枚举。

    代码:

    #include<bits/stdc++.h>
    #define dd double
    #define ld long double
    #define ll long long
    #define uint unsigned int
    #define ull unsigned long long
    #define N 20
    #define M number
    using namespace std;
    
    const int INF=0x3f3f3f3f;
    const int mod=1e9+7;
    
    template<typename T> inline void read(T &x) {
        x=0; int f=1;
        char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        x*=f;
    }
    
    inline int lowbit(int x){return x&(-x);}
    
    int lg2[1<<N],Cnt[1<<N],Pre[N],Next[N],n,m,g[1<<N],f[1<<N],Pow[N*N];
    
    inline int sgn(int a){if(a&1) return -1;return 1;}
    
    signed main(){
        // freopen("my.in","r",stdin);
        // freopen("my.out","w",stdout);
        read(n);read(m);
        //点的编号是 0 到 n-1
        for(int i=1;i<=m;i++){
            int from,to;read(from);read(to);
            from--;to--;
            Pre[to]|=(1<<from);Next[from]|=(1<<to);
        }
        lg2[0]=-1;Pow[0]=1;
        for(int i=1;i<1<<N;i++) lg2[i]=lg2[i>>1]+1;
        for(int j=1;j<1<<N;j++) Cnt[j]=Cnt[j>>1]+(j&1);
        for(int i=1;i<=m;i++) Pow[i]=1ll*Pow[i-1]*2%mod;
        f[0]=1;
        for(int S=1;S<1<<n;S++){
            // printf("S=%d
    ",S);
            for(int R=S&(S-1);;R=S&(R-1)){
                int T=S-R,lb=lowbit(T);
                //我们这里枚举 R=S-T
                //从大到小枚举 R,就是从小到大枚举 T。
                g[T]=g[T-lb]-Cnt[Pre[lg2[lb]]&T]+Cnt[Next[lg2[lb]]&R];
                // printf("g[%d]=g[%d]-Cnt[Pre[%d]&%d]+Cnt[Next[%d]&%d]
    ",T,T-lowbit(T),lg2[lowbit(T)]+1,T,lg2[lowbit(T)]+1,R);
                // printf("%d=%d-%d+%d
    ",g[T],g[T-lowbit(T)],Cnt[Pre[lg2[lowbit(T)]]&T],Cnt[Next[lg2[lowbit(T)]]&R]);
                // printf("g[%d]=%d
    ",T,g[T]);
                f[S]=(f[S]+1ll*sgn(Cnt[T]-1)*Pow[g[T]]*f[R]%mod)%mod;
                if(!R) break;
            }
            // printf("f[%d]=%d
    ",S,f[S]);
        }
        printf("%lld
    ",((Pow[m]-f[(1<<n)-1])%mod+mod)%mod);
        return 0;
    }
    
  • 相关阅读:
    刷题[极客大挑战 2019]HardSQL
    刷题[安洵杯 2019]不是文件上传
    归并排序算法及其JS实现
    快速排序算法原理及其js实现
    圣杯布局
    什么是文档流
    AngularJs四大特性
    call,apply,bind的区别
    计算给定数组 arr 中所有元素的总和的几种方法
    es6之Decorator
  • 原文地址:https://www.cnblogs.com/TianMeng-hyl/p/15426187.html
Copyright © 2011-2022 走看看