Loading

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",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\n",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\n",ans);
    }
    // printf("ans=%lld\n",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-\prod\limits_{(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\times (1-f_{n-1})q_{n-1}+f_{n-1})\times (1-f_{n-2})+f_{n-3})\times ...+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=\sum\limits_{T\subseteq S,T\not=\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=\sum\limits_{T\subseteq R}p_R \]

注意这里需要满足 \(R\subseteq S\)

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

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

\[p_T=\sum\limits_{T\subseteq R}q_R\times (-1)^{|R|-|T|} \]

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

\[\sum\limits_{T\subseteq S,T\not=\varnothing} p_T=\sum\limits_{T\subseteq S,T\not=\varnothing}\sum\limits_{T\subseteq R\subseteq S}q_R\times (-1)^{|R|-|T|}\\ =\sum\limits_{R\subseteq S,R\not= \varnothing}\sum\limits_{T\subseteq R,T\not= \varnothing}q_R\times (-1)^{|R|-|T|}\\ =\sum\limits_{R\subseteq S,R\not= \varnothing}q_R\sum\limits_{T\subseteq R,T\not= \varnothing} (-1)^{|R|-|T|}\\ =\sum\limits_{R\subseteq S,R\not= \varnothing}q_R\sum\limits_{k=1}^{|R|} (-1)^{|R|-k}\dbinom{|R|}{k}\\ =\sum\limits_{R\subseteq S,R\not= \varnothing}q_R\sum\limits_{k=0}^{|R|-1} (-1)^{k}\dbinom{|R|}{k}\\ =\sum\limits_{R\subseteq S,R\not= \varnothing}q_R([|R|=\varnothing]+(-1)^{|R|-1})\\ =\sum\limits_{R\subseteq S,R\not= \varnothing}q_R\times (-1)^{|R|-1} \]

所以我们有:

\[f_S=\sum\limits_{R\subseteq S,R\not=\varnothing}f_{S-R}\times 2^{way(R,S-R)}\times (-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\n",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]\n",T,T-lowbit(T),lg2[lowbit(T)]+1,T,lg2[lowbit(T)]+1,R);
            // printf("%d=%d-%d+%d\n",g[T],g[T-lowbit(T)],Cnt[Pre[lg2[lowbit(T)]]&T],Cnt[Next[lg2[lowbit(T)]]&R]);
            // printf("g[%d]=%d\n",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\n",S,f[S]);
    }
    printf("%lld\n",((Pow[m]-f[(1<<n)-1])%mod+mod)%mod);
    return 0;
}
posted @ 2021-10-19 19:56  hyl天梦  阅读(79)  评论(0编辑  收藏  举报