2023.8.7测试
当然也搬了很多原题
T1 转圈圈
一个长度为 \(n\) 的 \(01\) 串,初始时只有 \(s\) 位置上是 \(1\)。一次操作可以使一个长度为 \(k\) 的字串翻转。对于每个位置 \(i\),你要求出最少需要几次操作使得这个位置为 \(1\),否则输出 \(-1\)。
同时,串中存在 \(m\) 个禁止位置,这些位置永远都不能为 \(1\)
\(1\leq n\leq 10^5\)
为了卡常最后时刻手写队列直接写成栈,喜提 \(0\) 分
手玩可注意到一个点可以往一段区间内奇偶性相同的点连边权为 \(1\) 的边,直接连边再 \(\rm bfs\) 复杂度 \(O(n^2)\)。于是选择线段树优化建图,时间复杂度 \(O(n\log n+\log n)\)
#include<bits/stdc++.h>
#define mp make_pair
using namespace std;
const int N=100010,M=1000010;
int n,m,k,s,a[N],f[M],id[N],cnt;
bool v[N],vis[M];
vector < pair<int,int> > g[M];
deque <int> q;
struct SegmentTree
{
int idd[4*N],pos[N];
void build(int p,int l,int r)
{
if(l==r)
{
idd[p]=pos[l]=++cnt;
return;
}
idd[p]=++cnt;
int mid=(l+r)>>1;
build(p*2,l,mid); g[idd[p]].push_back(mp(idd[p*2],0));
build(p*2+1,mid+1,r); g[idd[p]].push_back(mp(idd[p*2+1],0));
}
void add(int p,int l,int r,int ql,int qr,int x)
{
if(ql<=l && qr>=r)
{
g[x].push_back(mp(idd[p],1));
return;
}
int mid=(l+r)>>1;
if(ql<=mid)
add(p*2,l,mid,ql,qr,x);
if(qr>mid)
add(p*2+1,mid+1,r,ql,qr,x);
}
}tree[2];
void bfs() //重点注意01bfs的写法
{
f[id[s]]=0; q.push_front(id[s]);
while(q.size())
{
int x=q.front(); q.pop_front();
if(vis[x])
continue;
vis[x]=1;
for(int i=0; i<g[x].size(); i++)
{
int y=g[x][i].first,z=g[x][i].second;
if(f[y]==-1 || f[x]+z<f[y])
{
f[y]=f[x]+z;
if(z==0)
q.push_front(y);
else
q.push_back(y);
}
}
}
}
int main()
{
memset(f,-1,sizeof(f));
scanf("%d%d%d%d",&n,&k,&m,&s);
for(int i=1; i<=m; i++)
{
int x;
scanf("%d",&x);
v[x]=1;
}
tree[0].build(1,1,n); tree[1].build(1,1,n);
for(int i=1; i<=n; i++)
{
id[i]=tree[i&1].pos[i];
if(v[i])
continue;
int l=(i>=k)? i-(k-1):k-i+1;
int r=(i+k-1<=n)? i+(k-1):n-k+1+n-i;
tree[(i&1)^((k&1)^1)].add(1,1,n,l,r,id[i]);
}
bfs();
for(int i=1; i<=n; i++)
printf("%d ",v[i]? -1:f[id[i]]);
return 0;
}
T2 括号匹配
(Uoj原题)
打部分分开了 \(\rm deque\) 直接 \(\rm MLE\) 喜提 \(0\) 分
注意到一个区间 \([i,j]\) 合法当且仅当满足以下条件:
-
\(j-i+1\) 是偶数
-
把 \()\) 当作 \(-1\),其余当作 \(1\),区间的所有前缀和都 \(\geq 0\),且总和等于 \(0\)
总和等于 \(0\) 很难处理,于是考虑转化成如下条件:
-
把 \()\) 当作 \(-1\),其余当作 \(1\),区间的所有前缀和都 \(\geq 0\)
-
把 \((\) 当作 \(-1\),其余当作 \(1\),区间的所有后缀和都 \(\geq 0\)
根据这些条件预处理出两个数组 \(L[i],R[i]\),分别表示以 \(i\) 为起点,向左/向右最远能到哪里,可以单调栈 \(O(n)\) 求出
那么现在再来考虑区间 \([i,j]\) 的合法性,我们可以写成如下的式子:
-
\(j-i+1\equiv 0 \pmod 2\)
-
\(i\geq L[j]\)
-
\(j\leq R[i]\)
转化一下变成在平面直角坐标系中,有多少个点 \((L[j],j)\) 在点 \((i,R[i])\) 的左下方,这是一个二维数点的问题,可以用树状数组解决
但是数点要满足 \(j>i\),这样很难操作。注意到当 \(j<i\) 时 \(L[j]\leq j<i\),此时必定满足条件,于是我们可以先全部加到答案里在减去比 \(i\) 小的 \(j\) 的贡献
时间复杂度 \(O(n\log n)\)
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=1000010;
int n,s1[N],s2[N],L[N],R[N];
int sta[N],top,cnt0,cnt1;
struct node{int x,y;}tmp0[N],tmp1[N];
char s[N];
LL ans;
bool cmp(node a,node b)
{
return a.x<b.x;
}
struct BIT
{
int c[N];
void add(int x,int y)
{
x++;
for(x; x<=n+1; x+=(x&-x))
c[x]+=y;
}
int ask(int x)
{
x++;
int res=0;
for(x; x; x-=(x&-x))
res+=c[x];
return res;
}
}t[2];
void prework()
{
s1[n+1]=-n; sta[++top]=0;
for(int i=1; i<=n+1; i++)
{
while(top && s1[i]<s1[sta[top]])
R[sta[top]+1]=i-1,top--;
sta[++top]=i;
}
top=0; s2[0]=-n; sta[++top]=n+1;
for(int i=n; i>=0; i--)
{
while(top && s2[i]<s2[sta[top]])
L[sta[top]-1]=i+1,top--;
sta[++top]=i;
}
for(int i=0; i<=n+1; i++)
{
if(s[i]=='(')
L[i]=i;
else if(s[i]==')')
R[i]=i;
}
}
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1; i<=n; i++)
s1[i]=s1[i-1]+(s[i]==')'? -1:1);
for(int i=n; i>=1; i--)
s2[i]=s2[i+1]+(s[i]=='('? -1:1);
prework();
for(int i=2; i<=n; i+=2)
tmp0[++cnt0]=(node){L[i],i};
for(int i=1; i<=n; i+=2)
tmp1[++cnt1]=(node){L[i],i};
sort(tmp0+1,tmp0+1+cnt0,cmp);
sort(tmp1+1,tmp1+1+cnt1,cmp);
int p0=1,p1=1;
for(int i=1; i<=n; i++)
{
if(i&1)
{
while(p0<=cnt0 && tmp0[p0].x<=i)
t[0].add(tmp0[p0].y,1),p0++;
ans+=1LL*(t[0].ask(R[i])-(i-1)/2);
}
else
{
while(p1<=cnt1 && tmp1[p1].x<=i)
t[1].add(tmp1[p1].y,1),p1++;
ans+=1LL*(t[1].ask(R[i])-i/2);
}
}
printf("%lld",ans);
return 0;
}
T3 崩原之战 1
其实就是 P8908 [USACO22DEC] Palindromes P
很有意思的题目
首先考虑暴力,将原串看成 \(01\) 串,显然对于区间 \([l,r]\),如果有奇数个 \(0\) 和奇数个 \(1\),那它的贡献就是 \(-1\)
否则,只要其中一种字符两两配对,那另一种字符肯定也配对了。所以我们考虑少的那一种字符(节省时间),假设是 \(1\)。那显然,交换相邻相同的字符肯定不优。所以我们肯定是首尾配对,如果还剩下一个的话就放在正中间。然后手玩可以发现肯定存在某一种最优的方案,使得配对的两个字符有一个不移动。假设区间 \([l,r]\) 里有 \(m\) 个 \(1\),位置分别是 \(a_{1\sim m}\),那我们可以写出最小的操作次数:
这样时间复杂度 \(O(n^3)\)
前面的那坨可以 \(O(n^2)\) 枚举解决掉,但后面那坨带绝对值的求和非常恶心,思考如何转化
注意到我们暴力枚举区间时会改变字符的配对情况,那如果反过来固定字符的配对情况再去枚举区间呢?
我们可以先枚举中间的一个或两个字符,每次再往外扩展一层,比如现在扩展到 \(a_i\) 和 \(a_j\) 的配对,那我们就去枚举区间 \(l\in (a_{i-1},a_i],\: r\in[a_j,a_{j+1})\),这样再中心点确定的情况下字符的配对情况也是确定的。
那如何快速计算字符配对对区间的贡献呢?
记 \(p=a_i+a_j\),集合 \(P\) 为目前已经匹配的字符的所有 \(p\) 构成的集合。将那个绝对值式子分类讨论,分成 \(a_i+a_j\leq l+r\) 和 \(a_i+a_j>l+r\)。对于前者,需要快速查询 \(P\) 内比 \(l+r\) 小的元素的个数和总和,分别记为 \(t\) 和 \(s\)。那么贡献就是 \(t(l+r)-s\)。对于后者,我们记 \(sum=\sum\limits_{p_i\in P}{p_i}\),那么贡献就是 \(sum-s-(|P|-t)(l+r)\)
想要快速查询集合内比 \(x\) 小的元素总和和个数?\(\rm BIT\)!!!
为什么这样枚举可以呢?因为在中心点不同的情况下,每个区间 \([l,r]\) 只会被枚举一次,所以时间复杂度 \(O(n^2\log n)\)
于是,这道题就愉快地结束了
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=7510;
int n,a[N],cnt;
LL ans;
char s[N];
struct BIT
{
LL c[2*N];
void add(int x,int y)
{
for(x; x<=2*n; x+=(x&-x))
c[x]+=(LL)y;
}
LL ask(int x)
{
LL res=0;
for(x; x; x-=(x&-x))
res+=c[x];
return res;
}
void clear()
{
for(int i=1; i<=2*n; i++)
c[i]=0;
}
}t1,t2;
int main()
{
scanf("%s",s+1); n=strlen(s+1);
int cntg=0;
for(int i=1; i<=n; i++)
cntg+=(s[i]=='G');
if(cntg>n/2) //将出现次数少的字符作为操作的对象
for(int i=1; i<=n; i++)
s[i]=(s[i]=='G'? 'H':'G');
for(int l=1; l<=n; l++)
{
cnt=0;
for(int r=l; r<=n; r++)
{
if(s[r]=='G')
a[++cnt]=r;
if((cnt&1) && (r-l+1)%2==0) //0和1都出现奇数次,无解,贡献-1
ans--;
else if(cnt&1) //提前计算式子的前半部分
ans+=abs((l+r)/2-a[cnt/2+1]);
}
}
cnt=0;
for(int i=1; i<=n; i++)
if(s[i]=='G')
a[++cnt]=i;
a[cnt+1]=n+1;
for(int i=1; i<=cnt; i++) //枚举一个中间的一个字符
{
t1.clear(); t2.clear();
LL sum=0;
for(int j=1; i-j>=1 && i+j<=cnt; j++)
{
int tmp=a[i-j]+a[i+j];
sum+=(LL)tmp;
t1.add(tmp,1); t2.add(tmp,tmp);
for(int l=a[i-j-1]+1; l<=a[i-j]; l++)
{
for(int r=a[i+j]; r<=a[i+j+1]-1; r++)
{
if((r-l+1)%2==0)
continue;
LL t=t1.ask(l+r),s=t2.ask(l+r);
ans+=1LL*(t*(l+r)-s+(sum-s)-(j-t)*(l+r));
}
}
}
}
for(int i=1; i<cnt; i++) //枚举中间的两个字符
{
t1.clear(); t2.clear();
LL sum=0;
for(int j=0; i-j>=1 && i+j+1<=cnt; j++)
{
int tmp=a[i-j]+a[i+j+1];
sum+=(LL)tmp;
t1.add(tmp,1); t2.add(tmp,tmp);
for(int l=a[i-j-1]+1; l<=a[i-j]; l++)
{
for(int r=a[i+j+1]; r<=a[i+j+2]-1; r++)
{
LL t=t1.ask(l+r),s=t2.ask(l+r);
ans+=1LL*(t*(l+r)-s+(sum-s)-(j+1-t)*(l+r));
}
}
}
}
printf("%lld",ans);
return 0;
}
T4 抽卡 1
最后时刻想冲部分分没冲出来(是自己想简单了)。做完这题后对期望有了更深入的认识
copy一下别人的题解
设一个位置集合 \(A\) 表示每一位的取值情况,\(A\) 的每一位是 \(\{0,1,?\}\) 的一种,所以 \(A\) 可以用三进制表示。称 \(A\) 合法,当且仅当 \(A\) 可以唯一确定目标串
对于操作次数的期望,一个经典套路是计算到达某个合法状态的概率,乘上停留在这里的期望时间,再求和。(注意状态每一位是 \(\{0,1\}\),表示是否知道该位的数字,是二进制)
考虑预处理停留在状态 \(S\) 的期望时间 \(t_S\)。设 \(P'_S\) 表示停留在 \(S\) 的概率,那么 \(P'_S=\prod\limits_{i\not\in S}(1-p_i)\)。根据经典结论 \(t_S=\prod\limits_{i=0}^{\infty}(P'_S)^i=\dfrac{1}{1-P'_S}\)(其实我是第一次知道,考完后Shui_Dream跟我分析了下才明白)
那么从 \(P_S\) 转移到 \(P_T\) 的系数就是 \(\prod\limits_{i\not\in S,i\in T}{p_i}\prod\limits_{i\not\in T}{(1-p_i)}t_S=\dfrac{\prod\limits_{i\not\in S,i\in T}{p_i}\prod\limits_{i\not\in T}{(1-p_i)}}{1-P'_S}\),预处理 \(g_{1,S}=\prod\limits_{i\in S}{p_i}\) 和 \(g_{2,S}=\prod\limits_{i\\not\in S}{(1-p_i)}\) 可以快速求出。枚举超集 \(\rm DP\) 时间复杂度 \(O(3^l)\)
对于目标串 \(s_i\),设 \(Q_{i}\) 表示 \(s_i\) 的合法位置集合,那么答案就等于 \(\sum\limits_{A\not\in Q_{i}}{P_At_A}\)。发现对于一个 \(A\),它要么唯一确定一个字符串,要么不能,而 \(A\) 的每一位都是 \(\{0,1,?\}\) 中的一个,因此 \(A\) 的个数是 \(O(3^l)\) 的,因此 \(\sum{|Q_i|}\leq 3^l\)。于是我们考虑容斥,\(ans=\sum\limits_{A}{P_At_A}-\sum\limits_{A\in Q_i}{P_At_A}\)
现在问题在于如何快速求出一个字符串 \(s\) 的合法位置集合。设 \(pt_A\) 表示 \(A\) 能唯一确定的字符串编号,如果不唯一则为 \(-1\),谁都无法确定就为 \(0\)
先考虑暴力,对于任意的 \(A\),选一位 \(0\) 或者 \(1\) 把它变成 \(?\),看它是否能确定其他的字符串,这样复杂度是 \(O(3^ll)\) 的
但如果反过来,我们选一些 \(?\),将它填上 \(0\) 或者 \(1\),看看两次的结果是否不同。若相同或有一个是 \(0\),就可以转移:令 \(pt_A\) 等于 \(pt_A'\),否则就为 \(-1\)。稍微思考一下就能发现我们只要填一个 \(?\) 就可以了,于是我们可以预处理出每个 \(A\) 最靠左的 \(?\),这样时间复杂度就是 \(O(3^l)\) 的
那么总的时间复杂度就是 \(O(T3^l)\)
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int M=20,N=40010,MAXN=15000010;
const int MOD=998244353;
int T,n,l,c[M],a[N],ans[N];
int p,pw[M],mi[MAXN],val[MAXN],pt[MAXN]; //mi表示最左边的?,val表示?位置的集合
int g1[N],g2[N],f[MAXN];
void init()
{
memset(f,0,sizeof(f));
memset(pt,0,sizeof(pt));
memset(val,0,sizeof(val));
}
int ksm(int x,int y)
{
int res=1;
while(y)
{
if(y&1)
res=1LL*res*x%MOD;
x=1LL*x*x%MOD;
y>>=1;
}
return res;
}
void prework()
{
pw[0]=1;
for(int i=1; i<=15; i++)
pw[i]=pw[i-1]*3;
mi[0]=0;
for(int i=0; i<15; i++)
{
for(int j=pw[i]-1; j>=0; j--) //预处理每个位置集合最左边的?
{
mi[j*3+0]=mi[j]+1;
mi[j*3+1]=mi[j]+1;
mi[j*3+2]=0;
}
}
}
void calc() //计算每个位置集合是否能唯一确定一个字符串
{
for(int i=0; i<=pw[l]-1; i++)
{
if(mi[i]>=l) //大于等于l说明没有?
continue;
int pre=i-pw[mi[i]]; //?位置填1
val[i]=val[pre]^(1<<mi[i]);
pt[i]=pt[pre];
pre=i-pw[mi[i]]*2; //?位置填0
if(!pt[i])
pt[i]=pt[pre];
else if(pt[i]!=pt[pre] && pt[pre]!=0)
pt[i]=-1;
}
}
int main()
{
p=ksm(10000,MOD-2);
prework();
scanf("%d",&T);
while(T--)
{
init();
scanf("%d%d",&l,&n);
for(int i=0; i<l; i++)
scanf("%d",&c[i]);
for(int i=1; i<=n; i++)
{
char x[N];
scanf("%s",x);
a[i]=0;
for(int j=0; j<l; j++)
if(x[j]=='1')
a[i]+=pw[j];
pt[a[i]]=i;
}
if(n==0)
{
printf("0\n");
return 0;
}
calc();
for(int i=0; i<=(1<<l)-1; i++) //预处理g1,g2
{
g1[i]=g2[i]=1;
for(int j=0; j<l; j++)
{
if(i&(1<<j))
{
g1[i]=1LL*g1[i]*c[j]%MOD*p%MOD;
g2[i]=1LL*g2[i]*(10000-c[j])%MOD*p%MOD;
}
}
}
f[0]=1;
int sum=0,S=(1<<l)-1;
for(int i=0; i<=S; i++)
{
f[i]=1LL*f[i]*ksm((1-g2[S^i]+MOD)%MOD,MOD-2)%MOD; //先乘上期望时间
for(int j=i; j<=S; j=(j+1)|i) //枚举超集
{
if(i==j)
continue;
f[j]=(f[j]+1LL*f[i]*g1[j^i]%MOD*g2[S^j]%MOD)%MOD;
}
sum=1LL*(sum+f[i])%MOD;
}
for(int i=1; i<=n; i++)
ans[i]=sum;
for(int i=0; i<=pw[l]-1; i++) //容斥
if(pt[i]>0)
ans[pt[i]]=(ans[pt[i]]-f[S^val[i]]+MOD)%MOD;
for(int i=1; i<=n; i++)
printf("%d\n",ans[i]);
}
return 0;
}

浙公网安备 33010602011771号