NOIP提高组模拟赛16

A. 茅山道术

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

code
#include <cstring>
#include <cstdio>
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 <cstring>
#include <cstdio>
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 <cstring>
#include <cstdio>
#include<algorithm>
#include<unordered_map>
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 x<y?x:y;}
ll max(ll x,ll y){return x>y?x:y;}
const int maxn=200005;
const int mod=998244353;
unordered_map<ll,ll>mp[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

posted @ 2022-03-03 11:40  Chen_jr  阅读(61)  评论(0编辑  收藏  举报