6月CWOI杂题
C0253 【0617 C组】模拟测试
军训归来的第一场模拟赛,小寄。
C 【0601 C组】树
好神奇的题目。
直径这个东西没什么能入手的性质,我们先考虑进行一些转化。
对于直径,我们去找它的中心点。中心点可能在边上,于是把边拆开,比如边 \((u,v)\) 拆成 \((u,x)(x,v)\),这样就有了 \(2n-1\) 个点,且直径的中心点都在点上。于是可以进一步把问题变成每次使一条边 \(+2\),使直径的一半(即半径,就是以中心点为端点延伸的最长路径)最小。至于为什么有半径就一定能找到对应直径的问题,我们下面会讲。
枚举中心点 \(i\),设离它最远的点距离为 \(r_i\),显然这样的点一定是叶子。定义所有叶子到 \(i\) 的距离之和为 \(s_i\),整棵树的叶子数为 \(c\),这些东西和 \(r_i\) 都可以换根 \(O(n)\)。
那么我们要把以 \(i\) 为中心点的半径控制在 \(r_i\) 最多可以操作 \(\dfrac{c\cdot r_i-s_i}{2}\) 次,如果半径为 \(r_i+2x\) 最多可以操作 \(\dfrac{c\cdot r_i-s_i}{2}+c\cdot x\) 次。因为叶子不可能作为中心点,所以 \(c\) 是定值。记 \(\dfrac{c\cdot r_i-s_i}{2}\) 为 \(o_i\),小推一下可以得到 \(x=\left\lceil\dfrac{k-o_i}{c}\right\rceil\),那么 \(i\) 点的答案就是:\(ans_i=\begin{cases}r_i&k\le o_i\\r_i+2\left\lceil\dfrac{k-o_i}{c}\right\rceil&k>o_i\end{cases}\)
解释一下为啥有半径就一定能找到对应直径。因为我们是直接粗暴地认为直径长为半径两倍,如果你的另一半不足 \(r_i\),首先根据定义它不是中心点;其次,因为中心点一定在点上,所以在枚举其他点的时候我们会得到正确的答案,故 \(2r_i\) 这个东西是不优的,不会对答案造成影响。
再解释一下为什么 \(o_i\) 是整数。叶子节点都是原来就有的,所以它们到 \(i\) 的距离的奇偶性相同。
现在问题转化成每次给你 \(k\),求在上述柿子中能得到的最小值。把柿子和询问离线下来,分别按照 \(o_i\) 和 \(k\) 从小到大排序。枚举 \(k\),那么就是有一段前缀柿子取到第二类而另一段后缀取第一类。分界点可以双指针。后缀那坨可以直接取最小值,前面的可以把上取整转化成根据余数分一下类,然后丢到线段树上。时间复杂度 \(O(n\log n)\)。
有一个细节:从 \(u\) 换根到 \(v\) 的时候可能会出现 \(u\) 就是叶子。
upd:好像可以不用线段树了捏。
可以把 \(r_i\) 写进上取整里面:如果 \(r_i\) 是偶数,柿子可以写成 \(2\left\lceil\dfrac{r_i}{2}+\dfrac{k-o_i}{c}\right\rceil=2\left\lceil\left(\dfrac{r_i}{2}-\dfrac{o_i}{c}\right)+\dfrac{k}{c}\right\rceil\),维护最大的 \(\left(\dfrac{r_i}{2}-\dfrac{o_i}{c}\right)\) 即可;如果 \(r_i\) 是奇数,先减一写进去最后再加一就是一样的了。精度要取高一点。
点击查看代码(线段树)
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define mk make_pair
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,nxt;
}e[800005];
int head[400005],tot,deg[400005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;deg[v]++;
}
int d[400005],cnt[400005],tag[400005],in[400005];
void dfs1(int u,int fa){
cnt[u]=tag[u],in[u]=(tag[u]?0:-inf);
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
d[v]=d[u]+1;dfs1(v,u);
cnt[u]+=cnt[v],in[u]=max(in[u],in[v]+1);
}
}
int out[400005],s[400005],r[400005],o[400005];
void dfs2(int u,int fa){
vector<pii>vec;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
s[v]=s[u]-cnt[v]+(cnt[1]-cnt[v]);
vec.push_back(mk(in[v],v));
out[v]=out[u]+1;
if(tag[u])out[v]=max(out[v],1ll);
}
int pre=-inf;
for(int i=0;i<(int)vec.size();i++){
out[vec[i].se]=max(out[vec[i].se],pre+2);
pre=max(pre,vec[i].fi);
}
int suf=-inf;
for(int i=(int)vec.size()-1;i>=0;i--){
out[vec[i].se]=max(out[vec[i].se],suf+2);
suf=max(suf,vec[i].fi);
}
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
dfs2(v,u);
}
}
struct segtree{
#define ls p<<1
#define rs p<<1|1
#define lson l,mid,ls
#define rson mid+1,r,rs
struct Node{
int mi;
}c[1600005];
void pushup(int p){
c[p].mi=min(c[ls].mi,c[rs].mi);
}
void build(int l,int r,int p){
if(l==r){c[p].mi=inf;return;}
int mid=(l+r)>>1;
build(lson),build(rson);
pushup(p);
}
void update(int l,int r,int p,int x,int k){
if(l==r){c[p].mi=min(c[p].mi,k);return;}
int mid=(l+r)>>1;
if(x<=mid)update(lson,x,k);
else update(rson,x,k);
pushup(p);
}
int query(int l,int r,int p,int L,int R){
if(L>R)return inf;
if(L<=l&&r<=R)return c[p].mi;
int mid=(l+r)>>1,res=inf;
if(L<=mid)res=min(res,query(lson,L,R));
if(R>mid)res=min(res,query(rson,L,R));
return res;
}
#undef ls
#undef rs
#undef lson
#undef rson
}Tr;
struct Dec{
int o,id;
}p[400005];
int cmpD(Dec x,Dec y){
return x.o<y.o;
}
int suf[400005];
struct Que{
int k,id;
}q[200005];
int cmpQ(Que x,Que y){
return x.k<y.k;
}
int A[400005],B[400005],ans[200005];
void solve(){
int n=read();
for(int i=1,u,v;i<n;i++){
u=read(),v=read();
add(u,n+i),add(n+i,u);
add(v,n+i),add(n+i,v);
}
int m=2*n-1,Q=read(),c=0;
for(int i=1;i<=m;i++){
if(deg[i]==1)c++,tag[i]=1;
}
dfs1(1,0);
for(int i=1;i<=m;i++){
if(tag[i])s[1]+=d[i];
}
out[1]=-inf;
dfs2(1,0);
int num=0;
for(int i=1;i<=m;i++){
r[i]=max(in[i],out[i]);
o[i]=(r[i]*c-s[i])/2;
A[i]=o[i]/c,B[i]=o[i]%c;
if(!tag[i])p[++num]=(Dec){o[i],i};
}
sort(p+1,p+num+1,cmpD);
suf[num+1]=inf;
for(int i=num;i>=1;i--){
suf[i]=min(suf[i+1],r[p[i].id]);
}
for(int i=1;i<=Q;i++){
q[i].k=read(),q[i].id=i;
}
sort(q+1,q+Q+1,cmpQ);
Tr.build(0,c-1,1);
for(int i=1,j=1;i<=Q;i++){
while(j<=num&&p[j].o<=q[i].k){
Tr.update(0,c-1,1,B[p[j].id],-2*A[p[j].id]+r[p[j].id]);
j++;
}
int res=inf;
res=min(res,suf[j]);
res=min(res,(q[i].k/c)*2+Tr.query(0,c-1,1,0,q[i].k%c-1)+2);
res=min(res,(q[i].k/c)*2+Tr.query(0,c-1,1,q[i].k%c,c-1));
ans[q[i].id]=res;
}
for(int i=1;i<=Q;i++){
printf("%lld\n",ans[i]);
}
}
signed main(){
int T=1;
while(T--)solve();
return 0;
}
点击查看代码(不用线段树)
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define mk make_pair
#define ldb long double
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
const ldb eps=1e-8;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int Ceil(ldb x){
int y=(int)x;x-=y;
if(fabsl(x)>eps)y++;
return y;
}
struct edge{
int v,nxt;
}e[800005];
int head[400005],tot,deg[400005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;deg[v]++;
}
int d[400005],cnt[400005],tag[400005],in[400005];
void dfs1(int u,int fa){
cnt[u]=tag[u],in[u]=(tag[u]?0:-inf);
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
d[v]=d[u]+1;dfs1(v,u);
cnt[u]+=cnt[v],in[u]=max(in[u],in[v]+1);
}
}
int out[400005],s[400005],r[400005],o[400005];
void dfs2(int u,int fa){
vector<pii>vec;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
s[v]=s[u]-cnt[v]+(cnt[1]-cnt[v]);
vec.push_back(mk(in[v],v));
out[v]=out[u]+1;
if(tag[u])out[v]=max(out[v],1ll);
}
int pre=-inf;
for(int i=0;i<(int)vec.size();i++){
out[vec[i].se]=max(out[vec[i].se],pre+2);
pre=max(pre,vec[i].fi);
}
int suf=-inf;
for(int i=(int)vec.size()-1;i>=0;i--){
out[vec[i].se]=max(out[vec[i].se],suf+2);
suf=max(suf,vec[i].fi);
}
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
dfs2(v,u);
}
}
int p[400005],suf[400005],k[200005],q[200005],ans[200005];
int cmpD(int x,int y){
return o[x]<o[y];
}
int cmpQ(int x,int y){
return k[x]<k[y];
}
void solve(){
int n=read(),m=2*n-1;
for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,n+i),add(n+i,u),add(v,n+i),add(n+i,v);
int Q=read(),c=0;
for(int i=1;i<=m;i++)if(deg[i]==1)c++,tag[i]=1;
dfs1(1,0);
for(int i=1;i<=m;i++)if(tag[i])s[1]+=d[i];
out[1]=-inf;dfs2(1,0);
int num=0;
for(int i=1;i<=m;i++){
r[i]=max(in[i],out[i]),o[i]=(r[i]*c-s[i])/2;
if(!tag[i])p[++num]=i;
}
sort(p+1,p+num+1,cmpD);
suf[num+1]=inf;for(int i=num;i>=1;i--)suf[i]=min(suf[i+1],r[p[i]]);
for(int i=1;i<=Q;i++)k[i]=read(),q[i]=i;
sort(q+1,q+Q+1,cmpQ);
ldb odd=inf,even=inf;
for(int i=1,j=1;i<=Q;i++){
while(j<=num&&o[p[j]]<=k[q[i]]){
if(r[p[j]]%2==0)even=min(even,(ldb)(-o[p[j]]*1.0L/c+r[p[j]]/2));
else odd=min(odd,(ldb)(-o[p[j]]*1.0L/c+r[p[j]]/2));
j++;
}
int res=inf;
res=min(res,suf[j]);
res=min(res,1ll+2ll*Ceil(odd+k[q[i]]*1.0L/c));
res=min(res,2ll*Ceil(even+k[q[i]]*1.0L/c));
ans[q[i]]=res;
}
for(int i=1;i<=Q;i++)printf("%lld\n",ans[i]);
}
signed main(){
int T=1;
while(T--)solve();
return 0;
}
C0266 【0624 C组】模拟测试
感觉 \(\ldots\) 不如 C 组,难度。
T3 数组开小,小丑一个。
A 【1102 B组】跳跃
根据余数大小分个类转移,萌萌题。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct segtree{
#define ls p<<1
#define rs p<<1|1
#define lson l,mid,ls
#define rson mid+1,r,rs
struct Node{
int ma;
}c[500005];
void pushup(int p){
c[p].ma=max(c[ls].ma,c[rs].ma);
}
void build(int l,int r,int p){
if(l==r){
c[p].ma=-inf;
return;
}
int mid=(l+r)>>1;
build(lson),build(rson);
pushup(p);
}
void update(int l,int r,int p,int x,int k){
if(l==r){
c[p].ma=max(c[p].ma,k);
return;
}
int mid=(l+r)>>1;
if(x<=mid)update(lson,x,k);
else update(rson,x,k);
pushup(p);
}
int query(int l,int r,int p,int L,int R){
if(L>R)return -inf;
if(L<=l&&r<=R)return c[p].ma;
int mid=(l+r)>>1;
if(R<=mid)return query(lson,L,R);
if(L>mid)return query(rson,L,R);
return max(query(lson,L,R),query(rson,L,R));
}
#undef ls
#undef rs
#undef lson
#undef rson
}Tr;
int t[100005],b[100005],f[100005],A[100005],B[100005],v[100005];
signed main(){
freopen("jump.in","r",stdin);
freopen("jump.out","w",stdout);
int k=read(),m=read(),d=read(),a=read(),n=read(),tot=0;
t[0]=k,b[0]=0,v[++tot]=t[0]%d;
t[n+1]=m,b[n+1]=0,v[++tot]=t[n+1]%d;
for(int i=1;i<=n;i++)t[i]=read(),b[i]=read(),v[++tot]=t[i]%d;
sort(v+1,v+tot+1);tot=unique(v+1,v+tot+1)-v-1;Tr.build(1,tot,1);
for(int i=0;i<=n+1;i++)A[i]=t[i]/d,B[i]=lower_bound(v+1,v+tot+1,t[i]%d)-v;
f[0]=0,Tr.update(1,tot,1,B[0],A[0]*a+f[0]);
for(int i=1;i<=n+1;i++){
f[i]=max(Tr.query(1,tot,1,1,B[i]-1)-a,Tr.query(1,tot,1,B[i],tot))-A[i]*a+b[i];
Tr.update(1,tot,1,B[i],A[i]*a+f[i]);
}
printf("%lld\n",f[n+1]);
return 0;
}
B 【1102 B组】匹配
\(P,Q\) 匹配当且仅当对于任意 \(i<j\),满足 \(([P_i=P_j]\oplus[Q_i=Q_j])=0\)。这个玩意不好表示,我们转化一下:定义 \(L_{s,i}\) 和 \(R_{s,i}\) 表示在 \(s\) 中满足 \(j<i,s_j=s_i\) 的最大的 \(j\) 和 \(j>i,s_j=s_i\) 的最小的 \(j\),如果没有就为 0。那么 \(P,Q\) 匹配就等价于 \(\forall i\in[1,m],L_{P,i}=L_{Q,i}\)。
现在,我们需要对 \(s\) 的每个长为 \(m\) 的子串求其是否与 \(t\) 匹配。这个匹配的条件可以写成哈希,所以你动态维护一下哈希值即可。注意判一下 \(L_{s,j}\) 不存在或超过当前枚举的边界的情况。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,SIZ=1e6,P=1145141,mod=1e9+7;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int pw[1000005],a[1000005],b[1000005],ans[1000005],p[1000005];
int La[1000005],Ra[1000005],Lb[1000005],Rb[1000005];
signed main(){
freopen("match.in","r",stdin);
freopen("match.out","w",stdout);
pw[0]=1;
for(int i=1;i<=SIZ;i++)pw[i]=(pw[i-1]*P)%mod;
int T=read(),c=read();
while(T--){
int n=read(),m=read(),tot=0;
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=m;i++)b[i]=read();
if(n<m){puts("0\n");continue;}
for(int i=1;i<=n;i++){
La[i]=p[a[i]],p[a[i]]=i;
}
for(int i=1;i<=c;i++)p[i]=0;
for(int i=n;i>=1;i--){
Ra[i]=p[a[i]],p[a[i]]=i;
}
for(int i=1;i<=c;i++)p[i]=0;
for(int i=1;i<=m;i++){
Lb[i]=p[b[i]],p[b[i]]=i;
}
for(int i=1;i<=c;i++)p[i]=0;
for(int i=m;i>=1;i--){
Rb[i]=p[b[i]],p[b[i]]=i;
}
for(int i=1;i<=c;i++)p[i]=0;
int ha=0,hb=0;
for(int i=1;i<=m;i++){
if(La[i]==0||La[i]<1)ha=(ha*P)%mod;
else ha=(ha*P%mod+(i-La[i]+1))%mod;
}
for(int i=1;i<=m;i++){
if(Lb[i]==0||Lb[i]<1)hb=(hb*P)%mod;
else hb=(hb*P%mod+(i-Lb[i]+1))%mod;
}
if(ha==hb)ans[++tot]=1;
for(int i=2;i+m-1<=n;i++){
if(La[i-1]!=0&&La[i-1]>=i-1)ha=(ha-pw[m-1]*((i-1)-La[i-1]+1)%mod+mod)%mod;
if(Ra[i-1]!=0&&Ra[i-1]<=(i-1)+m-1){
ha=(ha-pw[((i-1)+m-1)-Ra[i-1]]*(Ra[i-1]-(i-1)+1)%mod+mod)%mod;
}
if(La[i+m-1]==0||La[i+m-1]<i)ha=(ha*P)%mod;
else ha=(ha*P%mod+((i+m-1)-La[i+m-1]+1))%mod;
if(ha==hb)ans[++tot]=i;
}
printf("%lld\n",tot);
for(int i=1;i<=tot;i++)printf("%lld ",ans[i]);
puts("");
}
return 0;
}
C 【1102 B组】图
\(k\) 就是 \(\text{dist}(s,t)\)。关于构造方案,把 \(s\rightarrow t\) 路径上的边分成一层一层的赋权即可,其他边随便。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define mk make_pair
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,w,nxt;
}e[160005];
int tot,head[405];
void add(int u,int v,int w){
e[++tot]=(edge){v,w,head[u]},head[u]=tot;
}
int n,m,s,t,d[405],vis[405],ans[160005];
void dijkstra(){
for(int i=1;i<=n;i++)d[i]=inf,vis[i]=0;
priority_queue<pii,vector<pii>,greater<pii> >q;
d[s]=0,q.push(mk(d[s],s));
while(!q.empty()){
int u=q.top().se;q.pop();
if(vis[u])continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v,w=e[i].w;
if(d[v]>=d[u]+1)d[v]=d[u]+1,ans[w]=d[v],q.push(mk(d[v],v));
}
}
}
signed main(){
freopen("graph.in","r",stdin);
freopen("graph.out","w",stdout);
n=read(),m=read(),s=read(),t=read();
for(int i=1,u,v;i<=m;i++)u=read(),v=read(),add(u,v,i),add(v,u,i);
dijkstra();printf("%lld\n",d[t]);
for(int i=1;i<=m;i++)printf("%lld\n",min(d[t],ans[i]));
return 0;
}
D 【1102 B组】异或
先把所有数丢到 trie 上去 dp。定义 \(f_u\) 表示在以 \(u\) 为根的子树里选若干个数,使它们满足题目中给定的条件的方案数(可以不选)。记 \(cnt_u\) 表示 \(u\) 子树内有多少个数,\(val_u\) 表示 \(u\) 点对应的真实值,\(bit(x,d)\) 表示 \(x\) 二进制下从高到低第 \(d\) 位的值。
-
如果当前深度 \(d\) 满足 \(2^d>x\),则你在 \(ls(u)\) 和 \(rs(u)\) 中选的数互相之间一定满足条件,只用保证内部合法。同时,显然左右是独立的。有 \(f_{u}=f_{ls(u)}\times f_{rs(u)}\);
-
反之,那么你在左右两边至多能各选一个点。定义 \(g_{u,v}\) 表示在 \(u,v\) 中各选一个点的方案数。有 \(f_u=1+cnt_u+g_{ls(u),rs(u)}\)。
同时,对于 \(g\) 的计算,我们也可以分情况讨论。
-
如果当前深度 \(d\) 满足 \(bit(x,d)=1\),那么要满足异或不小于 \(x\) 就必须在 \(d\) 这一位走法不同。有 \(g_{u,v}=g_{ls(u),rs(v)}+g_{rs(u),ls(v)}\);
-
反之,如果这一位异或出 1 了,我们就已经满足不小于 \(x\) 的目标了;如果还是 0,我们继续走。有 \(g_{u,v}=cnt_{ls(u)}\times cnt_{rs(v)}+cnt_{rs(u)}\times cnt_{ls(v)}+g_{ls(u),ls(v)}+g_{rs(u),rs(v)}\)。
边界条件可以看代码。记得最后减去不选的方案。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=998244353;
inline ll read(){
ll x=0;int f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
ll qpow(ll b,int p){
ll res=1;
for(;p;p>>=1,b=b*b%mod)if(p&1)res=res*b%mod;
return res;
}
int tot=1,cnt[18000005],son[18000005][2];ll val[18000005];
inline void insert(ll x){
int u=1;
for(int i=60;i>=0;i--){
int o=(x>>i)&1ll;
if(!son[u][o])son[u][o]=++tot;
cnt[u]++,u=son[u][o];
}
val[u]=x,cnt[u]++;
}
inline ll dfs(int u,int v,int d,ll x){
if(u==0||v==0)return 0ll;
if(d==-1)return ((val[u]^val[v])>=x)?1ll*cnt[u]*cnt[v]%mod:0ll;
if((x>>d)&1ll)return (dfs(son[u][0],son[v][1],d-1,x)+dfs(son[u][1],son[v][0],d-1,x))%mod;
else return (dfs(son[u][0],son[v][0],d-1,x)+dfs(son[u][1],son[v][1],d-1,x)+1ll*cnt[son[u][0]]*cnt[son[v][1]]%mod+1ll*cnt[son[u][1]]*cnt[son[v][0]]%mod)%mod;
}
inline ll solve(int u,int d,ll x){
if(u==0)return 1ll;
if(d==-1)return (x==0)?qpow(2ll,cnt[u]):(cnt[u]+1)%mod;
if((x>>d)&1ll)return (cnt[u]+1+dfs(son[u][0],son[u][1],d-1,x))%mod;
else return solve(son[u][0],d-1,x)*solve(son[u][1],d-1,x)%mod;
}
signed main(){
freopen("xor.in","r",stdin);
freopen("xor.out","w",stdout);
int n=read();ll x=read();
for(int i=1;i<=n;i++)insert(read());
printf("%lld\n",(solve(1,60,x)-1+mod)%mod);
return 0;
}
C0272 【0626 B组】模拟测试
感觉自己有点蠢,大方向出来了,结论想到了,但还是不会。
A 【1109 B组】串
打表找规律。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
signed main(){
int n,k;scanf("%lld%lld",&n,&k);
if(k==0){puts("0");for(int i=1;i<=n;i++)printf("0 ");return 0;}
printf("%lld\n",(1+n/2)*(n/2)+(n&1)*(n/2+1));
for(int i=1;i<=n;i++)printf((i<=k-1||i==(n-(n-k)/2))?"1 ":"0 ");
return 0;
}
B 【1109 B组】艺术家
“保证任何两个区间都是不相交或包含的关系”,这句话提醒了我们把区间往树形结构上靠。把每个区间看成一个点,区间之间的包含关系使它们形成了一片森林。为了方便,我们再人为加上一个 \([0,n+1]\) 的区间作为根节点。
现在,一个区间合法的必要条件是它的儿子合法。所以我们只用考虑当前没合法过的区间中最下面的。更具体地,我们开一颗线段树,维护没合法过的区间中,最深的包含这个点的区间。显然一个区间最多合法一次,每次最多判断失败一次,总的是 \((n+m)\log n\) 的。
对于判断一个区间是否合法,我们定义 \(p_i\) 表示在 \(i\) 之前最大的满足 \(c_i=c_j\) 的 \(j\)(没有就等于负无穷),那么一个区间 \([l,r]\) 合法就等价于 \(\max\limits_{i=l}^r\{p_i\}<l\),线段树和 set 维护 \(p_i\) 即可。
点击查看代码
#include<iostream>
#include<set>
#include<queue>
#include<algorithm>
#include<stack>
using namespace std;
const int inf=1e9;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,m,q,a[500005];
struct Line{
int l,r,id;
}l[500005];
int cmpl(Line x,Line y){
if(x.l^y.l)return x.l<y.l;
return x.r>y.r;
}
int cmpi(Line x,Line y){
return x.id<y.id;
}
struct edge{
int v,nxt;
}e[1000005];
int tot,head[500005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
}
set<int>s[500005];
struct segtree1{
#define ls p<<1
#define rs p<<1|1
#define lson l,mid,ls
#define rson mid+1,r,rs
struct Node{
int ma;
}c[2000005];
void pushup(int p){
c[p].ma=max(c[ls].ma,c[rs].ma);
}
void build(int l,int r,int p){
if(l==r){
auto it=s[a[l]].lower_bound(l);
if(it==s[a[l]].begin())c[p].ma=-inf;
else it--,c[p].ma=(*it);
return;
}
int mid=(l+r)>>1;
build(lson),build(rson);
pushup(p);
}
void update(int l,int r,int p,int x,int k){
if(l==r){
c[p].ma=k;
return;
}
int mid=(l+r)>>1;
if(x<=mid)update(lson,x,k);
else update(rson,x,k);
pushup(p);
}
int query(int l,int r,int p,int L,int R){
if(L<=l&&r<=R)return c[p].ma;
int mid=(l+r)>>1,res=-inf;
if(L<=mid)res=max(res,query(lson,L,R));
if(R>mid)res=max(res,query(rson,L,R));
return res;
}
#undef ls
#undef rs
#undef lson
#undef rson
}Tr1;
struct segtree2{
#define ls p<<1
#define rs p<<1|1
#define lson l,mid,ls
#define rson mid+1,r,rs
struct Node{
int s,tag;
}c[2000005];
void pushup(int p){
c[p].s=c[ls].s+c[rs].s;
}
void pushdown(int l,int r,int p){
if(!c[p].tag)return;
int siz=r-l+1,ln=siz-(siz>>1),rn=siz>>1;
c[ls].tag=c[p].tag,c[rs].tag=c[p].tag;
c[ls].s=ln*c[p].tag,c[rs].s=rn*c[p].tag;
c[p].tag=0;
}
void build(int l,int r,int p){
c[p].tag=0;
if(l==r){c[p].s=0;return;}
int mid=(l+r)>>1;
build(lson),build(rson);
pushup(p);
}
void update(int l,int r,int p,int L,int R,int k){
if(L<=l&&r<=R){c[p].s=(r-l+1)*k,c[p].tag=k;return;}
int mid=(l+r)>>1;pushdown(l,r,p);
if(L<=mid)update(lson,L,R,k);
if(R>mid)update(rson,L,R,k);
pushup(p);
}
int query(int l,int r,int p,int L,int R){
if(L<=l&&r<=R)return c[p].s;
int mid=(l+r)>>1,res=0;pushdown(l,r,p);
if(L<=mid)res+=query(lson,L,R);
if(R>mid)res+=query(rson,L,R);
return res;
}
#undef ls
#undef rs
#undef lson
#undef rson
}Tr2;
int Fa[500005],tim[500005],son[500005];
void dfs(int u,int fa){
if(u!=m+1)Tr2.update(1,n,1,l[u].l,l[u].r,u);
Fa[u]=fa;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa)continue;
son[u]++;dfs(v,u);
}
}
signed main(){
n=read(),m=read(),q=read();
for(int i=1;i<=n;i++)a[i]=read(),s[a[i]].insert(i);
Tr1.build(1,n,1);
for(int i=1;i<=m;i++)l[i].l=read(),l[i].r=read(),l[i].id=i,tim[i]=inf;
sort(l+1,l+m+1,cmpl);stack<int>S;S.push(m+1);l[m+1]=(Line){0,n+1,m+1};
for(int i=1;i<=m;i++){
while(!S.empty()&&(l[S.top()].l>l[i].l||l[i].r>l[S.top()].r))S.pop();
if(!S.empty())add(l[S.top()].id,l[i].id),add(l[i].id,l[S.top()].id);
S.push(i);
}
sort(l+1,l+m+1,cmpi);Tr2.build(1,n,1);dfs(m+1,0);
queue<int>Q;
for(int i=1;i<=m;i++){
if(son[i]==0)Q.push(i);
}
while(!Q.empty()){
int u=Q.front();Q.pop();
if(Tr1.query(1,n,1,l[u].l,l[u].r)<l[u].l){
tim[u]=0;
Tr2.update(1,n,1,l[u].l,l[u].r,Fa[u]);
if(Fa[u]!=m+1&&Fa[u]!=0)Q.push(Fa[u]);
}
}
for(int i=1,x,y;i<=q;i++){
x=read(),y=read();
auto it=s[a[x]].lower_bound(x);it++;
if(it!=s[a[x]].end()){
int pre,suf=*it;it--;
if(it!=s[a[x]].begin())it--,pre=*it;
else pre=-inf;
Tr1.update(1,n,1,suf,pre);
}
s[a[x]].erase(x);
s[y].insert(x),a[x]=y;
it=s[y].lower_bound(x);
if(it!=s[y].begin()){
it--;int pre=*it;it++;
Tr1.update(1,n,1,x,pre);
}
else Tr1.update(1,n,1,x,-inf);
it++;
if(it!=s[y].end()){
Tr1.update(1,n,1,*it,x);
}
while(1){
int now=Tr2.query(1,n,1,x,x);
if(now==m+1||now==0)break;
if(Tr1.query(1,n,1,l[now].l,l[now].r)<l[now].l){
tim[now]=i;
Tr2.update(1,n,1,l[now].l,l[now].r,Fa[now]);
}
else break;
}
}
int ans=0;
for(int i=1;i<=m;i++){
if(tim[i]==inf)ans^=i+m;
else ans^=tim[i];
}
printf("%d\n",ans);
return 0;
}
C 【1109 B组】黑白树
手玩可以得到一个性质:如果直径两端点不同色,则答案一定是一个点到直径其中一个端点的距离(当然,如果同色,答案就是直径)。然后我们发现设答案为 \(d\),直径两端点为 \(u,v\),那么如果点 \(i\) 满足 \(\text{dist}(i,u/v)>d\),那么 \(i\) 一定不能和 \(u/v\) 同色。发现这样只能满足答案 \(\le d\),所以我们可以从小到大枚举 \(d\),求出前缀和再作差即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{int v,nxt;}e[2000005];
int tot,head[1000005];
void add(int u,int v){e[++tot]=(edge){v,head[u]},head[u]=tot;}
int d[1000005],pw[1000005],s[1000005],cU[1000005],cV[1000005],num[5];
vector<int>vU[1000005],vV[1000005];
void dfs(int u,int fa){
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(v!=fa)d[v]=d[u]+1,dfs(v,u);
}
}
signed main(){
int n=read();pw[0]=1;
for(int i=1;i<=n;i++)pw[i]=2ll*pw[i-1]%mod;
for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
d[1]=0;dfs(1,0);int U=1;for(int i=1;i<=n;i++)if(d[i]>d[U])U=i;
d[U]=0;dfs(U,0);int V=1;for(int i=1;i<=n;i++)if(d[i]>d[V])V=i;
for(int i=1;i<=n;i++)cU[i]=1,vU[d[i]].push_back(i);
int D=d[V],ans=0;ans=(ans+2ll*D%mod*pw[n-2]%mod)%mod;
d[V]=0;dfs(V,0);for(int i=1;i<=n;i++)cV[i]=1,vV[d[i]].push_back(i);
cU[V]=0,cV[U]=0;for(int i=1;i<=n;i++)num[cU[i]+cV[i]]++;
for(int d=D;d>=0;d--){
if(num[0])s[d]=0;else s[d]=pw[num[2]];
for(auto x:vU[d])num[cU[x]+cV[x]]--,cU[x]=0,num[cU[x]+cV[x]]++;
for(auto x:vV[d])num[cU[x]+cV[x]]--,cV[x]=0,num[cU[x]+cV[x]]++;
}
for(int d=0,c;d<=D;d++){
if(d==0)c=s[d];else c=(s[d]-s[d-1]+mod)%mod;
ans=(ans+2ll*d%mod*c%mod)%mod;
}
printf("%d\n",ans);
return 0;
}
D 【1109 B组】敢览求
不太会证复杂度。
C0276 【0628 B组】模拟测试
A 【0506 B组】博弈
显然,先手会优先砍断最小的环(因为这是在为他人做嫁衣),所以我们把数组从小到大排序,然后按顺序一个一个看。
假设当前枚举到 \(i\),不难发现转移至多有两种:1.先手砍断 \(a_i\),后手拿走 \(i\) 所有的绳结,同时需要砍断 \(a_{i+1}\)。这种情况可以看做双方交换了先后手顺序;2.当 \(a_i\ge 4\),先手砍断 \(a_i\),后手拿走 \(i\) 的 \(a_i-4\) 个绳结,同时把 4 个绳结砍成 2+2 的形式,然后后手一定会拿走这 4 个绳结并需要砍断 \(a_{i+1}\)。
定义 \(f_{i,0/1}\) 表示对于第 \(i\) 到第 \(n\) 个环,先手能取到的最小数量、以及此时后手的数量。为什么是最小值?因为先手实际上是没有选择权的,他的最优决策是随着后手的选择而固定的,所以后手可以“操纵”先手的行动。
注意:这里转移的时候 \(i\) 只能从 \(i+1\) 转移过来,不能迈更大的步子,即使这个转移明显不优也不行。为什么?因为你只能预测做了这个选择后,两人下一步的行动。对于更后面的决策,你不清楚也不需要。如果一次性从 \(i+2\) 或者更后面转移的话相当于你“钦定”了两人的行动,这显然是错的。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int a[105];pii f[105];
signed main(){
freopen("A.in","r",stdin);
freopen("A.out","w",stdout);
int n=read();
for(int i=1;i<=n;i++)a[i]=read();
sort(a+1,a+n+1);
f[n+1]=mk(0,0);
for(int i=n;i>=1;i--){
if(a[i]>=4)f[i]=min(mk(f[i+1].se,f[i+1].fi+a[i]),mk(f[i+1].fi+4,f[i+1].se+a[i]-4));
else f[i]=mk(f[i+1].se,f[i+1].fi+a[i]);
}
printf("%lld %lld\n",f[1].fi,f[1].se);
return 0;
}
B 【0506 B组】齿轮
如果齿轮 \(x\) 转动会带动 \(y\) 转动的话,设 \(x\) 的转速为 \(v\),可以得到 \(y\) 的转速(不考虑正负)为 \(\dfrac{va_x}{a_y}\),所以路径上齿轮的大小无关紧要。
所以现在我们就需要设计一个算法,能求出:1.两个点是否连通;2.连通块内是否有卡死的齿轮;3.两点间距离的奇偶性。发现 dsu 能完美满足以上需求。你可以写边带权/拓展域。
点击查看代码(边带权)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int gcd(int x,int y){
return (y==0)?x:gcd(y,x%y);
}
int fa[100005],d[100005],tag[100005];
int find(int x){
if(x==fa[x])return x;
int fx=find(fa[x]);
d[x]^=d[fa[x]];
return fa[x]=fx;
}
void merge(int x,int y){
int fx=find(x),fy=find(y);
if(fx==fy){
if((d[x]^d[y])!=1)tag[fx]=0;
return;
}
d[fy]=d[y],fa[fy]=y,tag[y]=tag[fy],d[y]=d[x]^1,fa[y]=fx;
if(tag[y]==0)tag[fx]=0;
}
int check(int x,int y){
int fx=find(x),fy=find(y);
if(!tag[fx]||fx!=fy)return 0;
return ((d[x]^d[y])==0?1:-1);
}
int a[100005];
signed main(){
freopen("B.in","r",stdin);
freopen("B.out","w",stdout);
int n=read(),m=read();
for(int i=1;i<=n;i++)a[i]=read(),fa[i]=i,d[i]=0,tag[i]=1;
while(m--){
int op=read();
if(op==1){
int x=read(),c=read();
a[x]=c;
}
else if(op==2){
int x=read(),y=read();
if(x==y)continue;
merge(x,y);
}
else {
int x=read(),y=read(),v=read(),o=check(x,y);
if(o==0)puts("0");
else{
int g=gcd(v*a[x],a[y]);
printf("%lld/%lld\n",o*v*a[x]/g,a[y]/g);
}
}
}
return 0;
}
点击查看代码(拓展域)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int gcd(int x,int y){
return (y==0)?x:gcd(y,x%y);
}
int fa[200005];
int find(int x){
return (x==fa[x])?x:fa[x]=find(fa[x]);
}
void merge(int x,int y){
int fx=find(x),fy=find(y);
if(fx!=fy)fa[fy]=fx;
}
int a[100005];
signed main(){
freopen("B.in","r",stdin);
freopen("B.out","w",stdout);
int n=read(),m=read();
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=2*n;i++)fa[i]=i;
while(m--){
int op=read();
if(op==1){
int x=read(),c=read();
a[x]=c;
}
else if(op==2){
int x=read(),y=read();
if(x==y)continue;
merge(x,y+n),merge(x+n,y);
}
else {
int x=read(),y=read(),v=read();
if((find(x)==find(y))^(find(x)==find(y+n))){
if(find(x)==find(y+n))printf("-");
int g=gcd(v*a[x],a[y]);
printf("%lld/%lld\n",v*a[x]/g,a[y]/g);
}
else puts("0");
}
}
return 0;
}
C 【0506 B组】排班方案
有一个贪心的错解能过,但是有反例,就不说了。
因为 \(d_i\) 不严格递增,我们可以按顺序安排每个人的工作时间(以别的顺序安排也行,但需要分讨,结果是一样的)。容易得到 \(c=\prod\limits_{i=1}^n(d_i-i+1)\)。令 \(D_i=d_i-i+1\),有 \(D_i\le D_{i+1}+1\),且每个 \(D_i\) 一定是 \(c\) 的因子。
现在,问题转化成了求 \(D_i\),让 \(D_n\) 最小。设 \(D_n=x\),\(\max\limits_{i=1}^n\{D_i\}=y\)。假设方案存在,则从 \(y\) 到 \(D_n\) 中间一定形如 \(y,y-1,y-2\ldots x\),因为你要把 \(y\) 到 \(x\) 连接起来。
假设我们已经确定了 \(x,y\) 的值,令 \(f_{i,j}\) 表示选若干 \(i\) 以内的数,使他们的乘积为 \(j\),最少需要选几个数,\(c'\) 为 \(c\) 除 \(x\) 到 \(y\) 之后的值,这对 \((x,y)\) 合法当且仅当 \(f_{y,c'}+(y-x+1)\le n\)。为什么是 \(\le n\)?因为少的你可以用 1 补齐。
发现 \(x,y,c'\) 都是 \(c\) 的因子,所以计算 \(f_{i,j}\) 是 \(O(d(c)^2)\) 的(枚举 \((x,y)\) 理论上也是 \(O(d(c)^2)\) 的,但有 \(13!=6227020800\),故固定 \(x\),可能合法的 \(y\) 不超过 13 个,所以这里是 \(O(d(c))\) 的)。\(d(c)\) 在 \(c\le 10^9\) 时最大值为 1344,可以通过。
对于构造方案,后面 \(y\) 到 \(x\) 那段就按这个顺序排,前面的凑 \(c'\) 的就从小到大排即可。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int inf=1e9;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,c,tot,B,d[5005],f[5005][5005],ans[3000005],r[40005];
int Find(int x){
if(x<=B)return r[x];
else return tot-r[c/x]+1;
}
void solve(){
n=read(),c=read(),tot=0,B=(int)sqrt(c);
for(int i=1;i*i<=c;i++){
if(c%i)continue;
d[++tot]=i;
if(c/i!=i)d[++tot]=c/i;
}
sort(d+1,d+tot+1);
for(int i=1;i<=tot&&d[i]<=B;i++){
r[d[i]]=i;
}
for(int i=0;i<=tot;i++){
for(int j=0;j<=tot;j++){
f[i][j]=inf;
}
}
f[0][1]=0;
for(int i=1;i<=tot;i++){
for(int j=1;j<=tot;j++){
f[i][j]=f[i-1][j];
if(d[j]%d[i]==0)f[i][j]=min(f[i][j],f[i][Find(d[j]/d[i])]+1);
}
}
int X=-1,Y=-1,C=-1;
for(int x=1;x<=tot;x++){
ll pro=d[x];
if(c%pro)continue;
if(f[x][Find(c/pro)]+1<=n){X=x,Y=x,C=Find(c/pro);break;}
for(int y=x+1;y<=tot;y++){
for(int i=d[y-1]+1;i<=d[y];i++){
pro*=i;
if(pro>c)break;
}
if(pro>c)break;
if(c%pro)continue;
if(f[y][Find(c/pro)]+(d[y]-d[x]+1)<=n){X=x,Y=y,C=Find(c/pro);break;}
}
if(X!=-1&&Y!=-1)break;
}
if(X==-1&&Y==-1)return puts("GG"),void();
int all=0,XX=X,YY=Y;
while(Y>1&&d[C]>1){
while(d[C]%d[Y]==0&&f[Y][C]==f[Y][Find(d[C]/d[Y])]+1){
ans[++all]=d[Y],C=Find(d[C]/d[Y]);
}
Y--;
}
for(int i=all+1;i<=n-(YY-XX+1);i++)ans[++all]=1;
if(all)sort(ans+1,ans+all+1);
for(int i=YY;i>=XX;i--)ans[++all]=d[i];
for(int i=1;i<=n;i++)printf("%d ",ans[i]+i-1);
puts("");
}
signed main(){
freopen("C.in","r",stdin);
freopen("C.out","w",stdout);
int T=read();
while(T--)solve();
return 0;
}
D 【0506 B组】简单的数据结构题
发现如果我们固定一个左端点 \(i\),因为区间变大 and 不增,且一旦变小至少会有一个 1 变为 0,那么这些区间的 and 至多有 \(\log A\) 种取值。可以从右往左枚举 \(i\),维护 \(pos_j\) 表示当前经过的所有 \(k\) 中,满足 \(a_k\) 第 \(j\) 位为 0 的最大的 \(k\)。这样我们就在 \(O(n\log A+n\log A\log\log A)\) 的时间内求出了每个 \(i\) 的 and 值的分割点(后面那个是排序 + 去重的时间)。
现在,我们把询问都离线下来,按左端点从大到小排序。从右往左枚举 \(i\),有一些分割点把 \(i\) 到 \(n\) 分成了若干段,以 \(i\) 为左端点,同一段内的若干个下标作为右端点,得到的 and 是相同的。这样,对于每个 \(i\),我们可以在 \(\log A\) 的时间内求出哪些区间的 and 是完全平方数(因为每段中间的数一定不会影响 and 的值)。
现在,我们把那些 and 为完全平方数的区间,以右端点为下标,丢到一颗线段树上维护。对于询问 \([ql,qr]\),我们只用查询线段树上 \([ql,qr]\) 区间的和即可。因为我们以右端点为下标,故会对答案造成贡献的区间 \([l,r]\) 满足 \(r\le qr\)。同时,枚举 \(i\) 的顺序也决定了 \(l\ge ql\),所以这样统计是不重不漏的。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int check(int n){
int x=(int)sqrt(n);
return (x*x==n);
}
struct segtree{
#define ls p<<1
#define rs p<<1|1
#define lson l,mid,ls
#define rson mid+1,r,rs
struct Node{
ll s,tag;
}c[400005];
void pushup(int p){
c[p].s=c[ls].s+c[rs].s;
}
void pushdown(int l,int r,int p){
if(!c[p].tag)return;
int siz=r-l+1,ln=siz-(siz>>1),rn=siz>>1;
c[ls].s+=c[p].tag*ln,c[rs].s+=c[p].tag*rn;
c[ls].tag+=c[p].tag,c[rs].tag+=c[p].tag;
c[p].tag=0;
}
void build(int l,int r,int p){
c[p].tag=0;
if(l==r){c[p].s=0;return;}
int mid=(l+r)>>1;
build(lson),build(rson);
pushup(p);
}
void update(int l,int r,int p,int L,int R,int k){
if(L>R)return;
if(L<=l&&r<=R){c[p].s+=1ll*(r-l+1)*k,c[p].tag+=k;return;}
int mid=(l+r)>>1;pushdown(l,r,p);
if(L<=mid)update(lson,L,R,k);
if(R>mid)update(rson,L,R,k);
pushup(p);
}
ll query(int l,int r,int p,int L,int R){
if(L>R)return 0;
if(L<=l&&r<=R)return c[p].s;
int mid=(l+r)>>1;ll res=0;pushdown(l,r,p);
if(L<=mid)res+=query(lson,L,R);
if(R>mid)res+=query(rson,L,R);
return res;
}
#undef ls
#undef rs
#undef lson
#undef rson
}Tr;
int a[100005],pos[30],r[500005];vector<int>v,q[100005];ll ans[500005];
signed main(){
freopen("D.in","r",stdin);
freopen("D.out","w",stdout);
int T=read();
while(T--){
int n=read(),m=read();Tr.build(1,n,1);
for(int o=0;o<30;o++)pos[o]=n+1;
for(int i=1;i<=n;i++)a[i]=read(),q[i].clear();
for(int i=1,l;i<=m;i++)l=read(),r[i]=read(),q[l].push_back(i);
for(int i=n,val;i>=1;i--){
v.clear(),val=a[i];
for(int o=0;o<30;o++)if(!((a[i]>>o)&1ll))pos[o]=i;
for(int o=0;o<30;o++)if(pos[o]!=n+1)v.push_back(pos[o]);
sort(v.begin(),v.end());v.erase(unique(v.begin(),v.end()),v.end());
for(int j=0;j<(int)v.size();j++){
if(check(val))Tr.update(1,n,1,((j==0)?i:v[j-1]),v[j]-1,1);
val&=a[v[j]];
}
if(check(val))Tr.update(1,n,1,(v.empty()?i:v.back()),n,1);
for(auto j:q[i])ans[j]=Tr.query(1,n,1,i,r[j]);
}
for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
}
return 0;
}
C0279 【0630 B组】模拟测试
坤皇高质量模拟赛!
A 【0630 B组】冤
你可以假定 \(\forall i\in[1,n],a_i\ge1\)。
唉,被诈骗了。
因为三角形需要满足两边之和大于第三边,显然最优选法是排序后取相邻三个。考虑最坏情况,即所有长度构成斐波那契数列。因为 \(a_i\le10^9\),故长度过大的区间一定合法,剩下的暴力判断即可。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int a[1500005],b[105];
signed main(){
int n=read(),q=read();
for(int i=1;i<=n;i++){
a[i]=read();
}
while(q--){
int l=read(),r=read(),m=0,fl=0;
if(r-l+1>50){puts("YES");continue;}
for(int i=l;i<=r;i++)b[++m]=a[i];
sort(b+1,b+m+1);
for(int i=2;i<=m-1;i++)if(b[i-1]+b[i]>b[i+1]){fl=1;break;}
puts(fl?"YES":"NO");
}
return 0;
}
下面说一个歪解。
如果你不知道这个性质,最暴力的方法是直接区间排序判断。这会白白浪费很多时间,我们考虑把询问离线下来莫队。不太能支持删除,所以我们直接莽一个回滚莫队+压位 trie。
这样还是过不去,所以我们考虑一点小小的剪枝:对于左端点在同一块里的询问,如果右边已经找到合法的三元组了,那以后的那些左端点在同一块里的询问也一定是合法的,可以直接跳过。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
using namespace std;
const int inf=1e9;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct Que{
int l,r,id;
}q[1500005];
int bel[1500005],bl[2005],br[2005];
int cmp(Que x,Que y){
if(bel[x.l]^bel[y.l])return bel[x.l]<bel[y.l];
return x.r<y.r;
}
//
#define clz(x) (__builtin_clzll(x))
#define ctz(x) (__builtin_ctzll(x))
ull BUFF[70000005],*BT=BUFF+sizeof(BUFF)/sizeof(ull);
ull *alloc(int siz){return BT-=siz;}
const int g=6,mod=(1<<g)-1;
int dep;ull *c[6];
void init(int siz){
for(dep=1;;dep++){
int cnt=(siz+(1ull<<g*dep)-1)>>g*dep;
c[dep-1]=alloc(cnt);
if(cnt==1)return;
}
}
void insert(int x){
for(int i=0;i<dep;i++){
ull p=1ull<<(x>>i*g&mod);
if(c[i][x>>(i+1)*g]&p)return;
c[i][x>>(i+1)*g]|=p;
}
}
void erase(int x){
for(int i=0;i<dep;i++)
if(c[i][x>>(i+1)*g]&=~(1ull<<(x>>i*g&mod)))return;
}
int getpre(int x){
for(int i=0;i<dep;i++){
int cur=(x>>i*g)&mod;ull v=c[i][x>>(i+1)*g];
if(v&((1ull<<cur)-1)){
int res=x>>(i+1)*g<<(i+1)*g;res+=(mod-clz(v&((1ull<<cur)-1)))<<i*g;
for(int j=i-1;j>=0;j--)res+=(mod-clz(c[j][res>>(j+1)*g]))<<j*g;
return res;
}
}
return -1;
}
int getsuf(int x){
for(int i=0;i<dep;i++){
int cur=(x>>i*g)&mod;ull v=c[i][x>>(i+1)*g];
if(v>>cur>1){
int res=x>>(i+1)*g<<(i+1)*g;res+=(ctz(v>>(cur+1))+cur+1)<<i*g;
for(int j=i-1;j>=0;j--)res+=ctz(c[j][res>>(j+1)*g])<<j*g;
return res;
}
}
return -1;
}
int B[1500005],tot,a[1500005],b[1500005];
int get(int x){
if(b[x]==0){
return 0;
}
if(B[x]>=2){
return 1;
}
else if(B[x]==1){
int pre=getpre(x),suf=getsuf(x);
if(pre!=-1&&suf!=-1&&b[pre]+b[x]>b[suf])return 1;
if(pre!=-1&&b[pre]+b[x]>b[x])return 1;
if(suf!=-1&&b[x]+b[x]>b[suf])return 1;
return 0;
}
else{
int pre=getpre(x),suf=getsuf(x);
if(pre!=-1&&suf!=-1&&b[pre]+b[x]>b[suf])return 1;
if(pre!=-1){
int ppre;
if(B[pre]>1)ppre=pre;
else ppre=getpre(pre);
if(ppre!=-1&&b[ppre]+b[pre]>b[x])return 1;
}
if(suf!=-1){
int ssuf;
if(B[suf]>1)ssuf=suf;
else ssuf=getsuf(suf);
if(ssuf!=-1&&b[x]+b[suf]>b[ssuf])return 1;
}
return 0;
}
}
int ask(int l,int r){
for(int i=l;i<=r;i++){
if(get(a[i])){
for(int j=l;j<i;j++){
B[a[j]]--;
if(B[a[j]]==0)erase(a[j]);
}
return 1;
}
B[a[i]]++;
if(B[a[i]]==1)insert(a[i]);
}
for(int i=l;i<=r;i++){
B[a[i]]--;
if(B[a[i]]==0)erase(a[i]);
}
return 0;
}
int ans[1500005];
signed main(){
// freopen("bitterness.in","r",stdin);
// freopen("bitterness.out","w",stdout);
int n=read(),m=read();
for(int i=1;i<=n;i++)a[i]=b[++tot]=read();
sort(b+1,b+tot+1);tot=unique(b+1,b+tot+1)-b-1;
init(tot+5);
for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+tot+1,a[i])-b;
for(int i=1;i<=m;i++){
q[i].l=read(),q[i].r=read(),q[i].id=i;
}
int siz=(int)sqrt(n),num=(n+siz-1)/siz;
for(int i=1;i<=n;i++)bel[i]=(i-1)/siz+1;
for(int i=1;i<=num;i++)bl[i]=(i-1)*siz+1,br[i]=i*siz;
br[num]=n;
sort(q+1,q+m+1,cmp);
for(int i=1;i<=m;i++)if(bel[q[i].l]==bel[q[i].r])ans[q[i].id]=ask(q[i].l,q[i].r);
for(int i=1,j=1;i<=num;i++){
int L=br[i]+1,R=br[i],res=0;
while(j<=m){
if(bel[q[j].l]!=i)break;
if(bel[q[j].l]==bel[q[j].r]){j++;continue;}
if(res){ans[q[j].id]=1;j++;continue;}
while(R<q[j].r){
R++,res|=get(a[R]),B[a[R]]++;
if(B[a[R]]==1)insert(a[R]);
}
int tmp=0;
while(L>q[j].l){
L--,tmp|=get(a[L]),B[a[L]]++;
if(B[a[L]]==1)insert(a[L]);
}
ans[q[j].id]=(res|tmp),j++;
while(L<=br[i]){
B[a[L]]--;
if(B[a[L]]==0)erase(a[L]);
L++;
}
}
while(R>br[i]){
B[a[R]]--;
if(B[a[R]]==0)erase(a[R]);
R--;
}
}
for(int i=1;i<=m;i++)puts((ans[i]?"YES":"NO"));
return 0;
}
B 【0630 B组】缘
首先这玩意是一个格雷码,但是知道了好像也没什么卵用。
然后 \(u\to v\) 等价于 \(u\oplus v\to 0\)。记 \(t=u\oplus v\),考虑这样一个流程:固定 \(t\) 中一个 1,对剩下的没有固定过的位做格雷码,做了一圈之后再把这一位改成 0,然后重复这个流程。因为格雷码是一个环,所以抛开固定的哪一位,剩下那些位在固定的这位 \(0\to 1\) 的时候只会变一位。所以这个流程可以看做不断地在 \(t\) 中选一个 1,把它变为 0,然后再把另一位取反。这样做显然对 \(t\) 中 1 的数量的奇偶性没有影响,所以我们来分个类。
奇数:你可以先化成一个 1 然后全是 0 的形式,然后你每次做的时候都把一个 0 改成 1,这样就能刚好遍历完 \(2^n\) 个数。
偶数:你可以先化成两个 1 然后全是 0 的形式,然后你每次做的时候都把一个 0 改成 1,这样最后一定是两个 1 的形式,直接 \(11\to 10\to 00\) 刚好遍历 \(2^n-1\) 个数。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int lowbit(int x){
return x&-x;
}
int checkbit(int x,int y){
return ((x>>y)&1ll);
}
int popcount(int x){
int cnt=0;
while(x)cnt+=(x&1ll),x>>=1;
return cnt;
}
int safemod(int x,int y,int mod){
return ((x+y)%mod+mod)%mod;
}
int getmask(int x,int msk){
int y=0,cnt=0;
while(msk){
int o=lowbit(msk);msk^=o;
y|=((x&o)!=0)<<cnt,cnt++;
}
return y;
}
int createmask(int x,int msk){
int y=0,cnt=0;
while(msk){
int o=lowbit(msk);msk^=o;
y|=o*checkbit(x,cnt),cnt++;
}
return y;
}
int g[300005],rnk[300005];vector<int>ans;
signed main(){
int n=read(),u=read(),v=read(),all=(1ll<<n)-1;u^=v;
if(u==0)return printf("0\n%lld\n",v),0;
for(int i=0;i<(1ll<<n);i++)g[i]=i^(i/2),rnk[g[i]]=i;
int fix=0,ucnt=n;ans.push_back(u);
while(ucnt>2||(ucnt>0&&popcount(u)%2==1)){
int p=0;
while(p<n&&(checkbit(fix,p)||!checkbit(u,p)))p++;
fix|=(1<<p),ucnt--;
int k=getmask(u,all^fix),t=(1<<ucnt),d=1;
if(g[safemod(rnk[k],-1,t)]==0)d=-1;
for(int i=safemod(rnk[k],d,t);i!=rnk[k];i=safemod(i,d,t)){
int nxt=(u&fix)|createmask(g[i],all^fix);
ans.push_back(nxt),u=nxt;
}
u^=(1<<p),ans.push_back(u);
}
if(ucnt==2&&popcount(u)==2){
ans.push_back((u&fix)|createmask(1,all^fix));
ans.push_back((u&fix)|createmask(0,all^fix));
}
printf("%lld\n",(int)ans.size()-1);
for(auto x:ans)printf("%lld ",x^v);
return 0;
}
C 【0630 B组】愿
首先考虑哪些边是有用的。你发现我们需要保留的边一定是最小生成树,这个你可以从类似 kruskal 加边的角度去推。
我们现在把所有边按边权从小到大排序,依次加边。定义 \(f_{i,j}\) 表示当前枚举到第 \(i\) 条边,使得 \(j\) 所在连通块每个点水位小于 \(w_i\) 的方案数。可以再记录一个 \(h_i\) 表示以 \(i\) 为根的连通块当前的水位限制是多少。因为每条边只会改变两个连通块的答案,所以第一维可以省略。转移式很简单,这里不写了。
对于那些没有与 1 相连的连通块,它们只对方案数有影响,这就是一个乘法原理,最后答案乘一下即可。对于那些与 1 节点相连的点,这个跟刚刚 dp 转移的相似。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353,i2=(mod+1)/2;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int fa[100005],h[100005],f[100005];
int find(int x){
return ((x==fa[x])?x:fa[x]=find(fa[x]));
}
struct edge{
int u,v,w;
}e[200005];
int cmp(edge x,edge y){
return x.w<y.w;
}
signed main(){
int n=read(),m=read(),d=read(),ans=0;
for(int i=1;i<=n;i++)fa[i]=i,h[i]=1,f[i]=0;
for(int i=1,u,v,w;i<=m;i++){
u=read(),v=read(),w=read(),e[i]=(edge){u,v,w};
}
sort(e+1,e+m+1,cmp);
for(int i=1;i<=m;i++){
int u=find(e[i].u),v=find(e[i].v),w=e[i].w;
if(u==v)continue;
if(u==find(1)||v==find(1)){
if(v==find(1))swap(u,v);
ans=1ll*(ans+1ll*(w-1+h[u])%mod*(w-h[u])%mod*i2%mod)%mod*(f[v]+w-h[v])%mod,h[u]=w,fa[v]=u;
continue;
}
f[u]=1ll*(f[u]+w-h[u])%mod*(f[v]+w-h[v])%mod,h[u]=w,fa[v]=u;
}
ans=1ll*(ans+1ll*(d+h[find(1)])%mod*(d-h[find(1)]+1)%mod*i2%mod)%mod;
for(int i=1;i<=n;i++){
if(find(i)!=find(1)&&find(i)==i)ans=1ll*ans*((f[i]+(d-h[i]+1))%mod)%mod;
}
printf("%d\n",ans);
return 0;
}
D 【0630 B组】怨
因为数区间不好做,我们数端点,数量除以 2 就是答案。考虑把所有询问离线下来,从小到大枚举询问的右端点 \(i\)。在枚举的同时,把每个序列中的元素区间看做对数轴的一个覆盖,用 ODT 维护一下数轴上每个位置最后一次被覆盖的时间戳。如果有相邻的两个区间时间戳分别为 \(x\) 和 \(y\)(设 \(x<y\)),那么所有形如 \((j,i)\) 的满足 \(j\in (x,y]\) 的询问都会在这两个区间的交界处找到端点。你可以用 BIT 维护这个区间加和单点差。
当然,实际写起来你会发现因为区间是连续的,普通的 ODT 写法非常【数据删除】,所以你可以把维护信息改为不同时间戳交接的位置,以及左右区间的时间戳。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,SIZ=1e9;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,m,l[400005],r[400005],ql[1000005],qr[1000005];
vector<int>v[400005];
struct BIT{
int c[1000005];
void build(){
for(int i=1;i<=n;i++)c[i]=0;
}
void add(int x,int k){
for(;x<=n;x+=x&-x)c[x]+=k;
}
void add(int l,int r,int k){
if(l>r)return;
add(l,k),add(r+1,-k);
}
int ask(int x){
int res=0;
for(;x;x-=x&-x)res+=c[x];
return res;
}
}Tr;
struct Node{
int p,lc,rc;
bool operator <(const Node &o)const{
return p<o.p;
}
};
set<Node>s;
void assign(int l,int r,int v){
auto itl=s.lower_bound((Node){l,0,0}),itr=s.lower_bound((Node){r+1,0,0});
int lc=prev(itl)->rc,rc=itr->lc;
for(auto it=itl;it!=itr;it++)Tr.add(min(it->lc,it->rc)+1,max(it->lc,it->rc),-1);
s.erase(itl,itr);s.insert((Node){l,lc,v}),s.insert((Node){r,v,rc});
Tr.add(min(lc,v)+1,max(lc,v),1);Tr.add(min(v,rc)+1,max(v,rc),1);
}
int ans[1000005];
signed main(){
n=read(),m=read();
for(int i=1;i<=n;i++)l[i]=read(),r[i]=read();
for(int i=1;i<=m;i++)ql[i]=read(),qr[i]=read(),v[qr[i]].push_back(i);
s.insert((Node){0,0,0}),s.insert((Node){SIZ+1,0,0});Tr.build();
for(int i=1;i<=n;i++){
assign(l[i],r[i],i);
for(auto x:v[i])ans[x]=Tr.ask(ql[x]);
}
for(int i=1;i<=m;i++)printf("%lld\n",ans[i]/2);
return 0;
}

浙公网安备 33010602011771号