Codeforces Round 1073 Div1 + Div2 部分题目题解
比赛传送门:Codeforces Round 1073。
打的 Div2。AB 赛时切了。
C 题解
设 \(s\) 升序排序后为 \(t\),那么如果 \(s=t\) 显然 Bob 胜利,否则我们断言 Alice 一定胜利。
如果 Alice 可以一步将 \(s\) 变成 \(t\),那么他就赢了,而我们发现如果 Alice 操作 \(s\) 与 \(t\) 不相等那部分,显然可以将其排序,做完了。
#include<bits/stdc++.h>
#define int long long
#define double long double
using namespace std;
inline int read(){
char c=getchar();
int f=1,ans=0;
while(c<48||c>57) f=(c==45?f=-1:1),c=getchar();
while(c>=48&&c<=57) ans=(ans<<1)+(ans<<3)+(c^48),c=getchar();
return ans*f;
}
inline void solve(){
int n=read();
string s,t;cin>>s;t=s;
sort(t.begin(),t.end());
if (s==t){
puts("Bob");
return ;
}
puts("Alice");
vector<int>anss;
for (int i=0;i<n;i++) if (s[i]!=t[i]) anss.push_back(i);
printf("%lld\n",(int)anss.size());
for (auto i:anss) printf("%lld ",i+1);puts("");
}
main(){
int T=read();
while(T--) solve();
return 0;
}
D1 题解
枚举使得 \(t\) 比 \(s\) 优的位置 \(i\) 满足 \(s_i\) 为右括号,那么 \(t\) 前 \(i-1\) 与 \(s\) 前 \(i-1\) 位相同,找到第一个 \(j>i\) 满足 \(s_j\) 为左括号。
因此 \(t\) 中我们一定不能选 \(i+1\) 到 \(j\) 这些位置,为了让 \(t\) 是正则的,后面还不能选 \(j-i\) 个左括号,判一下是否有这么多即可,答案即为 \(\max\{n-2(j-i)\}\)。
证明这样的 \(t\) 是正则的,可以把左括号看成 \(1\),右括号看成 \(-1\),然后分析即可。
所以显然答案只有 \(-1\) 或 \(n-2\)。
#include<bits/stdc++.h>
#define int long long
#define double long double
using namespace std;
const int N=2e5+10;
inline int read(){
char c=getchar();
int f=1,ans=0;
while(c<48||c>57) f=(c==45?f=-1:1),c=getchar();
while(c>=48&&c<=57) ans=(ans<<1)+(ans<<3)+(c^48),c=getchar();
return ans*f;
}
int n,a[N],b[N];
inline void solve(){
n=read();
string s;cin>>s;s=" "+s;
for (int i=0;i<=n+1;i++) a[i]=b[i]=0;
for (int i=n;i>0;i--) b[i]=b[i+1]+(s[i]=='(');
a[n+1]=n+1;
for (int i=n;i>0;i--) if (s[i]=='(') a[i]=i;else a[i]=a[i+1];
int ans=-1;
for (int i=1;i<=n;i++) if (s[i]==')'&&a[i]<=n&&b[a[i]+1]>=a[i]-i) ans=max(ans,n-2*(a[i]-i));
printf("%lld\n",ans);
}
main(){
int T=read();
while(T--) solve();
return 0;
}
D2 题解
首先你要会 D1,容易发现这里的贡献只有 \(0,|t|-2\) 两种。
主播主播,官解 \(O(n^3)\) 确实很强,但还是太吃脑子了,有没有更加简单强势的 \(O(n^4)\) 推荐一下。
有的兄弟有的,我们发现如果这个子序列要有贡献,当且仅当存在位置 \(i\) 使得 \(s_i=\texttt{)}\) 且 \(s_{i+1}=\texttt{(}\),且存在 \(j>i+1\) 使得 \(s_j=\texttt{(}\)。
按照这个设计 dp,设 \(f_{i,j,k,0/1,0/1}\),以 \(i\) 结尾的子序列,且子序列长度为 \(j\),且子序列中的左括号看成 \(1\),右括号看成 \(-1\) 的总和为 \(k\),是否存在连续的 \(\texttt{)(}\) 以及之后是否存在 \(\texttt{(}\) 的方案数。
转移的话枚举上一位分讨,记上一位为 \(l\)。
- \(s_i=\texttt{(}\) 且 \(s_l=\texttt{(}\)
- \(s_i=\texttt{(}\) 且 \(s_l=\texttt{)}\)
- \(s_i=\texttt{)}\)
最后答案直接枚举 \(i,j\),贡献即为 \(f_{i,j,0,1,1}(j-2)\)。
#include<bits/stdc++.h>
#define int long long
#define double long double
using namespace std;
inline int read(){
char c=getchar();
int f=1,ans=0;
while(c<48||c>57) f=(c==45?f=-1:1),c=getchar();
while(c>=48&&c<=57) ans=(ans<<1)+(ans<<3)+(c^48),c=getchar();
return ans*f;
}
const int N=110,mod=998244353;
int f[N][N][N][2][2],n;
string s;
inline void add(int &x,int y){x=(x+y)%mod;}
inline void solve(){
n=read();cin>>s;s=" "+s;
for (int i=0;i<=n+1;i++) for (int j=0;j<=n+1;j++) for (int k=0;k<=n;k++) f[i][j][k][0][0]=f[i][j][k][0][1]=f[i][j][k][1][0]=f[i][j][k][1][1]=0;
for (int i=1;i<=n;i++) if (s[i]=='(') f[i][1][1][0][0]=1;
for (int i=2;i<=n;i++) for (int j=1;j<=n;j++) for (int k=0;k<=n;k++) for (int l=1;l<i;l++)
if (s[i]=='('&&s[l]=='('&&k-1>=0){
add(f[i][j][k][0][0],f[l][j-1][k-1][0][0]);
add(f[i][j][k][1][0],f[l][j-1][k-1][1][0]);
add(f[i][j][k][1][1],f[l][j-1][k-1][1][0]);
}
else if (s[i]=='('&&s[l]==')'&&k-1>=0){
add(f[i][j][k][0][0],f[l][j-1][k-1][0][0]);
add(f[i][j][k][1][0],f[l][j-1][k-1][0][0]);
add(f[i][j][k][1][1],f[l][j-1][k-1][1][0]);
}
else if (s[i]==')'&&k+1<=n){
add(f[i][j][k][0][0],f[l][j-1][k+1][0][0]);
add(f[i][j][k][1][0],f[l][j-1][k+1][1][0]);
add(f[i][j][k][1][1],f[l][j-1][k+1][1][1]);
}
int ans=0;
for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) add(ans,f[i][j][0][1][1]*(j-2)%mod);
printf("%lld\n",ans);
}
main(){
int T=read();
while(T--) solve();
return 0;
}
主播主播,你的 \(O(n^4)\) 确实很强,但还是太吃时间了,有没有更加简单强势的 \(O(n^3)\) 推荐一下。
有的兄弟有的,我们发现如果这个子序列要有贡献,当且仅当存在位置 \(i\) 使得 \(s_i=\texttt{)}\) 且 \(s_{i+1}=\texttt{(}\),且存在 \(j>i+1\) 使得 \(s_j=\texttt{(}\)。
考虑放宽限制,可以发现只需存在一个子序列 \(\texttt{)((}\) 即可,证明考虑调整法调整成前面的情况。
这样就可以考虑 dp 了,设 \(f_{i,j,k,0/1/2/3}\) 表示前 \(i\) 个位置长度为 \(j\) 的子序列且子序列中的左括号看成 \(1\),右括号看成 \(-1\) 的总和为 \(k\),且子序列匹配了 \(\texttt{)((}\) 的前 \(0/1/2/3\) 位。
转移考虑按照这一位选择不不选择分讨。
- 不选择 \(i\) 位置。
- 选择 \(i\) 且 \(s_i=\texttt{(}\)
- 选择 \(i\) 且 \(s_i=\texttt{)}\)
最后答案枚举 \(j\),贡献即为 \(f_{n,j,0,3}(j-2)\)。
#include<bits/stdc++.h>
#define int long long
#define double long double
using namespace std;
inline int read(){
char c=getchar();
int f=1,ans=0;
while(c<48||c>57) f=(c==45?f=-1:1),c=getchar();
while(c>=48&&c<=57) ans=(ans<<1)+(ans<<3)+(c^48),c=getchar();
return ans*f;
}
const int N=110,mod=998244353;
int f[N][N][N][4],n;
string s;
inline void add(int &x,int y){x=(x+y)%mod;}
inline void solve(){
n=read();cin>>s;s=" "+s;
for (int i=0;i<=n+1;i++)
for (int j=0;j<=n+1;j++)
for (int k=0;k<=n+1;k++)
for (int l=0;l<4;l++) f[i][j][k][l]=0;
f[0][0][0][0]=1;
for (int i=1;i<=n;i++) for (int j=0;j<=n;j++) for (int k=0;k<=n;k++){
add(f[i][j][k][0],f[i-1][j][k][0]);
add(f[i][j][k][1],f[i-1][j][k][1]);
add(f[i][j][k][2],f[i-1][j][k][2]);
add(f[i][j][k][3],f[i-1][j][k][3]);
if (s[i]=='('&&j-1>=0&&k-1>=0){
add(f[i][j][k][0],f[i-1][j-1][k-1][0]);
add(f[i][j][k][1],f[i-1][j-1][k-1][1]);
add(f[i][j][k][2],f[i-1][j-1][k-1][1]);
add(f[i][j][k][3],f[i-1][j-1][k-1][2]);
}
if (s[i]==')'&&j-1>=0&&k+1<=n){
add(f[i][j][k][0],f[i-1][j-1][k+1][0]);
add(f[i][j][k][1],f[i-1][j-1][k+1][0]);
add(f[i][j][k][2],f[i-1][j-1][k+1][2]);
add(f[i][j][k][3],f[i-1][j-1][k+1][3]);
}
}
int ans=0;
for (int i=2;i<=n;i++) add(ans,f[n][i][0][3]*(i-2)%mod);
printf("%lld\n",ans);
}
main(){
int T=read();
while(T--) solve();
return 0;
}

浙公网安备 33010602011771号