Educational Codeforces Round 153 A~E
Educational Codeforces Round 153
A-Not a Substring
题意:给出一个长为 \(n\) 的序列,只含有左右括号,要求给出一个长为 \(2n\) 的合法括号序列,满足原序列不是该括号序列的子串。
判断是否有解,若有解,给出一个合法括号序列。
显然,我们先判掉原序列为 "()" 的情况,一个合法括号序列必然要出现该图形。
其次,容易发现,对于存在 "((" 或者 "))" 连续段的,则 "()()()……" 此类必然是一个解。
如果不存在,则显然 ((((())))) 此类是一个解。
B-Fancy Coins
题意:你有 \(a_1\) 个面值为1的普通硬币,\(a_k\) 个面值为 \(k\) 的普通硬币,以及无限个面值为1和 \(k\) 的印花硬币。
现在需要买一个价值为 \(s\) 的物品,求最少的印花硬币使用量。
首先,我们需要将 \(s\) 用一元硬币化为 \(s'\) 使得 \(k|s'\)。这一步的代价是 \(\max(0,(s\bmod k)-a_1)\)。记录剩下的一元普通硬币个数 \(a'_1\)
然后,我们将 \(a_k\leftarrow a_k+\lfloor\frac{a'_1}{k}\rfloor\)。只需判断还需要几个印花硬币就可以凑出即可。
int n,k,a1,ak;
cin>>n>>k>>a1>>ak;
int ans=0;
if(a1>=n%k)a1-=n%k,n-=n%k;
else ans+=n%k-a1,a1=0,n-=n%k;
int s=n/k;
if(s<=ak+a1/k)cout<<ans<<"\n";
else{
int t=a1/k;ak+=t;
ans+=s-ak;
cout<<ans<<"\n";
}
C-Game on Permutation
题意:有两个人轮流操作。最初有一个序列 \(a\),是一个 \(n\) 的排列。
有一个筹码,最初由先手任意放在一个位置上,接着后/先手交替移动,每一次可以将筹码从 \(i\) 移动到 \(j\),满足 \(j<i,a_j<a_i\)
当一个棋子不可以移动时,当前轮到的人获胜。
对于博弈论问题,我们考虑必胜态与必败态。
必败态是什么?也即后手可以通过移动一次筹码将其移动到一个能且仅能移动再一次的位置上。
那么必胜态?也即这个位置满足无论后手如何移动,它都只能移动到一个再不能移动的位置。
我们先考虑不可移动的位置 \(i\),它显然满足 \(a_i=\min_{j\le i} a_j\)。
然后考虑如何维护再不能移动的位置。
显然,我们可以通过权值树状数组维护当前节点 \(i\) 有多少个点可以移动到,又有多少个点可以移动到且再不能移动。
当这二者相等且不为0的时候,说明当前节点是一个必胜点。
read(n);
for(int i=1;i<=n;i++){
read(a[i]);c[i]=b[i]=d[i]=0;
}
int mn=n,ans=0;
for(int i=1;i<=n;i++){
mn=min(mn,a[i]);
if(mn==a[i])b[i]=1;
}
for(int i=1;i<=n;i++){
if(ask(a[i])==ask2(a[i])&&ask(a[i])!=0)++ans;
add(a[i],b[i]);add2(a[i],1);
}
cout<<ans<<"\n";
D-Balanced String
题意:给定一个01序列 \(a\),记录 \(c_1=\sum_{i<j}[a_i=1][a_j=0],c_0=\sum_{i<j}[a_i=0][a_j=1]\)。
每一次可以交换任意两个数,求使得 \(c'_0=c'_1\) 的最小操作次数。
\(1\le |a|\le 100\)
启发:可以确定的信息尽量确定。
首先我们可以统计出 \(c_0,c_1\) 表示0/1的个数,则 01对和10对的总数显然是 \(2s={n\choose 2}-{c_0\choose 2}-{c_1\choose 2}\)。
所以,我们让 \(c'_0=c'_1\),本质上是让"01"对的个数为 \(s\)。
现在我们想想,假设我们已经知道了 \(p_1\sim p_{c_1}\) 表示 \(1\) 的位置。则“01”对的个数是好求的,即为:\(\sum_{i=1}^{c_1}p_i-i=\sum{p_i}-\frac{c_1(c_1+1)}{2}\)。
所以容易知道,本质上是让 \(\sum p_i=s+\frac{c_1(c_1+1)}{2}\)。
关于这种问题,是容易解决的。设 \(f_{i,j,k}\) 为在前 \(i\) 个数中,选出了 \(j\) 个1,且下标总和为 \(k\) 的最小代价。
则显然有 \(f_{i,j,k}=\min(f_{i-1,j,k},f_{i-1,j-1,k-i}+[a_i=0])\)。
不过空间需要滚动一下。
int main(){
cin>>b+1;n=strlen(b+1);
for(int i=1;i<=n;i++)a[i]=b[i]-'0',c1+=a[i];
c0=n-c1;int cnt=n*(n-1)/2-c0*(c0-1)/2+c1*(c1-1)/2;cnt/=2;cnt+=c1;
memset(f,0x3f,sizeof f);
f[0][0][0]=0;
for(int i=1;i<=n;i++){
for(int j=0;j<=min(c1,i);j++){
for(int k=0;k<=cnt;k++){
f[i&1][j][k]=min(f[i&1][j][k],f[(i^1)&1][j][k]);
if(k>=i&&j)f[i&1][j][k]=min(f[i&1][j][k],f[(i^1)&1][j-1][k-i]+(a[i]==0));
}
}
}
cout<<f[n&1][c1][cnt]<<"\n";
}
E-Fast Travel Text Editor
题意:给定一个长为 \(n\) 的字符序列 \(a\),有一个光标,它只会停留在两个字符之间,或者最后一个字符后,或者第一个字符前。
光标移动的规则如下:
每一次移动,从下列三种方案中选择一个:
- 从 \((x,x+1)\) 移动到 \((x-1,x)\),前提是 \(x\neq 0\)
- 从 \((x,x+1)\) 移动到 \((x+1,x+2)\),前提是 \(x+1\neq n\)
- 从 \((x,x+1)\) 移动到 \((k,k+1)\),满足 \(a_x=a_k,a_{x+1}=a_{k+1}\)
现给出若干询问,每次给出 \(x,y\),求光标从 \((x,x+1)\) 移动到 \((y,y+1)\) 的最小代价。
\(1\le m\le 5\times 10^4\)
这个题,很明显是个最短路题,但怎么搞最短路是最重要的。
注意到求 \(x\rightarrow y\) 的最短路线,则有两种情况:
- 直接暴力移动,答案 \(|x-y|\)
- 找到一对传送点 \((k,k+1)\),两边自己处理。
有一个直接的想法是对于 \(26^2\) 种字符组合各建一个虚点,将这种字符组合的位置与其相连,边权为1。
然后对于正常移动,边权为2。最后将答案除2即可解决问题。但这样问题就化为了一个全源最短路问题,强行跑dijkstra也是必TLE。
那么有没有更高效的做法?
注意到在本题中,操作可逆,那么我们换个方向,何不考虑组合答案?
我们的起点并不需要将 \(1\sim n\) 全部跑完,而是可以选择查询换预处理,因为这里查询和预处理的复杂度明显不对齐,上面也提到了,第一种情况可以直接算答案,第二种情况可以通过枚举字符对以 \(26^2\) 次查询得到答案。
我们仅仅需要将每一个新建的虚点作为起点跑最短路即可。甚至在这里,我们不需要建图,直接BFS求最短路。但需要注意的是,这里不需要建立出虚点,但需要标记虚点是否走过。(否则会出现某个虚点连很多条边的情况,导致vis判太多次超时)
int main(){
ios::sync_with_stdio(false);
cin>>a+1;n=strlen(a+1);
cin>>m;memset(dis,-1,sizeof dis);
for(int i=1;i<n;i++)p[a[i]-'a'][a[i+1]-'a'].push_back(i);
for(int i=0;i<26;i++){
for(int j=0;j<26;j++){
if(p[i][j].empty())continue;
for(int s=0;s<26;s++)for(int t=0;t<26;++t)vis[s][t]=0;
queue<int>q;
for(auto x:p[i][j]){
dis[i][j][x]=0;q.push(x);
}
while(!q.empty()){
int x=q.front();q.pop();
if(x>1&&dis[i][j][x-1]==-1){
dis[i][j][x-1]=dis[i][j][x]+1;
q.push(x-1);
}
if(x<n-1&&dis[i][j][x+1]==-1){
dis[i][j][x+1]=dis[i][j][x]+1;
q.push(x+1);
}
if(vis[a[x]-'a'][a[x+1]-'a'])continue;
vis[a[x]-'a'][a[x+1]-'a']=1;
for(auto k:p[a[x]-'a'][a[x+1]-'a']){
if(dis[i][j][k]==-1){
dis[i][j][k]=dis[i][j][x]+1;
q.push(k);
}
}
}
}
}
while(m--){
int x,y;cin>>x>>y;
int ans=abs(x-y);
for(int i=0;i<26;i++)for(int j=0;j<26;j++)if(!p[i][j].empty())ans=min(ans,dis[i][j][x]+dis[i][j][y]+1);
cout<<ans<<"\n";
}
}