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]\) 的最小值为
最大值为
这里对右半部分的处理为加上左边结束后的结果。
复杂度 \(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 对矛盾的人的方案个数,剩下的人随意,那么最终答案即为
我们发现,讨厌的关系是个树形结构,因为一个人只会讨厌成绩比自己差的人,所以如果一个人 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;
}
浙公网安备 33010602011771号