2023.8.7
Bronya19C 场。
转圈圈
一个长为 \(n\) 的 \(01\) 串 \(S\),串中有且仅有一个 \(1\),你可以操作若干次,每次可以将一个长为 \(k\) 的子串反转。
对每个 \(i\) 询问 \(1\) 至少几步可以翻转到位置 \(i\),另外地,一些位置在操作的过程中不能有 \(1\).
对于 \(i\),如果不存在这个最小步数,输出 \(-1\).
\(n\le 10^5\).
如果 \(i\) 能翻转到 \(j\),那么有:
-
\(|i-j|<k\)
-
\(k+1\le i+j\le 2n-(k-1)\)
-
\(i-j\not\equiv k\space(\operatorname{mod}2)\)
每次能转移到的点一定为一段区间内的全体偶数或奇数。
拿两个 set 存未访问过的奇点和偶点,把取出的点用作下一次的 bfs.
写的时候 lower_bound
一下然后不断跳就行了。
这么简单的东西怎么没想出来。
时间复杂度 \(O(n\log n)\).
#include<bits/stdc++.h>
#define sit set<int>::iterator
#define N 100010
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int n,k,m,s;
bool flag[N];
int ans[N];
set<int>s0,s1;
queue<int>q,del;
int main(){
n=read(),k=read(),m=read(),s=read();
for(int i=1;i<=m;i++)
flag[read()]=true;
if(k>n||k==1){
for(int i=1;i<=n;i++)
i==s?printf("0 "):printf("-1 ");
printf("\n");
return 0;
}
for(int i=1;i<=n;i++){
if(flag[i]||i==s)continue;
i&1?s1.insert(i):s0.insert(i);
}
memset(ans,-1,sizeof(ans)),ans[s]=0;
q.push(s);
while(!q.empty()){
int i=q.front();q.pop();
int L=max(1,k+1-i),R=min(n,2*n-(k-1)-i);
L=max(L,i-k+1),R=min(R,k+i-1);
if((i-k)&1){
sit it=s0.lower_bound(L);
for(;it!=s0.end()&&*it<=R;++it)
ans[*it]=ans[i]+1,del.push(*it),q.push(*it);
while(!del.empty())
s0.erase(del.front()),del.pop();
}
else{
sit it=s1.lower_bound(L);
for(;it!=s1.end()&&*it<=R;++it)
ans[*it]=ans[i]+1,del.push(*it),q.push(*it);
while(!del.empty())
s1.erase(del.front()),del.pop();
}
}
for(int i=1;i<=n;i++)
printf("%d ",ans[i]);
printf("\n");
return 0;
}
括号匹配
串 \(S\) 有左右括号和通配符 ?
,问 \(S\) 的多少子串可以成为合法括号串。
\(|S|\le 10^6\).
Sub1:只有通配符,\(n\le 10^6\)
枚举为偶数的长度 \(len\) 即可。
Sub2:没有通配符,\(n\le 10^6\)
记 (
为 \(1\),)
为 \(-1\),作前缀和。
枚举区间开始位置 \(i\),对于结束位置 \(j\) 应有
对于 \(s\) 跑一遍单调栈,这个相等的条件可以设一个偏移量 \(\Delta\),然后放在 vector 里二分。
Sub3,4:\(n\le 2000\)
一个合法的括号串可以以如下方式拓展:
-
在两端添加一个合法的括号串
-
在左端添加
(
,右端添加)
这样就容易写出一个 \(O(n^3)\) 的 dp,由于剪枝效率会比较高。
Sub5:\(n\le 10^6\)
一个区间合法当且仅当:
-
区间长度为偶数
-
令
(
和?
为 \(1\),)
为 \(-1\),前缀和数组的每一项 \(\ge0\). -
令
)
和?
为 \(1\),(
为 \(-1\),后缀和数组的每一项 \(\ge0\).
这样可以通过调整,使得前缀和数组非负且最后一项为 \(0\).
对于 \(l\),可以预处理出 \(P_l\),表示应有 \(l\le r\le P_l\).
对于 \(r\),可以预处理出 \(Q_r\),表示应有 \(Q_r\le l\le r\).
两者使用单调栈处理。
枚举 \(l\),问有多少 \(r\in[l,P_l]\) 满足 \(Q_r\le l\).
容易将其转化为二维数点问题。开奇偶两个树状数组即可。
时间复杂度 \(O(n\log n)\).
#include<bits/stdc++.h>
#define ll long long
#define N 1000010
using namespace std;
char c[N];int n;
int s[N],P[N],Q[N];
int st[N],top;
int tot;
struct query{
int l,x,flag,m2;
bool operator<(const query &t)const{
return l<t.l;
}
}q[N<<1];
#define lb(x) x&-x
int t1[N],t2[N];
void add(int *t,int x,int k){
for(;x<=n;x+=lb(x))t[x]+=k;
}
int qry(int *t,int x){
int ret=0;
for(;x;x-=lb(x))ret+=t[x];
return ret;
}
int main(){
scanf("%s",c+1),n=strlen(c+1);
for(int i=1;i<=n;i++)
s[i]=s[i-1]+(c[i]==')'?-1:1);
st[top=1]=0;
for(int i=1;i<=n;i++){
while(top&&s[st[top]]>s[i])
P[st[top--]+1]=i-1;
st[++top]=i;
}
while(top)P[st[top--]+1]=n;
for(int i=n;i;i--)
s[i]=s[i+1]+(c[i]=='('?-1:1);
st[top=1]=n+1;
for(int i=n;i;i--){
while(top&&s[st[top]]>s[i])
Q[st[top--]-1]=i+1;
st[++top]=i;
}
while(top)Q[st[top--]-1]=1;
for(int i=1;i<=n;i++){
if(i>P[i])continue;
q[++tot]=(query){P[i],i,1,i&1};
if(i>1)q[++tot]=(query){i-1,i,-1,i&1};
}
sort(q+1,q+1+tot);
ll ans=0;
for(int i=1,j=1;i<=n&&j<=tot;i++){
if(i>=Q[i])add(i&1?t1:t2,Q[i],1);
while(j<=tot&&q[j].l==i)
ans+=q[j].flag*qry(q[j].m2?t2:t1,q[j].x),j++;
}
printf("%lld\n",ans);
return 0;
}
崩原之战
你玩元神吗。
P8908 [USACO22DEC] Palindromes P
一个串的价值是不断交换串中的某两个相邻的字符,使其成为回文串的最小次数。如果其不能被重排为回文串,其价值为 \(-1\).
求 \(S\) 的所有子串的价值和。
\(|S|\in\{100,200,500,1000,2000,5000,7500\}\),\(\Sigma=\{H,G\}\).
字符集也对上了。
先把字符串变成 \(01\) 串。
首先如果子串长度为偶数而 \(01\) 都有奇数个,答案显然为 \(-1\).
思考怎么操作能使得答案最小。
假设只考虑 \(1\),不断将它们首尾配对,若剩下一个则放在正中间。
一定存在一种最优的方案使得两个 \(1\) 中至少一个不移动。
记共 \(m\) 个 \(1\),第 \(i\) 个 \(1\) 的位置是 \(a_i\),操作数为
前面一部分是对中心点的计算。时间复杂度 \(O(n^3)\).
考虑枚举 \(a_i,a_{m-i+1}\) 并计算它们配对产生的贡献。如果 \(a_i\) 和 \(a_j\) 配对,那么有 \(l\le a_i,r\ge a_j\),若 \(j-i+1\) 为奇数那么 \(r-l+1\) 也为奇数。
\(i,j\) 的信息可以从 \(i-1,j+1\) 得来。从 \(a\) 的每段前缀和后缀出发,每次砍掉首尾两个元素,把新的 \([l,r]\) 扔进 DS 里,那么新产生的 \([l,r]\) 就是 \(l\in(a_{i-1},a_i]\),\(r\in[a_j,a_{j+1})\) 对应的所有区间。
枚举 \(l+r\),算一下这个东西的出现次数,一起扔进 DS 里。
最后想想这个 DS 要干什么。
维护一个可重集,查询 \(\le x\) 的元素之和和元素个数。\(x\) 的上界显然为 \(2n\),开两个 BIT 即可。
时间复杂度 \(O(n^2\log n)\).
这个牛逼计数感性理解一下吧。
#include<bits/stdc++.h>
#define ll long long
#define N 7510
#define lb(x) x&-x
using namespace std;
char ch[N];int n,cnth,cntg;bool a[N];
int bit1[N<<1],cnt;ll bit2[N<<1],sum;
void add(int c,int k){
sum+=1ll*c*k,cnt+=c;
for(int x=k;x<=n*2;x+=lb(x))
bit1[x]+=c,bit2[x]+=1ll*c*k;
}
int qry1(int x){
int ret=0;
for(;x;x-=lb(x))ret+=bit1[x];
return ret;
}
ll qry2(int x){
ll ret=0;
for(;x;x-=lb(x))ret+=bit2[x];
return ret;
}
void work(int l,int r,int L,int R,bool fl){
r-=l,R-=L;
for(int i=0,_l,_r;i<=r+R;i++){
if(fl&&((i+l+L)&1))continue;
_l=max(0,i-R),_r=min(i,r);
add(_r-_l+1,i+l+L);
}
}
int c0[N],c1[N],pos[N],m;
ll ans;
void solve(int l,int r){
bool fl=(r-l+1)&1;
cnt=sum=0,memset(bit1,0,sizeof(bit1)),memset(bit2,0,sizeof(bit2));
while(l<=r){
work(pos[l-1]+1,pos[l],pos[r],pos[r+1]-1,fl);
int x=pos[l]+pos[r];
ll val=sum-2*qry2(x)+1ll*x*(2*qry1(x)-cnt);
ans+=(l==r)?val/2:val;
l++,r--;
}
}
int main(){
scanf("%s",ch+1),n=strlen(ch+1);
for(int i=1;i<=n;i++)
ch[i]=='H'?cnth++:cntg++;
for(int i=1;i<=n;i++){
a[i]=(ch[i]=='H')^(cnth>cntg);
if(a[i])pos[++m]=i;
c0[i]=c0[i-1]+!a[i];
c1[i]=c1[i-1]+a[i];
}
pos[m+1]=n+1;
for(int i=1;i<=m;i++)solve(1,i);
for(int i=2;i<=m;i++)solve(i,m);
for(int l=1,x,y;l<=n;l++)
for(int r=l;r<=n;r++){
x=c0[r]-c0[l-1],y=c1[r]-c1[l-1];
if((x&1)&&(y&1))ans--;
}
printf("%lld\n",ans);
return 0;
}
抽卡
上面那个词被 Cnblogs BAN 了。
极其好的期望题,也很难。
已知 \(n\) 个长为 \(l\) 的二进制数,每一次交互有 \(p_i\) 的概率得到第 \(i\) 位的二进制值。
对于每个数,求出能唯一确定这个二进制数的期望交互次数。答案对 \(998244353\) 取模。
多组测试数据。
\(T\le 10\),\(l\le 15\),\(n\le 2^l\),\(\frac{p_i}{10^{-4}}\in[1,10^4]\cap\mathbb Z\),\(n\) 个数两两不同。
求操作次数的期望有一个经典套路,就是根据期望的线性性,求出到达每个合法状态的概率 \(P_S\),乘上停留在该状态的期望时间 \(t_S\),然后求和。
设 \(P_S\) 为停留在状态 \(S\) 的概率,有 \(\displaystyle t_S=\frac{1}{1-P_S}\).
从 \(P_S\) 转移至 \(P_T\) 的系数是 \(\displaystyle\prod_{i\in T,i\not\in S}p_i\times\prod_{i\not\in T}(1-p_i)\times\frac{1}{1-P_S}\).
预处理 \(\prod p_i\) 和 \(\prod(1-p_i)\),枚举 \(S\) 的超集 \(T\) 可做到 \(O(3^l)\).
对于串 \(s\) 记 \(T\) 为能唯一确定 \(s\) 的集合,那么答案为 \(\sum_{S\not\in T}P_St_S\).
容斥得 \(ans=\sum_{S}P_St_S-\sum_{S\in T}P_St_S\)
状态 \(S\) 每位的状态是 \(\{0,1,?\}\),所以 \(S\) 的个数为 \(O(3^l)\),易得 \(\sum|T|\le 3^l\).
求哪些状态可以唯一确定字符串。
记 \(f_S\) 为能确定的唯一字符串的编号,不存在则为 \(0\),不唯一则为 \(-1\).
初始化 \(2^l\) 个已经确定 \(01\) 了的 \(f_S\),记录为 \(id\) 或 \(0\).
主动转移的复杂度是 \(O(3^ll)\).
被动转移,考虑 \(S\) 有若干位 \(?\),选择其中一个并填入 \(0/1\),能够做到转移复杂度 \(O(1)\).
总复杂度 \(O(T3^l)\).
代码写起来真的很抽象。放一份 luogu 上面的 code。
#include<bits/stdc++.h>
#define R 14400000
#define L 16
#define N 1<<L
#define P 998244353
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int qpow(int k,int b){
int ret=1;
while(b){
if(b&1)ret=1ll*ret*k%P;
k=1ll*k*k%P,b>>=1;
}
return ret;
}
const int i10000=qpow(10000,P-2);
int T,l,n,c[N],a[N];
int pw[L];
int gets(){
int x=0,cur=0;char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))x+=(1<<cur)*(ch^48),cur++,ch=getchar();
return x;
}
int cnt[R],pcnt[R],id[R],val[R];
int hav[N],dis[N],f[N],ans[N];
int main(){
T=read();
pw[0]=1;for(int i=1;i<L;i++)pw[i]=3*pw[i-1];
while(T--){
l=read(),n=read();
for(int i=0;i<l;i++)
c[i]=1ll*read()*i10000%P;
for(int i=1,s;i<=n;i++){
s=gets(),a[i]=0;
for(int j=1;j<=l;j++)
a[i]+=pw[l-j]*((s&(1<<j-1))?1:0);
if(n!=1)id[a[i]]=i;
}
if(n==1){
puts("0");
continue;
}
memset(f,0,sizeof(f));
memset(cnt,0,sizeof(cnt)),cnt[0]=0;
for(int i=0;i<l;i++){
for(int k=0;k<pw[i];k++){
pcnt[k*3]=pcnt[k*3+1]=cnt[k]+1;
pcnt[k*3+2]=0;
}
for(int k=0;k<pw[i+1];k++)
cnt[k]=pcnt[k],pcnt[k]=0;
}
for(int i=0;i<pw[l];i++){
if(cnt[i]>=l)continue;
val[i]=val[i-pw[cnt[i]]]^(1<<l-cnt[i]-1);
int nxt=i-pw[cnt[i]];
if(!id[i])id[i]=id[nxt];
else if(id[nxt]!=id[i]&&id[nxt]!=0)id[i]=-1;
nxt=i-2*pw[cnt[i]];
if(!id[i])id[i]=id[nxt];
else if(id[nxt]!=id[i]&&id[nxt]!=0)id[i]=-1;
}
for(int i=0;i<(1<<l);i++){
hav[i]=dis[i]=1;
for(int j=0;j<l;j++){
if(!(i&(1<<j)))continue;
hav[i]=1ll*hav[i]*c[j]%P,dis[i]=1ll*dis[i]*(P+1-c[j])%P;
}
}
f[(1<<l)-1]=1,ans[0]=0;
for(int i=(1<<l)-1;i>=0;i--){
f[i]=1ll*f[i]*qpow((P+1-dis[i])%P,P-2)%P;
for(int S=i,s;S;S=(S-1)&i){
s=S^i;
(f[s]+=1ll*f[i]*hav[i^s]%P*dis[s]%P)%=P;
}
(ans[0]+=f[i])%=P;
}
for(int i=1;i<=n;i++)ans[i]=ans[0];
for(int i=0;i<pw[l];i++){
if(id[i]>0)(ans[id[i]]+=P-f[val[i]])%=P;
id[i]=0;
}
for(int i=1;i<=n;i++)
printf("%d\n",ans[i]);
}
return 0;
}