NOIP A层联测8

感觉以后模拟赛都应该认真改题。

\(100+100+0+100\),开题顺序 \(1-4-2-3\),以为打到12:00结果只打到11:30导致 T3 特殊性质和暴力都没写,后来也懒得写了。

T4 前一天刚做过究极弱化版,跟偷了题一样,切得挺顺利;T2 由于忘了 \(k\) 相等耽误了好久,幸好做出来了。


T1 集合(collection)

考虑计算每种子集和的出现次数,裸的背包,最后直接算,要用费马小定理。

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int MOD=998244353;
int n,f[25000];long long ans=1;
inline int ksm(long long a,int b)
{
    long long ans=1;
    while(b)
    {
        if(b&1) ans=ans*a%MOD;
        a=a*a%MOD,b>>=1;
    }
    return ans;
}
int main()
{
#ifdef ONLINE_JUDGE
    freopen("collection.in","r",stdin);
    freopen("collection.out","w",stdout);
    cin.tie(0),cout.tie(0);
    ios::sync_with_stdio(0);
#endif
    cin>>n;f[0]=1;
    for(register int i=1;i<=n;++i)
        for(register int j=(i+1)*i/2;j>=i;--j)
            f[j]=(f[j]+f[j-i])%(MOD-1);
    for(register int j=1;j<=(n+1)*n/2;++j)
        ans=ans*ksm(j,f[j])%MOD;
    cout<<ans<<'\n';return 0;
}


T2 出租(lantern)

考虑每次从前往后处理,这样显然尽可能往前放是对的,放不下的直接留给下一个位置考虑。

\(sum_i\) 为所有喜好位置为 \(i\) 的总人数。假设所有喜好位置小于 \(x\) 的都可以放下。放完 \(x\) 位置后,还没有规定位置的个数是以 \(x\) 为结尾的,\(sum_i-k\) 的最大后缀和。这些数要放到 \([x+1,x+d]\) 中,也就是说这个值是否小于等于 \(dk\) 等价于是否可行。

所以可以符合所有条件就等价于对于 \(\forall i\in[1,n-d]\),最大后缀和都小于等于 \(dk\)

对于所有位置的最大后缀和就相当于全局的最大子段和。单点修改,全局最大子段和,线段树直接做。

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN=5e5+10;
int n,m,d;long long k;
struct tree{long long num,sum,suml,sumr;}t[MAXN<<2];
inline void push_up(int p)
{
    t[p].num=t[p<<1].num+t[p<<1|1].num;
    t[p].sum=max(max(t[p<<1].sum,t[p<<1|1].sum),t[p<<1].sumr+t[p<<1|1].suml);
    t[p].suml=max(t[p<<1].suml,t[p<<1].num+t[p<<1|1].suml);
    t[p].sumr=max(t[p<<1|1].sumr,t[p<<1|1].num+t[p<<1].sumr);
    return ;
}
void build(int l,int r,int p)
{
    if(l==r){t[p].num=-k;return ;}
    int mid=(l+r)>>1;
    build(l,mid,p<<1),build(mid+1,r,p<<1|1);
    push_up(p);return ;
}
void change(int l,int r,int p,int x,int z)
{
    if(l==r)
    {
        t[p].num+=z;
        t[p].sum=t[p].suml=t[p].sumr=max((long long)0,t[p].num);
        return ;
    }
    int mid=(l+r)>>1;
    if(x<=mid) change(l,mid,p<<1,x,z);
    else change(mid+1,r,p<<1|1,x,z);
    push_up(p);return ;
}
signed main()
{
#ifdef ONLINE_JUDGE
    freopen("lantern.in","r",stdin);
    freopen("lantern.out","w",stdout);
    cin.tie(0),cout.tie(0);
    ios::sync_with_stdio(0);
#endif
    cin>>n>>m>>k>>d;build(1,n,1);
    while(m--)
    {
        int x,y;cin>>x>>y;
        change(1,n,1,x,y);
        cout<<((t[1].sum<=k*d)?"YES":"NO")<<'\n';
    }
    return 0;
}

T3 连通块(connection)

后来发现是挺平凡的树形 DP,状态设计有点没想到。

定义 \(f_{i,j}\) 是以 \(i\) 为根的,dfs 序最后一位是第 \(j\) 号结点的最大连通块,最终答案为 \(\max\limits_{i,j\in[1,n]} f_{i,j}\),初始化 \(f_{i,i}=a_i\),其余为极小值。

考虑 \(y\)\(x\) 的子节点,将 \(y\) 合并到 \(x\) 上。即 \(f_{i,j}=\max\limits_{j,k\in [1,n],k \text{ 和 } y \text{ 没有约束}} (f_{x,k}+f_{y,j})\),时间复杂度 \(O(n^3)\)

发现其实对于同一个 \(y\) 每个 \(j\) 都是从同一个 \(k\) 转移过来的,所以先找出最大的 \(k\)\(y\) 没有约束的 \(f_{x,k}\),再直接用这个值去 DP,时间复杂度 \(O(n^2)\)

发现与约束有关的数 \(\leq 2m\) 个,所以可以重新编号,将所有与约束无关的点都看成等价的点,时间复杂度 \(O(nm)\)

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<vector>
#include<string.h>
#define int long long
using namespace std;
const int MAXN=1e5+10,INF=1e16+7;
int n,m,a[MAXN],s[MAXN],h[MAXN],tot;
int ANS,f[MAXN][45];bool b[45][45];
vector <int> v[MAXN];
inline void dp(int x,int fa=0)
{
    f[x][h[x]]=a[x];
    for(int y:v[x])
    {
        if(y==fa) continue;dp(y,x);int MAX=-INF;
        for(register int i=0;i<=tot;++i)
            if(!b[i][h[y]]) MAX=max(MAX,f[x][i]);
        for(register int j=0;j<=tot;++j)
            f[x][j]=max(f[x][j],MAX+f[y][j]);
    }
    for(register int j=0;j<=tot;++j) ANS=max(ANS,f[x][j]);
    return ;
}
signed main()
{
#ifdef ONLINE_JUDGE
    freopen("connection.in","r",stdin);
    freopen("connection.out","w",stdout);
    cin.tie(0),cout.tie(0);
    ios::sync_with_stdio(0);
#endif
    cin>>n>>m;
    for(register int i=1;i<=n;++i) cin>>a[i];
    for(register int i=1;i<=n;++i)
    {
        cin>>s[i];
        for(register int j=1,x;j<=s[i];++j)
            cin>>x,v[i].push_back(x);
    }
    for(register int i=1,x,y;i<=m;++i)
    {
        cin>>x>>y;
        if(!h[x]) h[x]=++tot;
        if(!h[y]) h[y]=++tot;
        b[h[x]][h[y]]=b[h[y]][h[x]]=1;
    }
    memset(f,-0x3f3f3f,sizeof(f));dp(1);
    cout<<ANS<<'\n';return 0;
}

T4 跳棋(checkers)

考虑没有 ? 的情况。

将每个连续段中的 1 两两分组,总组数是 \(a\),所有剩余的单独出来的 1 的个数为 \(b\),结论是状态数是 \(\dbinom{n-a-b}{a}\),懒得写一遍,参考 AquaMoon and Chess

对于有 ? 的情况。发现仅与 \(a\)\(b\) 有关,而 \(n\leq 500\),考虑枚举 \(a,b\)。但是不知道每种 \(a,b\) 的出现次数,挺 DP 的,设 \(f_{i,x,y,0/1/2}\) 为填到 \(i\),有 \(x\)\(a\)\(y\)\(b\)\(i\) 位是 \(0\) \(/\) 作为一个单独的 1 的结尾 \(/\) 作为一个 11 组的结尾,的方案数。(PS:好像最后一维两种状态就行)

考虑第 \(i\) 位填 \(0\)\(f_{i,x,y,0}=\sum\limits_{k\in[0,2]} f_{i-1,x,y,k}\) 是显然的。

\(i\) 位填 \(1\) 的话,如果第 \(i-1\) 位状态是 \(0\)\(2\),情况都是添加了一个单独的 1,所以 \(f_{i,x,y,1}=f_{i-1,x,y-1,0}+f_{i-1,x,y-1,2}\)

如果 \(i-1\) 位状态是 \(1\),那就相当于你减少了一个单独的 1 而增加了一组 11,所以要从 \(x-1,y+1\) 转移过来,即 \(f_{i,x,y,2}=f_{i-1,x-1,y+1,1}\)

最后直接枚举 \(a,b\) 算对应方案数即可。DP 时滚一下数组。

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN=510,MOD=1e9+7;
int n,f[2][255][255][3],s[255][255];
char c[MAXN];long long ans,P[MAXN],inv[MAXN];
inline long long C(int n,int m)
{
    if(n<m) return 0;
    return P[n]*inv[m]%MOD*inv[n-m]%MOD;
}
inline long long ksm(long long a,int b)
{
    long long ans=1;
    while(b)
    {
        if(b&1) ans=ans*a%MOD;
        a=a*a%MOD,b>>=1;
    }
    return ans;
}
int main()
{
#ifdef ONLINE_JUDGE
    freopen("checkers.in","r",stdin);
    freopen("checkers.out","w",stdout);
    cin.tie(0),cout.tie(0);
    ios::sync_with_stdio(0);
#endif
    cin>>n;P[0]=1;f[0][0][0][0]=1;
    for(register int i=1;i<=n;++i) cin>>c[i],P[i]=P[i-1]*i%MOD;
    inv[n]=ksm(P[n],MOD-2);
    for(register int i=n-1;i>=0;--i) inv[i]=inv[i+1]*(i+1)%MOD;
    for(register int i=1;i<=n;++i)
        for(register int x=0;x<=n/2;++x)
            for(register int y=0;y<=min((n+1)/2,n-2*x);++y)
            {
                for(register int k=0;k<=2;++k) f[i&1][x][y][k]=0;
                if(c[i]!='1')
                    for(register int k=0;k<=2;++k)
                        f[i&1][x][y][0]=(f[i&1][x][y][0]+f[i&1^1][x][y][k])%MOD;
                if(c[i]!='0')
                {
                    if(y>=1)
                        f[i&1][x][y][1]=(f[i&1^1][x][y-1][0]+f[i&1^1][x][y-1][2])%MOD;
                    if(x>=1)
                        f[i&1][x][y][2]=f[i&1^1][x-1][y+1][1];
                }
            }
    for(register int x=0;x<=n/2;++x)
        for(register int y=0;y<=min((n+1)/2,n-2*x);++y)
        {
            int sum=0;
            for(register int k=0;k<=2;++k) sum=(sum+f[n&1][x][y][k])%MOD;
            ans+=C(n-x-y,x)*sum%MOD;
        }
    cout<<ans%MOD<<'\n';return 0;
}
posted @ 2023-10-09 16:27  int_R  阅读(139)  评论(1)    收藏  举报