NOIP2023模拟9联测31 总结

发布时间 2023-11-07 21:16:10作者: 彬彬冰激凌

NOIP2023模拟9联测31 总结

题目

T1 游戏
大意

博弈论,在 \(1—n\)\(\phi(i)\) 小于 \(m\) 都选入集合 \(S\)。在集合 \(S\) 中选数 \(x\),选择完后把数 \(x\) 及其的因数从 \(S\) 中删去。当不能取数时的人败。给 \(n,m\) 求先手胜还是后手胜。

\(1 \leq n \leq 100,0 \leq m \leq n\)

比赛思路

发现一个数进入集合,那么它的所有因数也一定进入集合,将每个数向自己去除一个质因子(只减少一个指数)后的数连有向边,每个数被删掉后那么和这个点连有向边的节点也会被删除。然后后面就没想法了,但是手动模拟发现 \(Alice\)(先手) 的胜率极高,于是在前前后后浪费两小时后写了 \(Alice\) 的表。

正解思路

如果有一种情况先手可以获胜,那么直接取先手。如果不可以获胜,取走 \(1\),将几乎相同的局面留给对手,那么对手将不可以获胜。特别的,对于 \(m=0\) 的情况,后手获胜。

T2 涂鸦
大意

\(n*m\) 个格子,每次操作选择 \((x,y,c)\),表示将 \((1,1)\)\((x,y)\) 的矩形染成 \(c\) 颜色(\(c \in {0,1}\)),花费 \(x*y\)。每次操作在 \(n*m*2\) 种操作中随机选一。给出初始状态,目标状态,求到目标的期望代价。

比赛思路

观察了样例的解释,觉得应该和方程有关,但没想到方程的列法,于是转战 dp,结果 dp 也没想到,最后之后写特殊情况骗分。

正解思路

本文博客链接:2023NOIP A层联测23 T2 涂鸦 - 彬彬冰激凌 - 博客园 (cnblogs.com)

考虑设 \(f_{mst}\),为 \(n*m\) 个格被压成一个二进制 \(mst\),转移到最终状态的期望花费。

可以列出方程

\[f_{mst}=\frac{\sum f_j + w}{2nm} \]

\(j\)\(mst\) 通过一次变换可以到达的状态,\(w\) 为到这个状态的花费。

\(j\) 为最终状态,则 \(f_j=0\)

可以暴力的高斯消元解方程组,时间复杂度 \(2^{3nm}\)

后面的优化变得科学(玄学)起来。

如果某个状态 \(i\)\(j\) 列与目标状态不同,那么在这个位置,一定要进行一次覆盖 \((1,1)\)\((i,j)\) 的操作。

把所有的覆盖操作提取出来,发现覆盖的范围够成了一个阶梯形。

如图:

图中蓝色部分为一次覆盖操作,红色部分为与目标状态相同的部分。

这些覆盖操作有效的只有红蓝交界的覆盖。

why?

红蓝交界的覆盖表示了某个状态与目标状态红色状态相同,蓝色状态不同。而在蓝色部分内的覆盖如果执行了,那么相同部分会增加,也就是变成了别的红蓝状态。

我们可以发现,组成相同红蓝状态的原状态到目标状态的值是一样的。(因为都需要覆盖最外层,即交界的蓝色部分)

而红蓝状态的状态数较少,这样我们就缩小了状态数。

方程可以这么列:

1.得出红蓝状态,可以用二进制表示。

2.某个红蓝状态转回原状态。(任意转成一个即可,方便起见,红色部分与目标相同,蓝色部分与目标完全相反)

3.通过原状态暴力枚举操作,转移到其他的红蓝状态,列出方程。(方程类似于优化前的,相当于把原状态的未知数全部换成对于的红蓝状态的未知数。也就是说,一个红蓝状态对于多个原状态的未知数)

列出方程以后就可以愉快的高斯消元了。

#include<bits/stdc++.h>
using namespace std;

#define ll long long

const ll mod=998244353;
const int maxn=260;

int n,m,cnt,beg,fin,nn;

bool f[maxn];

ll a[maxn][maxn],val[maxn];

char st[10][10],ed[10][10];

unordered_map<int,int>mp;

vector<int>v;

ll ksm(ll x,ll y)
{
    int sum=1;
    for(;y;y/=2,x=x*x%mod) if(y&1) sum=sum*x%mod;
    return sum;
}
int gtid(int x,int y){return (x-1)*m+y;}

void dfs(int x,int k,int state)
{
    if(!x)
    {
        mp[state]=++cnt;
        v.push_back(state);
        return ;
    }
    for(int i=0;i<=k;i++) dfs(x-1,i,state|((1<<x*m)-1^(1<<x*m-i)-1));
}
int change(int id)
{
    int newid=(1<<n*m)-1;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if((id>>gtid(i,j)-1&1)!=(ed[i][j]=='B'))
            {
                for(int ii=1;ii<=i;ii++)
                {
                    for(int jj=1;jj<=j;jj++) newid&=INT_MAX^(1<<gtid(ii,jj)-1);
                }
            }
        }
    }
    return newid;
}

void gsxy()
{
    int r=1,c=1;
    for(;r<=nn&&c<=nn;r++,c++)
    {
        int mxid=r;
        for(int i=r+1;i<=nn;i++) if(abs(a[mxid][c])<abs(a[i][c])) mxid=i;
        for(int i=1;i<=nn+1;i++) swap(a[mxid][i],a[r][i]);
        if(abs(a[r][c])==0){r--;continue;}
        for(int i=1;i<=nn;i++)
        {
            if(i==r) continue;
            ll g=(a[i][c]*ksm((a[r][c]%mod+mod)%mod,mod-2)%mod+mod)%mod;
            for(int j=1;j<=nn+1;j++) a[i][j]=(a[i][j]-a[r][j]*g%mod+mod)%mod;
        }
    }
    memset(f,1,sizeof(f));
    for(int i=1;i<=nn+1;i++)
    {
        int ct=0,id=0;
        for(int j=1;j<=nn;j++) if(f[j]&&abs(a[i][j])) ct++,id=j;
        if(ct==1) f[id]=0,val[id]=(a[i][nn+1]*ksm(a[i][id]%mod+mod,mod-2)%mod+mod)%mod;
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%s",st[i]+1);
    for(int i=1;i<=n;i++) scanf("%s",ed[i]+1);
    dfs(n,m,0);
    for(int i=n;i;i--)
    {
        for(int j=m;j;j--)
        {
            fin=fin*2+(ed[i][j]=='B');
            beg=beg*2+(st[i][j]=='B');
        }
    }
    nn=v.size();
    for(int i=0;i<v.size();i++)
    {
        int t=v[i];
        if(i==mp[change(fin)]-1)
        {
            a[i+1][i+1]=1;
            continue;
        }
        int tt=0;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                if(t>>(gtid(i,j)-1)&1) tt|=(ed[i][j]=='B')<<(gtid(i,j)-1);
                else tt|=(ed[i][j]=='W')<<gtid(i,j)-1;
            }
        }
        a[mp[t]][mp[t]]+=2*n*m;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                int id=tt;
                for(int ii=1;ii<=i;ii++)
                {
                    for(int jj=1;jj<=j;jj++)
                    {
                        id|=1<<(gtid(ii,jj)-1);
                    }
                }
                a[mp[t]][nn+1]+=i*j;
                a[mp[t]][mp[change(id)]]--;
                for(int ii=1;ii<=i;ii++)
                {
                    for(int jj=1;jj<=j;jj++)
                    {
                        id^=1<<(gtid(ii,jj)-1);
                    }
                }
                a[mp[t]][nn+1]+=i*j;
                a[mp[t]][mp[change(id)]]--;
            }
        }
    }
    gsxy();
    printf("%lld\n",val[mp[change(beg)]]);
}

本题的代码逻辑复杂,写代码前可以想好或写下大概框架,在动键盘写代码,有效提升效率,减少错误。

T3 迷路
大意

你知道地图上 \(k\) 个点有树,其他位置均为空。对于可以在 \(n*m\) 的矩阵中等概率选 \(r\) 个点,螺旋状的行走。判断 \(r\) 个点中是从哪个点开始的所走的最小步数(即为路径上遇到的)的期望。

赛时思路

想不到期望 dp 和数学推导,考虑写暴力选点跑,但因为代码一开始没有构思清,终没有写出来。

正解思路

哈希构建等价类,优化 dp,接下来维护多项式。但期望极弱,看不懂。

T4 潘多拉的魔盒
大意

\(n\) 个盒子,每个盒子里有一些宝物,第 \(i\) 个盒子出现宝物 \(j\) 有一定的概率,宝箱可以出现多个宝物,每一个宝物有自己的价值,你可以选择价值最高的宝物带走。但开启宝箱有一定的花费 \(c\),答案为价值减去总花费,求最优策略的期望最大值。

赛时思路

模拟了一下,模拟完发现淳朴的暴力过不了最低数据门槛,遂弃之。

正解思路

设出 dp 方程,不断探究性质优化;或列出不等式分析答案上下界,维护求值。

但期望还是不会。

赛时

开题看到博弈论,小慌了一下。

然后看到 \(3\) 道期望,愈发害怕。

比赛时间初步分配:\(T1:50,T2:40,T3:40,T4:40\)

觉得 \(T1\) 还是最简单的,所以比赛前后投了将近 \(2h\) 去做 \(T1\)

\(T1\) 没做出来猜结论,心态不是很差,可能是早有预料。

\(T2,T3,T4\) 都手推了一下,没有思路,最后写了一手 \(T2\) 的表。

检查完,比赛结束。

赛后

似乎期望对大家都很难,\(T1\) 结论对了,\(T2\) 表了 \(12\) 分,拿到了大众分略高的分数。

反思

由于合理的预期心态到把握的不错,证明看清自己的水平对比赛来说还是有效的。

遇到取模、期望、博弈论不要未战先怯,越是畏惧更做不出来题目。

自己不熟悉的题目,比赛时不要慌神,下考场要努力改变。可以学习要像联测 29 做 \(T2\) 的心态,有决心和信心。

1.对于数学和博弈论方面要有计划性加强。

2.考场上遇到不熟悉的知识点,在时间规定内要有决心信心,当做一次挑战自我的机会。不过,最后做不出了还是要留时间写暴力的。

3.结论题可以打表找规律,及时找不到规律也不亏,代码可以当做暴力代码交。

4.写代码前一定要构思结构,复杂的要写下来。

计划

1.每周的蓝题中至少添加一道数学(除去的组合数学),一道博弈。