蝶恋花(互测赛)题解
补题题号:U468736 - U468741
A.声渐悄
Subtask.1
博弈相关暴搜,或许需要子集枚举之类的东西,我没研究。
Subtask.3
考虑先手如何取最优,发现可以构造使得剩余的物品两两相邻,这样后手选择时每两个就必定会剩下一个,下一轮自己就可以选。按
最难做
我们先假设第一轮先手剩下一段长为
所以先手可以在剩余连续段长度不超过
意义即为中间留下一个则必为后手的,留两个的话后手会取较大的,自己可以获得较小的。答案为
n=read();
for(int i=1;i<=n;i++) f[i]=a[i]=read();
for(int i=3;i<=n;i++) f[i]=max(f[i-2],f[i-3]+min(a[i-2],a[i-1]))+a[i];
print(max(f[n],f[n-1]));
B.乱红飞
Subtask.1
把
Subtask.2
发现对于满二叉树,同一层的点的子树结构完全相同,所以设
最难做
注:这里
同 Subtask.2 一样,考虑把同一层的点按结构分类。这里按照该子树在第
那么设
考虑算出第
在出现第
cin>>d>>s,g[d][1]=1; bool tf=false;
for(int i=d-1;i>=1;i--)
{
g[i][0]=(g[i+1][0]+1)*(g[i+1][0]+1)%mod,g[i][1]=(g[i+1][1]+1)*(g[i+1][1]+1)%mod;
if(tf) g[i][2]=(g[i+1][2]+1)*(g[i+1][s[i]-'0']+1)%mod;
else if(s[i]-'0') tf=true,g[i][2]=(g[i+1][1]+1)*(g[i+1][0]+1)%mod;
}
if(s[0]=='1') cout<<g[1][1]<<'\n';
else if(tf) cout<<g[1][2]<<'\n';
else cout<<g[1][0]<<'\n';
C.恨千缕
Subtask.1.2
考虑暴力,也就是记录下每缕思绪是否存在以及出现的时间,每次查询时枚举所有思绪判断是否符合题意,时间复杂度
Subtask.3
根据题面中的形式化题意,第
设
最难做
发现每缕思绪本质上是向若干个区间贡献
同时由于Subtask.3 中的做法只能做
考虑到
n=read(),m=read(),B=sqrt(m/2);
for(int i=1;i<=n;i++) x[i]=read(),y[i]=read();
for(int i=1;i<=m;i++)
{
int opt=read(),k=read(),tr=0,mo=(x[k]+y[k]);
if(mo<=B)
{
if(opt==1)
{
st[k]=i;
for(int j=0;j<mo;j++) if(((j-st[k])%mo+mo)%mo>=x[k]) f[mo][j]++;
}
else for(int j=0;j<mo;j++) if(((j-st[k])%mo+mo)%mo>=x[k]) f[mo][j]--;
}
else
{
if(opt==1)
{
st[k]=i;
for(int j=i;j+x[k]<=m;j+=mo)
{
s[j+x[k]]++;
if(j+mo<=m) s[j+mo]--;
}
}
else for(int j=(i-st[k])/mo*mo+st[k];j+x[k]<=m;j+=mo)
{
if(j+x[k]>=i) s[j+x[k]]--;
else s[i]--;
if(j+mo<=m)
{
if(j+mo>=i) s[j+mo]++;
else s[i]++;
}
}
}
for(int j=2;j<=B;j++) tr+=f[j][i%j];
s[i]+=s[i-1],print(tr+s[i]),putchar('\n');
}
D.寄彩笺
Subtask.1
暴力枚举,分别枚举
正解思路
考虑求每个
先考虑
接着考虑去掉重叠算多的贡献次数,发现前后两次会重叠当且仅当
问题在于,若 border 长度超过
所以可以保证字符串没有或仅有一个 border,有则不会自己重叠,那么可以用唯一 border 的长度作为开头说的分类方式。枚举 border 的长度
之后容斥求答案,即加上
这里要枚举
if(k*2<m||n<m)
{
cout<<0;
return 0;
}
res=sol(k,m)*p[n-m]%mod*(n-m+1)%mod; int now=1;
for(int x=1;x*2<=m;x++)
{
now=now*(k-x+1)%mod;
int ta=now*sol(k-x,m-2*x)%mod,w=1;
for(int i=2; ;i++)
{
w*=-1;
int len=m+(m-x)*(i-1);
if(len>n) break;
res=(res+ta*p[n-len]%mod*(n-len+1)%mod*w+mod)%mod;
}
}
cout<<res;
但是还有一处没有解决,即 Sol 函数解决用
Subtask.2.3
考虑 DP,设
由于每次的
int sol(int k,int m)
{
f[0][0]=1; int res=0;
for(int i=1;i<=m;i++) for(int j=0;j*2<=i;j++)
{
f[i][j]=f[i-1][j]*(k-(i-1-j))%mod;
if((j-1)*2<=i-1) f[i][j]=(f[i][j]+f[i-1][j-1]*(i-1-2*(j-1))%mod)%mod;
}
for(int i=0;i*2<=m;i++) res=(res+f[m][i])%mod;
return res;
}
最难做
枚举字符串中出现
由于
p[0]=p2[0]=fr[0]=fk[0]=1;
for(int i=1;i<=n;i++) p[i]=p[i-1]*k%mod,p2[i]=p2[i-1]*2%mod;
for(int i=1;i<=m;i++) fr[i]=fr[i-1]*i%mod;
for(int i=1;i<=min(m,k);i++) fk[i]=fk[i-1]*(k-i+1)%mod;
int sol(int tk,int tm)
{
int res=0;
for(int i=0;i*2<=tm;i++) if(tm-i<=tk)
{
int ts=1,dt=k-tk,dk=inv(fk[dt]);
ts=ts*(fk[i+dt]*dk%mod)%mod*inv(fr[i])%mod;
ts=ts*(fr[tm]*inv(fr[tm-i*2])%mod*inv(p2[i])%mod)%mod;
ts=ts*(fk[tm-i+dt]*dk%mod*inv(fk[i+dt]*dk%mod)%mod)%mod;
res=(res+ts)%mod;
}
return res;
}
F.重霄九
Subtask.1
每次询问直接对子树内每个点分别
Subtask.2-4
考虑类似线段树维护每个结点子树内的最大值,最小值和子树内合法结点的数量,修改时修改点权后将其到根路径上的所有结点依次重新维护,时间复杂度
struct tree
{
int lc[N],rc[N],a[N],s[N],mx[N],mn[N],fa[N];
bool f[N];
void pushup(int u)
{
int tl=lc[u],tr=rc[u];
mx[u]=max(max(mx[tl],mx[tr]),a[u]),mn[u]=min(min(mn[tl],mn[tr]),a[u]);
f[u]=(a[u]>=mx[tl]&&a[u]<=mn[tr]&&f[tl]&&f[tr]),s[u]=s[tl]+s[tr]+f[u];
}
void build(int u)
{
if(lc[u]) build(lc[u]);
if(rc[u]) build(rc[u]);
pushup(u);
}
void change(int u,int x)
{
a[u]=mx[u]=mn[u]=x;
while(u) pushup(u),u=fa[u];
}
}T;
signed main()
{
n=read(),Q=read();
for(int i=1;i<=n;i++) T.lc[i]=read(),T.rc[i]=read(),T.fa[T.lc[i]]=T.fa[T.rc[i]]=i;
for(int i=1;i<=n;i++) T.a[i]=T.mx[i]=T.mn[i]=read();
T.mx[0]=-inf,T.mn[0]=inf,T.s[0]=0,T.f[0]=1,T.build(1);
while(Q--)
{
int opt=read(),x=read();
if(opt==1)
{
int y=read();
T.change(x,y);
}
else print(T.s[x]),putchar('\n');
}
return 0;
}
最难做
发现树的形态不变,那么若子树是二叉搜索树,其中最大值和最小值的位置是确定的,分别为其最靠右和最靠左的结点。这里最靠某方向的点是指从根节点一直走该方向的儿子,直到这个儿子为空时所停的点。
同时若点
根据定义,这个点合法只需要其左右儿子均合法且
所以只要一棵子树内所有点都满足自身的限制,这棵子树就是二叉搜索树。考虑设
考虑维护这个子树和
#include<iostream>
#include<vector>
#define pii pair<int,int>
using namespace std;
const int N=2e5+10;
int n,q,a[N],lc[N],rc[N],lx[N],rx[N],rm[N],lm[N],f[N];
void ini(int u)
{
if(!u) return;
ini(lc[u]),ini(rc[u]);
rm[u]=rm[rc[u]],lm[u]=lm[lc[u]];//lm 和 rm 分别表示最靠左和最靠右的结点
if(!rm[u]) rm[u]=u;
if(!lm[u]) lm[u]=u;
lx[u]=rm[lc[u]],rx[u]=lm[rc[u]];
}
vector <int> lim[N],edge[N];
int ct,siz[N],de[N],fa[N],son[N],id[N],top[N],aa[N],tw[N];
void dfs(int u,int fat)
{
fa[u]=fat,de[u]=de[fat]+1,siz[u]=1,tw[u]=f[u];
for(int v:edge[u])
{
dfs(v,u),siz[u]+=siz[v],tw[u]+=tw[v];
if(siz[son[u]]<siz[v]) son[u]=v;
}
}
void dfsb(int u,int to)
{
top[u]=to,id[u]=++ct,aa[ct]=tw[u];
if(!son[u]) return;
dfsb(son[u],to);
for(int v:edge[u]) if(v!=son[u]) dfsb(v,v);
}
struct sgmtt
{
int t=1,lc[N*2],rc[N*2],tag[N*2]; pii a[N*2]; //最小值及个数
void pt(int u,int k) {a[u].first+=k,tag[u]+=k;}
void pushdown(int u) {if(tag[u]) pt(lc[u],tag[u]),pt(rc[u],tag[u]),tag[u]=0;}
void pushup(pii &u,pii tl,pii tr)
{
u.first=min(tl.first,tr.first),u.second=0;
if(tl.first==u.first) u.second+=tl.second;
if(tr.first==u.first) u.second+=tr.second;
}
void build(int u,int l,int r)
{
if(l==r) {a[u]={aa[l],1}; return;}
int mid=(l+r)/2;
lc[u]=++t,build(t,l,mid);
rc[u]=++t,build(t,mid+1,r);
pushup(a[u],a[lc[u]],a[rc[u]]);
}
void add(int u,int l,int r,int ll,int rr,int k)
{
if(l>=ll&&r<=rr) {pt(u,k); return;}
int mid=(l+r)/2; pushdown(u);
if(ll<=mid) add(lc[u],l,mid,ll,rr,k);
if(rr>mid) add(rc[u],mid+1,r,ll,rr,k);
pushup(a[u],a[lc[u]],a[rc[u]]);
}
pii check(int u,int l,int r,int ll,int rr)
{
if(l>=ll&&r<=rr) return a[u];
int mid=(l+r)/2; pushdown(u);
if(rr<=mid) return check(lc[u],l,mid,ll,rr);
if(ll>mid) return check(rc[u],mid+1,r,ll,rr);
pii res;
pushup(res,check(lc[u],l,mid,ll,rr),check(rc[u],mid+1,r,ll,rr));
return res;
}
}T;
void addrt(int x,int k)
{
while(top[x]!=1) T.add(1,1,n,id[top[x]],id[x],k),x=fa[top[x]];
T.add(1,1,n,1,id[x],k);
}
signed main()
{
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>q;
for(int i=1;i<=n;i++) cin>>lc[i]>>rc[i];
ini(1),a[0]=-1,a[n+1]=n+1;
for(int i=1;i<=n;i++) cin>>a[i],lim[i].push_back(i);
for(int i=1;i<=n;i++)
{
if(lc[i]) edge[i].push_back(lc[i]);
if(rc[i]) edge[i].push_back(rc[i]);
if(lx[i]) lim[lx[i]].push_back(i);
if(rx[i]) lim[rx[i]].push_back(i);
else rx[i]=n+1;
f[i]=!(a[lx[i]]<=a[i]&&a[rx[i]]>=a[i]);
}
dfs(1,0),dfsb(1,1),T.build(1,1,n);
while(q--)
{
int opt,x; cin>>opt>>x;
if(opt==2)
{
pii te=T.check(1,1,n,id[x],id[x]+siz[x]-1);
cout<<(te.first?0:te.second)<<'\n';
}
else
{
int y; cin>>y;
for(int i:lim[x]) addrt(i,-f[i]);
a[x]=y;
for(int i:lim[x]) f[i]=!(a[lx[i]]<=a[i]&&a[rx[i]]>=a[i]),addrt(i,f[i]);
}
}
return 0;
}
Ex.凭阑意
以下设
Subtask.1.2
发现答案上界为
Subtask.3
转换条件,发现
for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) if(a[j]>a[i]) f[a[j]-a[i]]=1;
for(int i=1;i<=V;i++)
{
for(int j=i*2;j<=V;j+=i)
{
f[i]|=f[j];
if(f[i]) break;
}
if(!f[i]) {print(i);break;}
}
Subtask.4
同 Subtask.3 思路相同,从小到大判断数是否能作为
考虑使用 bitset 优化,开值域大小的 bitset
再开个数组记忆化,保证每个
bitset <M> bs;
bool vis[N],ma[N];
bool check(int x)
{
if(!vis[x])
{
vis[x]=1;
if(((bs>>x)&bs).count()) ma[x]=1;
else ma[x]=0;
}
return ma[x];
}
void solb()
{
for(int i=1;i<=n;i++) bs[a[i]]=1;
for(int i=2;i<=V/10;i++)// V/10 即为本 Subtask 的值域 10^5
{
bool tf=true;
for(int j=i;j<=V/10;j+=i) if(check(j)) {tf=false; break;}
if(tf) {print(i); break;}
}
}