题解|2023暑期杭电多校08

【原文链接】

1005.0 vs 1

博弈,模拟

题目大意

两人名为零和壹,在给定的 01 01 01 串上进行博弈
零只能取走两端的某一个 0 0 0 ,壹只能取走两端的某一个 1 1 1 ,零执先
先不能取的人判负,若取完则判平局

解题思路

模拟博弈过程,当前操作者 x x x 可以可以遵循以下策略:

  1. 两端不同,只能取 x x x 的一端,交替操作权
  2. 两端相同
    1. 两端都不是 x x x ,无法操作,失败
    2. 两端都是 x x x ,假设取了某端
      1. 这端的下一个数字是 x x x ,则两端都是 x x x ,对方无法操作,获胜
      2. 这端的下一个数字是 ! x !x !x ,则对方只能取这一端
    3. 如果离任一端最近的连续两个相同的数都为 x x x ,则根据上 2 2 2 一直取到 x x x 获胜
    4. 如果离两端最近的连续两个相同的数都为 ! x !x !x ,则不论选哪端,最终都会到达两端都为 ! x !x !x 的情况,失败
      1. 特判:如果整个串有且仅有 1 1 1 段连续两个相同的 ! x !x !x ,则从两端向中间将各会取掉一个,达成平局

可以结合代码注释理解这一过程

时间复杂度

O ( n ) O(n) O(n)

参考代码

void solve_s(deque<int> s,int now){
    ll n=s.size(),i,j;;
    if(s.empty()) {cout << "-1" << endl;return ;}//取完平局
    if(s.front()!=s.back()){//前后不同,无法自由选择,交替操作权
        if(s.front()==now){//取前
            s.pop_front();solve_s(s,(now?0:1));return ;
        }else{//取后
            s.pop_back(); solve_s(s,(now?0:1));return ;
        }
    }else{//前后相同
        if(s.front()!=now) {cout << (now?"0":"1") << endl;return ;}//两端不可取,失败
        for(i=0;i<n-1;i++) if(s[i]==s[i+1]) break;//从前往后找第一个连续
        if(i==n-1) {cout << "-1" << endl;return ;}//无连续,取完平局
        if(s[i]==now) {cout << (now?"1":"0") << endl;return ;}//有己方连续,胜利
        for(j=n-1;j>i;j--) if(s[j]==s[j-1]) break;//从后往前找最后一个连续
        if(s[j]==now) {cout << (now?"1":"0") << endl;return ;}//有己方连续,胜利
        if(i+1==j) {cout << "-1" << endl;return ;}//有且仅有一对对方连续,取完平局
        {cout << (now?"0":"1") << endl;return ;}//两对以上对方连续,失去主动权,判负
    }
}
void solve(){
    ll n;cin >> n;
    string ts;cin >> ts;
    deque<int> s(n);FORLL(i,0,n-1) s[i]=ts[i]-'0';
    solve_s(s,0);
}

1007.Solubility

并查集/DFS

题目大意

给定 n n n 个元素之间的 m m m 对等价关系,问指定 k k k 个元素是否属于同一等价类

解题思路

这里给出两种解题思路:

  1. DFS:建无向图,DFS判断指定元素是否在同一个连通分量里
  2. 并查集:标准并查集板子题,裸套即可

参考代码

  1. DFS
#define N 100005
int visited[N]={0};
vector<vector<ll>> G;
void DFS(ll x){
    visited[x]=1;
    for(auto v:G[x]){
        if(!visited[v]) DFS(v);
    }
}
void solve()
{
    ll m,n;cin >> n >> m;
    G.clear();G.resize(n+1);
    FORLL(i,1,n) visited[i]=0;
    ll u,v;
    FORLL(i,1,m){
        cin >> u >> v;
        G[u].emplace_back(v);
        G[v].emplace_back(u);
    }
    ll k;cin >> k;
    vector<ll> s(k);for(auto &x:s) cin >> x;
    DFS(s[0]);
    for(auto x:s) if(!visited[x]) {cout << NO;return ;}
    cout << YES;
}
  1. 并查集
void solve(){
    ll n,m;cin >> n >> m;
    DSU dsu(n);
    ll a,b;
    FORLL(i,1,m){
        cin >> a >> b;
        dsu.merge(a,b);
    }
    ll k;cin >> k >> a;a=dsu.find(a);
    FORLL(i,2,k){
        cin >> b;b=dsu.find(b);
        if(b!=a) {cout << NO;FORLL(j,i+1,k) cin >> n;return ;}
    }cout << YES;
}

1008.Expectation of Rank

线性代数-矩阵与向量空间、期望、动态规划

题目大意

给定两个正整数 n , p n,p n,p ,其中 p p p 是质数
n n n 阶矩阵 A \bf A A 中的所有元素随机在 p p p 的有限域 F p \mathbb{F}_p Fp 中产生,求矩阵 A \bf A A 的秩的期望 E ( r a n k ( A ) ) \mathbb{E}(rank(\bf A)) E(rank(A)) ,答案取模

前置知识点

  1. 有限域 F p \mathbb{F}_p Fp :在本题中可以粗略的理解为 [ 0 , p − 1 ] [0,p-1] [0,p1] 的整数集
  2. 线性代数-矩阵与向量空间基础知识:多维向量与向量组、线性相关、矩阵的秩与向量之间的关系、向量组张成向量空间的概念等

建议在学习过《线性代数》课程后再解决本题。

解题思路

一个含 k k k 个向量的极大无关组可以张成一个 k k k 维向量空间

p p p 的有限域 F p \mathbb{F}_p Fp 下,每一维度上的坐标有 p p p 种选择,故以该极大无关组为基,通过线性组合可以产生 p k p^k pk 种不同的 k k k 维向量(高维包含低维)
顺带一提,这并不意味着这 p k p^k pk 种向量仅有 k k k 位坐标非 0 0 0

矩阵 A \bf A A 的每一行可以视为一个 n n n 维向量,前 i i i 行的秩表示了前 i i i 个向量组成的向量组,其极大无关组中有多少个向量。这也意味着前 i i i 行已经张成了一个多少维度的向量空间

构造DP数组, d p i , k dp_{i,k} dpi,k 用以表示矩阵 A \bf A A i i i 行的秩为 k k k 的方案数

假设前 i − 1 i-1 i1 行的秩为 k k k ,那么其张成的向量空间为 k k k 维,考虑状态转移:

  1. i i i 行中可以构造出 p k p^k pk 个向量落在这个向量空间中,并不改变秩(或者说维数)
  2. 余下 p n − p k p^n-p^k pnpk 个向量将与前 i − 1 i-1 i1 个向量线性无关,并使张成的空间增大一维,秩 + 1 +1 +1

综上所述,构造出以下状态转移方程:
d p i , k = { 0 , k > i ( r a n k i ≤ i 恒成立 ) 1 , k = 0 ( 当且仅当全为 0 向量 ) ∑ j = 1 i − 1 d p i − 1 , j × p j + d p i − 1 , j − 1 × ( p n − p j ) , O t h e r w i s e dp_{i,k}= \begin{cases} 0 \qquad,k>i\quad(rank_i\le i恒成立) \newline 1 \qquad,k=0\quad(当且仅当全为 \bf{0} \text{向量}) \newline \sum\limits_{j=1}^{i-1} dp_{i-1,j}\times p^j + dp_{i-1,j-1}\times (p^n-p^j) &,Otherwise \end{cases} dpi,k= 0,k>i(rankii恒成立)1,k=0(当且仅当全为0向量)j=1i1dpi1,j×pj+dpi1,j1×(pnpj),Otherwise

总方案数为 ( p n ) n = p n 2 (p^n)^n=p^{n^2} (pn)n=pn2 ,最终期望为 1 p n 2 ∑ j = 1 n j × d p n , j \dfrac{1}{p^{n^2}}\sum\limits_{j=1}^{n} j\times dp_{n,j} pn21j=1nj×dpn,j

时间复杂度

DP: O ( n 2 ) O(n^2) O(n2)

参考代码

#define N 5005
ll n,p;
ll dp[N][N],powp[N];//dp_i,k表示矩阵前i行秩为k的方案数
void Init(ll n){
    FORLL(i,0,n)
        FORLL(j,0,n)
            dp[i][j]=0;
    powp[0]=1;
    dp[0][0]=1;
    FORLL(i,1,n) powp[i]=mul(powp[i-1],p);
}
void solve()
{
    cin >> n >> p;
    Init(n);
    FORLL(i,1,n){
        dp[i][0]=1;
        FORLL(k,1,i){//每行看作一个向量,有p^n种构造法。确定前i行的秩为k的总向量方案数
            addto(dp[i][k],mul(dp[i-1][k],powp[k]));
            //对于前i-1行秩为k的某方案,其向量空间为k维,故在p的有限集下有且仅有p^k个向量落在该向量空间
            addto(dp[i][k],mul(dp[i-1][k-1],sub(powp[n],powp[k-1])));
            //对于前i-1行秩为k-1的某方案,有p^(k-1)个向量落在其张成的向量空间,剩余向量与前i-1行线性无关
        }
    }
    ll re=0,ninv=inv(qcpow(powp[n],n));//ninv:总方案数(p^n)^n的倒数
    FORLL(i,1,n) addto(re,mul(mul(i,dp[n][i]),ninv));
    cout << re << endl;
}

1010.Rikka with Square Numbers

数学、贪心

题目大意

给定两个正整数 a , b a,b a,b ,每次操作可以使 a a a 增大或减小一个平方数 x x x ,求把 a a a 变成 b b b 的最小操作次数

解题思路

即求 a , b a,b a,b 之差最少可以用多少个平方数的和差表示。以下是一些思路:

  1. a = b a=b a=b 0 0 0
  2. n 2 n^2 n2 ,平方数本身, 1 1 1
  3. n 2 − ( n − 1 ) 2 = 2 n − 1 n^2-(n-1)^2=2n-1 n2(n1)2=2n1 ,用两个相邻平方数之差即可表示任意奇数
  4. n 2 − ( n − 2 ) 2 = 4 ( n − 1 ) n^2-(n-2)^2=4(n-1) n2(n2)2=4(n1) ,用两个距离为 2 2 2 的平方数之差可以表示任意 4 4 4 的倍数
  5. 结合以上两条可以归纳证明两个平方数之差一定为奇数或 4 4 4 的倍数, 2 2 2
  6. 4 4 4 2 2 2 的情况可能为两平方数加和,可以枚举判断, 2 2 2
  7. 其余的数可以用第 3 3 3 点得到任意奇数后加减 1 1 1 3 3 3

综上判定即可

时间复杂度

O ( n ) O(\sqrt n) O(n )

参考代码

bool Is_Sqr(ll x){
    ll t=sqrt(x);
    if(t*t==x) return 1;
    return 0;
}
void solve()
{
    ll a,b;cin >> a >> b;
    if(a<b) swap(a,b); ll dif=a-b;
    if(a==b) {cout << 0 << endl;return ;}
    if(Is_Sqr(dif)) {cout << 1 << endl;return ;}
    if(dif%2||(dif%4==0)) {cout << 2 << endl;return ;}
    for(ll i=1;i*i<=dif;i++) if(Is_Sqr(dif-i*i)) {cout << 2 << endl;return ;}
    cout << 3 << endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深翼CCLMSY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值