2023.8.7测试
当然也搬了很多原题
T1 转圈圈
一个长度为
同时,串中存在
为了卡常最后时刻手写队列直接写成栈,喜提
手玩可注意到一个点可以往一段区间内奇偶性相同的点连边权为
#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原题)
打部分分开了
注意到一个区间
-
是偶数 -
把
当作 ,其余当作 ,区间的所有前缀和都 ,且总和等于
总和等于
-
把
当作 ,其余当作 ,区间的所有前缀和都 -
把
当作 ,其余当作 ,区间的所有后缀和都
根据这些条件预处理出两个数组
那么现在再来考虑区间
转化一下变成在平面直角坐标系中,有多少个点
但是数点要满足
时间复杂度
#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
很有意思的题目
首先考虑暴力,将原串看成
否则,只要其中一种字符两两配对,那另一种字符肯定也配对了。所以我们考虑少的那一种字符(节省时间),假设是
这样时间复杂度
前面的那坨可以
注意到我们暴力枚举区间时会改变字符的配对情况,那如果反过来固定字符的配对情况再去枚举区间呢?
我们可以先枚举中间的一个或两个字符,每次再往外扩展一层,比如现在扩展到
那如何快速计算字符配对对区间的贡献呢?
记
想要快速查询集合内比
为什么这样枚举可以呢?因为在中心点不同的情况下,每个区间
于是,这道题就愉快地结束了
#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一下别人的题解
设一个位置集合
对于操作次数的期望,一个经典套路是计算到达某个合法状态的概率,乘上停留在这里的期望时间,再求和。(注意状态每一位是
考虑预处理停留在状态
那么从
对于目标串
现在问题在于如何快速求出一个字符串
先考虑暴力,对于任意的
但如果反过来,我们选一些
那么总的时间复杂度就是
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?