NOIP提高组模拟赛16


A. 茅山道术

简单\(DP\),只考虑相邻两个同色的是否使用就行

code
#include 
#include 
using namespace std;
const int maxn=1000005;
const int mod=1e9+7;
int rem[maxn],c[maxn],las[maxn],pos[maxn],n;
bool vis[maxn];
int work(int x){
    if(x>n)return 1;
    if(vis[x])return rem[x];
    vis[x]=1;
    if(pos[x]&&pos[x]!=x+1)return rem[x]=(1ll*work(pos[x])+work(x+1))%mod;
    return rem[x]=work(x+1);
}
int main(){
    freopen("magic.in","r",stdin);
    freopen("magic.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;++i)scanf("%d",&c[i]);
    for(int i=n;i>=1;--i){
        pos[i]=las[c[i]];
        las[c[i]]=i;
    }
    int aas=work(1);
    printf("%d\n",aas);
    return 0;
}

B. 泰拳警告

这题不难,如果你知道组合数的一些性质会更加简单,不会像我一样还要想垃圾\(dp\)

期望=概率*得分=总得分/总方案数

把平局看成\(p\)种方案,这样算出总得分最后乘以\((p+2)^n\)的逆元即可

得分与平局数相关,考虑枚举平局数\(k\),假设我们知道剩下\(n-k\)轮中,只考虑胜负,胜局多于负局的方案数\(f[n-k]\)

那么总得分为\(\sum_{k=0}^n (k+1)\times f[n-k]\times C_n^k\)(可以在\(n\)轮中任意\(k\)轮平局,所以需要乘上\(C_n^k\))

剩下的问题变成求解\(f[n-k]\)

不妨求\(f[i]\)

当前这一轮只有两种情况胜或负

如果当前胜,那么需要知道前\(i-1\)轮 胜场\(>=\)负场 的方案数

也就是 \(f[i-1]+\)\((i-1)\)轮 胜场等于负场 的方案数

如果\(i-1\)是偶数那么后者的方案数就是\(C_{i-1}^{(i-1)/2}\),如果\(i-1\)是奇数,那么不存在后者

如果当前负,那么需要知道前\(i-1\)轮,胜场\(>\)负场\(+1\) 的方案数

也就是 \(f[i-1]-\)\((i-1)\)轮 胜场 等于 负场\(+1\) 的方案数

如果\(i-1\)是奇数那么后者的方案数就是\(C_{i-1}^{(i-1)/2}\),如果\(i-1\)是偶数,那么不存在后者

这样就能解出\(f[]\)

code
#include 
#include 
using namespace std;
const int maxn=3000005;
const int mod=998244353;

int jc[maxn],inv[maxn],n,p;

int qpow(int x,int y){
    int ans=1;
    while(y){
        if(y&1)ans=1ll*ans*x%mod;
        x=1ll*x*x%mod;
        y>>=1;
    }
    return ans;
}

void  ycl(){
    jc[0]=jc[1]=inv[0]=1;
    for(int i=2;i<=n;++i)jc[i]=1ll*jc[i-1]*i%mod;
    inv[n]=qpow(jc[n],mod-2);
    for(int i=n-1;i>=1;--i)inv[i]=1ll*inv[i+1]*(i+1)%mod;
}

int get_C(int n,int m){return 1ll*jc[n]*inv[n-m]%mod*inv[m]%mod;}

int f[maxn];


int main(){
   freopen("fight.in","r",stdin);
   freopen("fight.out","w",stdout);
   scanf("%d%d",&n,&p);
   ycl();
   f[1]=f[2]=1;
   for(int i=3;i<=n;++i){
       f[i]=1ll*f[i-1]*2%mod;
       if(i&1) f[i]=(1ll*f[i]+get_C(i-1,(i-1)>>1))%mod;
       else  f[i]=(1ll*f[i]-get_C(i-1,i>>1)+mod)%mod;
   }
   int ans=0,pk=1;
   for(int k=0;k<=n;++k){
       ans=(ans+1ll*get_C(n,k)*pk%mod*f[n-k]%mod*(k+1)%mod)%mod;
       pk=1ll*pk*p%mod;
   }
   int in=qpow(p+2,n);
   in=qpow(in,mod-2);
   ans=1ll*ans*in%mod;
   printf("%d\n",ans);
   return 0;
}

C. 万猪拱塔

奇妙的线段树维护

发现符合要求的子矩阵一定是值域连续的,然后上题解。。。

考虑判定值在区间 \([l,r]\) 内的所有数所在的格子是否形成了一个矩形,记这些
格子的颜色为黑色,其它的格子颜色为白色。

考虑所有的 \((n + 1) ×(m + 1)\)\(2 ×2\)的小正方形 (部分超出边界也算),则所有
黑色格子形成一个矩形,当且仅当恰好有 \(4\) 个小正方形内部有 \(1\) 个黑色格子,
并且没有任何一个小正方形内部有 \(3\) 个黑色格子

从小到大枚举 \(r\) ,对每个\(l ≤r\),记 \(f(l)\) 表示染黑权值在 \([l,r]\)内的格子后,有多
少小正方形内部有\(1\)个或\(3\)个黑色格子

可以发现有 \(f(l) ≥4\),\(f(r) = 4\) ,于是只需要对每个 \(l\) 维护 \(f(l)\) 最小值,最小值的
数目和取得最小值的所有 \(l\) 之和

每次\(r\) 增加\(1\) 时,会影响到周边的\(4\)\(2 ×2\) 的小正方形,在线段树上修改即可

时间复杂度 \(O(nm log nm)\)

code

#include 
#include 
#include
#include
typedef long long ll;
using namespace std;
void swap(int &x,int &y){x^=y;y^=x;x^=y;}
ll min(ll x,ll y){return xy?x:y;}
const int maxn=200005;
const int mod=998244353;
unordered_mapmp[maxn];
struct node{ll cnt,lazy,sum,min;}ret;
int n,m;
struct tree{
    
    node t[maxn<<2|1];

    void built(int x ,int l,int r){
        if(l==r){
            t[x].cnt=1;
            t[x].sum=l;
            return;
        }
        int mid=(l+r)>>1;
        built(x<<1,l,mid);
        built(x<<1|1,mid+1,r);
    }

    void push_up(int x){
        t[x].sum=t[x].cnt=0;
        t[x].min=min(t[x<<1].min,t[x<<1|1].min);
        if(t[x].min==t[x<<1].min)t[x].cnt+=t[x<<1].cnt,t[x].sum+=t[x<<1].sum;
        if(t[x].min==t[x<<1|1].min)t[x].cnt+=t[x<<1|1].cnt,t[x].sum+=t[x<<1|1].sum;
        
    }

    void push_down(int x){
        t[x<<1].lazy+=t[x].lazy;
        t[x<<1|1].lazy+=t[x].lazy;
        t[x<<1].min+=t[x].lazy;
        t[x<<1|1].min+=t[x].lazy;
        t[x].lazy=0;
    }

    void modify(int x,int l,int r,int L,int R,ll z){
        if(L>R)return;
        if(L<=l&&r<=R){
            t[x].lazy+=z;
            t[x].min+=z;
            return;
        }
        int mid=(l+r)>>1;
        if(t[x].lazy)push_down(x);
        if(L<=mid)modify(x<<1,l,mid,L,R,z);
        if(R>mid)modify(x<<1|1,mid+1,r,L,R,z);
        push_up(x);
    }

    void query(int x,int l,int r,int L,int R){
        if(L<=l&&r<=R){
            if(t[x].min==4){
                ret.sum+=t[x].sum;
                ret.cnt+=t[x].cnt;
            }
            return;
        }
        if(t[x].lazy)push_down(x);
        int mid=(l+r)>>1;
        if(L<=mid)query(x<<1,l,mid,L,R);
        if(R>mid)query(x<<1|1,mid+1,r,L,R);
    }
    
    

}T;
struct tpp{int x,y;}tp[maxn];
int ls[25];
void work(int x,int y,int w){
    ll cnt=0,p,k=1;
    ls[++cnt]=mp[x][y];
    ls[++cnt]=mp[x-1][y-1];
    ls[++cnt]=mp[x-1][y];
    ls[++cnt]=mp[x][y-1];
    sort(ls+1,ls+cnt+1);
    p=lower_bound(ls+1,ls+cnt+1,w)-ls;//防止更新非法状态
    for(p;p>=1;--p,k*=-1)T.modify(1,1,n,ls[p-1]+1,ls[p],k);
}
int main(){
     freopen("pig.in","r",stdin);
     freopen("pig.out","w",stdout);
    
    scanf("%d%d",&n,&m);
    for(int i=0;i<=n+1;++i)mp[0][i]=mp[n+1][i]=mp[i][0]=mp[i][n+1]=maxn;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j){
            scanf("%d",&mp[i][j]);
            tp[mp[i][j]].x=i;tp[mp[i][j]].y=j;
        }

    n=n*m;
    T.built(1,1,n);
    
    long long ans=0;
    for(int i=1;i<=n;++i){
        //对受影响的四个矩形进行操作
        work(tp[i].x,tp[i].y,i);
        work(tp[i].x+1,tp[i].y+1,i);
        work(tp[i].x+1,tp[i].y,i);
        work(tp[i].x,tp[i].y+1,i);
        //查询当前贡献
        T.query(1,1,n,1,i);
        ans=(ans+(i+1)*ret.cnt-ret.sum+mod)%mod;
        ret.cnt=ret.sum=0;
    }
    printf("%lld\n",ans);
    return 0;
}

D. 抑郁刀法

不会。这题给我的唯一收获--了解了什么是\(NPC\)问题

粘一下题解

考虑对图进行收缩,对于度数为 \(1\)的点,可以直接将其删掉,并将最后答案乘
\((k ?1)\)

对于度数为 \(2\) 的点,记它连向的两个点分别为\(a,b\)

将这个点删掉,若最后 \(a,b\) 之间同色,则有 \(k ?1\) 种情况它与 \(a,b\) 都异色,有 \(1\)
种情况它与 \(a,b\) 都同色

\(a,b\) 之间异色,则有 \(k ?2\) 种情况它与 \(a,b\) 都异色,有\(1\)种情况与\(a\)同色,\(1\) 种情况与 \(b\) 同色

我们为每两个点之间记录\(f(a,b),g(a,b)\)分别表示它们同色时答案乘上的系数,异色时答案乘上的系数。

在删掉度数为\(2\)的点时更新对应的 \(f(a,b)\) 即可

最终得到的图中,每个点度数至少为 \(3\) ,结合 \(m ≤n + 5\) 可算出 \(n ≤10,m ≤15\)

对这个图做状压 \(dp\) ,并考虑上 \(f,g\) 的系数即可

状压 \(dp\) ,每次选择一个未染色的子集进行染色,时间复杂度 \(O(nm3^n)\)

code

相关