day 7 模拟赛 4

模拟赛 4

T1 叠叠乐

长度为 n 的序列 a,进行以下操作:

  • 选择一个 i,对任意 \(i<j\le n\),将 \(a_j\) 变为 \(a_j+a_i\)

最多使用 \(3n\) 次操作,使序列单调不增或单调不降,并保证任意时刻 \(|a_i|\le10^{18}\)

\(n\le10^5,|a_i|\le n\)

先考虑当 a 都是正数时怎么做,发现对于所有的 \(a_i>a_{i+1}\),对 i 进行一次操作,就能让 \(a_i<a_{i+1}\),扫一次 a,最多 n 次操作就能让 a 单调不降。负数同理。

当有正有负的时候,我们考虑能否统一符号,我们对第一个非 0 数 \(a_x\) 进行 \(n-1\) 次操作,发现当 \(a_x>0\) 时,剩下的所有数必定变为正数,再用上述方法扫一遍即可,当 \(a_x<0\) 时同理,剩下的数会被减成负数,之后让序列单调不增即可。-------------------------------40分

但是,我们发现这样构造出的数会很大,因为每次操作都是不断累加的,可能会超过 \(10^{18}\),不符合题意。

那我们反过来,完成符号统一后从右往左,就能避免不断的累加的问题。-------------------------------100分

#include<bits/stdc++.h>
#define int long long
using namespace std;
int t,n,a[100005],b[300005];
signed main(){
    freopen("jenga.in","r",stdin);
    freopen("jenga.out","w",stdout);
    cin>>t;
    while(t--){
        cin>>n;
        for(int i=1;i<=n;i++)cin>>a[i];
        int x=1;
        while(x<=n&&a[x]==0)x++;
        if(x>=n||n<=2){
            cout<<"0\n\n";
            continue;
        }
        int minn=1e18,maxn=-1e18;
        for(int i=x;i<=n;i++){
            minn=min(minn,a[i]);
            maxn=max(maxn,a[i]);
        }
        int k;
        if(a[x]>0){
            k=(-minn-1)/a[x]+1;
        } else {
            k=(maxn-1)/(-a[x])+1;
        }
        for(int i=x+1;i<=n;i++){
            a[i]+=k*a[x];
        }
        int cnt=0;
        if(a[x]>0){
            for(int i=n-1;i>=1;i--){
                if(a[i]>a[i+1])b[++cnt]=i;
            }
        } else {
            for(int i=n-1;i>=1;i--){
                if(a[i]<a[i+1])b[++cnt]=i;
            }
        }
        int ans=k+cnt;
        cout<<ans<<"\n";
        for(int i=1;i<=k;i++)cout<<x<< " ";
        for(int i=1;i<=cnt;i++)cout<<b[i]<< " ";
        cout<<"\n";
    }
    return 0;
}

T2 往事回首

长度为 n 的 +- 序列,每个符号代表快乐/悲伤,会让心情值 +1/-1,给定 q 次询问,每次询问忽略掉区间 \([l,r]\),求心情值有多少不同的值。

\(n,q\le2*10^5\)

部分分

暴力枚举。-------------------------------30分

\(r=1\) 时,遍历一次,同时记录心情值的种类即可。-------------------------------40分

正解

每次 +1/-1 变化,可以发现最终所有出现过的心情值是一段连续的区间,我们只需要求出这个区间的最大/最小值即可。

设心情的前缀和为 s,记录:

  • lmin[i]s[0,...,i] 的最小值;
  • lmax[i]s[0,...,i] 的最大值;
  • rmax[i]s[i,...,n]相对最小值;
  • rmax[i]s[i,...,n]相对最大值;
  • 这里的“相对”就是 \(s[j]-s[i-1]\) 的值,即偏移量。

那么区间 \([l,r]\) 的最小值为

\[VL = \min(Lmin[l-1], pre[l-1] + Rmin[r+1]) \]

最大值为

\[VR = \max(Lmax[l-1], pre[l-1] + Rmax[r+1]) \]

这里对右半部分的处理为加上左边结束后的结果。

复杂度 \(O(n)\)。-------------------------------100分

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,q;
string s;
int a[200005],lmin[200005],rmin[200005],lmax[200005],rmax[200005];
signed main(){
	freopen("history.in","r",stdin);
	freopen("history.out","w",stdout);
	cin>>n>>q>>s;
	for(int i=0;i<n;i++){
		if(s[i]=='+')a[i+1]=a[i]+1;
		else a[i+1]=a[i]-1;
	}
	lmin[0]=0;rmin[n+1]=0;lmax[0]=0;rmax[n+1]=0;
	for(int i=1;i<=n;i++){
		lmin[i]=min(lmin[i-1],a[i]);
		lmax[i]=max(lmax[i-1],a[i]);
	}
	for(int i=n;i>=1;i--){
		int delta=a[i]-a[i-1];
		rmin[i]=min((int)0,delta+rmin[i+1]);
		rmax[i]=max((int)0,delta+rmax[i+1]);
	}
	for(int i=1;i<=q;i++){
		int l,r;cin>>l>>r;
		int vl=min(lmin[l-1],a[l-1]+rmin[r+1]);
		int vr=max(lmax[l-1],a[l-1]+rmax[r+1]);
		cout<<vr-vl+1<<"\n";
	}
	return 0;
}

T3 组队难题

\(2n\) 名选手,要两人一队组成 n 支队伍,成绩最高的 \(2n-1\) 人,没人选一个成绩比自己低的,自己最讨厌的人,需要保证每个人不和这个选出的最讨厌的人在一组,求总方案数对 998244353 取模。\(n\le 2000\)

考虑容斥原理,设 \(f(i)\) 为指定有 i 对矛盾的人的方案个数,剩下的人随意,那么最终答案即为

\[\sum_{i=0}^n(-1)^i\times f(i) \]

我们发现,讨厌的关系是个树形结构,因为一个人只会讨厌成绩比自己差的人,所以如果一个人 i 讨厌 \(p_i\),那么就让 \(p_i\) 是 i 的父亲,这个树的根是谁都不讨厌的 \(2n\)

考虑如何求出 \(f(i)\),方案数可以拆成 指定 i 对矛盾的方案数*其他人随意组队的方案数。

对于指定 i 对矛盾的方案数,可以用树形 DP 解决:

dp[u][j][0] : 以 u 为根的子树中,选了 j 对“讨厌关系”组队,且 u 还没有被配对
dp[u][j][1] :以 u 为根的子树中,选了 j 对“讨厌关系”组队,且 u 已经被配对(跟某个子节点组队了)

dfs 遍历一次树,考虑把 u 的孩子 v,把子树 v 合并进子树 u 该如何转移:

  • 当 u 和 v 不组队,我们枚举 j1,j2 为之前有讨厌关系的数量,注意枚举的时候最大值是 \(siz/2\),这样每个结点会被枚举 1 次,复杂度才正确:

    new_dp[u][j1 + j2][0] += dp[u][j1][0] * dp[v][j2][0]
    new_dp[u][j1 + j2][0] += dp[u][j1][0] * dp[v][j2][1]
    new_dp[u][j1 + j2][1] += dp[u][j1][1] * dp[v][j2][0]
    new_dp[u][j1 + j2][1] += dp[u][j1][1] * dp[v][j2][1]
    
  • 当 u 和 v 组队,因为我们只讨论有讨厌关系的组队,那么必然有 \(p_u=v\)\(p_v=u\),需要提前判断,而且原来必须都是未组队状态:

    new_dp[u][j1 + j2 + 1][1] += dp[u][j1][0] * dp[v][j2][0]
    

最终这一部分的方案数量为 dp[2n][i][0]+dp[2n][i][1]

那么剩下的队伍是可以随意组队的,还剩下 \(2n-2i\) 个人未组队。

考虑 \(2n\) 个人随意组队的方案数 \(s_n\),这个值应该是 \(\frac{C_n^{2n}\times n!}{2^n}\)。因为我们选 n 个“队长”,共有 \(C_n^{2n}\) 种选法,剩下的人任意排列,有 \(n!\) 种,但因为这样每个相同的组会被计算 \(2^n\) 次,所以要除掉。计算 s,需要预处理阶乘、阶乘逆元、组合数、2的幂次的逆元。

最终,\(f(i)=dp[2n][i][0]+dp[2n][i][1]\times s(n-i)\)

复杂度是树形 DP 的 \(O(n^2)\)。-------------------------------100分

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=998244353;
int n;
int jie[4005],jieni[4005],c[4005][2005],p2[4005],p2ni[4005],s[2005];
int qpow(int x,int k){
    int ans=1;
    while(k){
        if(k&1)ans=ans*x%mod;
        x=x*x%mod;
        k>>=1;
    }
    return ans;
}
vector<int>tr[4005];
int p[4005];
int dp[4005][2005][2],tmp[2005][2],sz[4005];
void dfs(int u,int fa){
	sz[u]=1;
    for(int i=0;i<=n;i++)dp[u][i][0]=dp[u][i][1]=0;
    dp[u][0][0]=1;
    for(int v:tr[u]){
        if(v==fa)continue;
        dfs(v,u);
        for(int i=0;i<=n;i++)tmp[i][0]=tmp[i][1]=0;
        for(int j1=0;j1<=sz[u];j1++){
            for(int j2=0;j2<=sz[v];j2++){
                if(j1+j2>n)break;
                tmp[j1+j2][0]=(tmp[j1+j2][0]+dp[u][j1][0]*(dp[v][j2][0]+dp[v][j2][1]))%mod;
                tmp[j1+j2][1]=(tmp[j1+j2][1]+dp[u][j1][1]*(dp[v][j2][0]+dp[v][j2][1]))%mod;
                if((p[u]==v||p[v]==u)&&j1+j2+1<=n){
                    tmp[j1+j2+1][1]=(tmp[j1+j2+1][1]+dp[u][j1][0]*dp[v][j2][0])%mod;
                }
            }
        }
        for(int i=0;i<=n;i++){
            dp[u][i][0]=tmp[i][0];
            dp[u][i][1]=tmp[i][1];
        }
		sz[u]+=sz[v];
    }
}
int f(int i){
    return (dp[2*n][i][0]+dp[2*n][i][1])*s[n-i]%mod;
}
signed main(){
    freopen("pro.in","r",stdin);
    freopen("pro.out","w",stdout);
    cin>>n;
	for(int i=1;i<=2*n-1;i++){
		cin>>p[i];
		tr[i].push_back(p[i]);
		tr[p[i]].push_back(i);
	}
    jie[0]=1; 
	p2[0]=1; 
	jieni[0]=1; 
	p2ni[0]=1;
    for(int i=1;i<=2*n;i++){
        jie[i]=jie[i-1]*i%mod;
        jieni[i]=qpow(jie[i],mod-2);
        p2[i]=p2[i-1]*2%mod;
        p2ni[i]=qpow(p2[i],mod-2);
    }
    for(int i=1;i<=2*n;i++){
        for(int j=0;j<=i;j++){
            if(j==0||j==i)c[i][j]=1;
            else c[i][j]=jie[i]*jieni[j]%mod*jieni[i-j]%mod;
        }
    }
    for(int i=0;i<=n;i++){
        if(i==0)s[0]=1;
        else s[i]=c[2*i][i]*jie[i]%mod*p2ni[i]%mod;
    }
    dfs(2*n,0);
    int ans=0;
	for(int i=0;i<=n;i++){
		if(i%2)ans=(ans-f(i)+mod)%mod;
		else ans=(ans+f(i))%mod;
	}
	cout<<ans;
    return 0;
}
posted on 2026-07-16 20:53  郑浩崎  阅读(2)  评论(0)    收藏  举报