10.14模拟赛

t1:

Alice 正在玩一个 multiset。最初,集合中只有一个元素 \(0\)。每一轮,集合中的每一个元素 \(x\) 都有 \(3\) 种可能的操作:

  • 1.\(x\) 加上 \(1\),即 \(x = x +1\)

  • 2.\(x\) 分裂成两个非负整数 \(y\)\(z\)。即 \(x = y + z\),且 \(y \ge0\)\(z \ge 0\)

  • 3.什么都不做。

注意,在一轮中每个元素只能选择一种操作。

Alice 已经玩了很久了,但她并不知道自己已经玩了多少轮。现在给出最终的集合,请你输出 Alice 最少玩的轮数。

对于 \(100\%\) 的数据:$ N \le 1,000,000$, $A_i \le 1,000,000 $。

题解:

容易想到贪心。因为先分裂比增加后再分裂更优。但何时边分裂边增加呢?何时只分裂不增加呢?

显然正序模拟不可行,考虑倒序模拟。

对于当前状态中的 \(0\) 元素将他们两两合并(\(0\) 元素只能被快速分裂得到),对于当前状态中的所有非 \(0\) 元素将他们共同 \(-1\)

当然我们也可以正序套上二分来解决。

t2:

不同于我们所在的世界, 在颜神的世界里, 时间线并不是一条直线, 而是一个环。设时间线连成的环 的长度为 \(L\), 当颜神顺着时间线前进时, 若跨过了第 \(L\) 个时刻, 就会回到第 \(0\) 个时刻。反过来, 若逆着 时间线前进, 且跨过了第 $0 $ 个时刻, 则会回到第 \(L\) 个时刻。在整个时间环上有 \(n\) 个颜神, 每个颜神都是 顺着时间线或逆着时间线以相同的速度前进。若同一时刻沿着不同方向行走的两个颜神相遇, 则会产生 正反物质湮灭, 导致无法预料的灾难。因此若两个颜神同时位于某一个时刻 (可能是小数时刻), 则他们 会在该时刻同时进入反转机来改变自己的运动方向。

现在给定每个颜神所在的位置以及运动方向, 请告诉管理时空的至高之神 beginend, \(T\) 个时刻以后, 每个颜神在时间环中所在的位置。

对于 \(100 \%\) 的数据, \(1 \leq n \leq 10^{5}, 1 \leq L, T \leq 10^{9}, 0 \leq X_{1}<X_{2}<\cdots<X_{n} \leq L-1,1 \leq W_{i} \leq 2\)

题解:

考虑所有颜神相对位置都不变,而且换方向等同于替换编号,考虑先直接求除每个点不换方向的答案。那么我们只用知道其中一个点具体是哪个点就好了。

所以先求出所有颜神最终所在的位置集合。初始状态坐标最小的颜神编号为 \(1\) 。在行走过程中, 若有一 个颜神从 \(0\) 走到 \(L-1\), 那么坐标最小的颜神的编号就会加 \(1\) 。若有一个颜神从 \(L-1\) 走到 \(0\), 坐标最 小的颜神的编号就会减 1 。这样就可以得到最终位置的坐标最小的是哪一个颜神了。

t3:

由于阿巴阿巴的原因,你需要给歪比歪比的一张图进行定向

记这张无向图为 \(G\)无自环,但可能有重边

每条边的边权为 \(1,3,5\) 中的一种

\(deg(x)=\sum_{(u,v)\in E}[[u=x]\cup [v=x]]\),满足 \(\forall x\in V,2|deg(x)\)

\(\cup\) 表示或者, \([~]\) 表示条件判断,括号内为真返回值为 \(1\),否则为 \(0\)

现在你需要给每没边定向,即对于任意一条无向边 \((u,v)\),设为 \(u\to v\) 或者 \(v\to u\) 的有向边

现在设 \((u,v)\) 表示一个 \(u\to v\) 的边,\(w(u,v)\) 表示此边的边权

\(\forall x\in V\),设 \(S1=\sum_{(u,v)\in E\cap u=x}w(u,v),S2=sum_{(u,v)\in E\cap v=x}w(u,v)\),满足 \(|S1-S2|\leqslant 4\)

\(\cap\) 表示集合并。

可能存在多种定向方式,输出任意一种即可;如果不存在定向方式,输出一行一个 \(-1\)

注: \(V=\left \{1,2,3,...,n\right \}\)

题解:

\(D(u)=S1-S2\),同时我们准备一张新图\(G'(V,\emptyset)\)

首先对每种边权分别处理

设当前处理的是w,那么把边权为w的边拿出来,组成新图\(G_w\left \{V,E_w\right \}\)

deg(u)表示u的度数

首先我们给\(G_w\)新填一个虚点y,然后我们给\(G_w\)加一些边

如果\(deg(u)\equiv 1(mod\ 2)\),则连\(y-u\)(无向边)

然后我们可以对\(G_w\)的每个联通块跑一个欧拉回路

若联通块里面没有y,直接按回路的方向定向(对于A性质,y没有添加的必要,于是就解决了)

对于y所在的联通块,令这个回路从y出发,然后最后回到y

把y在这个回路中的位置标出来,形如

\(y-a_1-....-b_1-y-a_2-...-b_2-y-...-y-a_l-...-b_l-y\)

冷静分析

  1. \(a_i,b_i\)互不相同
  2. \(\forall_{1\leqslant i\leqslant l},deg(a_i)\equiv deg(b_i)\equiv 1(mod \ 2)\)
  3. 对于$u\in y $ 所在的联通块 $ \ deg(u)\equiv 1(mod\ 2)$,u在a或b中出现恰好一次

按照D的定义,截取\(a_i-...-b_i\)这一段路径,实际上执行了\(D(a_i)+=w\)\(D(b_i)-=w\) 的操作,把路径上的边翻转,就执行了\(D(a_i)-=w\)\(D(b_i)+=w\)

我们对G'添加边\((a_i,b_i,w)\)

\(w\in\left\{1,3,5\right\}\)都执行上面的操作

现在我们只要对G‘定向满足条件就好了

因为保证了G中\(deg(u)\equiv 0(mod\ 2)\)

所以G'满足B性质

观察G'中的联通块,要么是单点,要么是一个环,环的定向用顺时针方向或者逆时针方向均可

#include<bits/stdc++.h>
using namespace std;
 
#define cout cerr
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
 
typedef long long ll;
typedef pair<int,int> pii;
#define FR first
#define SE second
 
namespace IO{
    char buf[1000010],*cur=buf+1000010;
    inline char getc(){
        (cur==buf+1000010)?fread(cur=buf,1,1000010,stdin):0;
        return *cur++;
    }
    char buff[1000010],*curr=buff;
    inline void flush(){
        fwrite(buff,1,curr-buff,stdout);
    }
    inline void putc(const char &ch){
        (curr==buff+1000010)?fwrite(curr=buff,1,1000010,stdout):0;
        *curr++=ch;
    }
   
    inline void rd(int &x){
        x=0;char ch=getc();int f=1;
        while(ch<'0'||ch>'9'){
            if(ch=='-')f=-1;
            ch=getc();
        }
        while(ch>='0'&&ch<='9'){
            x=x*10+ch-'0';
            ch=getc();
        }
        x*=f;
    }
  
    char st[60];int tp;
    void PT(int x){
        if(x==0)putc('0');
        else{
            while(x>0){
                st[++tp]=x%10+'0';
                x/=10;
            }
        }
        while(tp)putc(st[tp--]);
    }
}
using IO::getc;
using IO::putc;
using IO::rd;
using IO::PT;
 
int n,m;
#define V 1500010
#define E 3000010
 
int stk[E],top,U[E],len;
bool c[E<<1];
int rev(int x){
    if(x&1)return x+1;
    return x-1;
}
namespace sub{
    int head[V],v[E],nxt[E],tot=0;
    inline void add_edge(int s,int e){
        tot++;v[tot]=e;nxt[tot]=head[s];head[s]=tot;
        tot++;v[tot]=s;nxt[tot]=head[e];head[e]=tot;
    }
    int p[V],cnt;
    bool vis[V];
    void Euler(int u){
        while(head[u]){
            int t=head[u];head[u]=nxt[head[u]];
            if(!vis[(t+1)/2]){
                vis[(t+1)/2]=true;
                Euler(v[t]);
                p[++cnt]=t;
            }
        }
    }
    void solve(){
        rep(u,1,n){
            cnt=0;Euler(u);
            rep(i,1,cnt){
                int t=(p[i]+1)/2;
                if(p[i]&1)
                    rep(j,U[t-1]+1,U[t])c[stk[j]]=true;
                else
                    rep(j,U[t-1]+1,U[t])c[rev(stk[j])]=true;
            }
        }
    }
}
 
struct Edge{
    int s,e;
}edge[E]; 
vector<int> vec[3];
int head[V],v[E*3],nxt[E*3],tot=0;
bool deg[V];
bool vis[V*3];
inline void add_edge(int s,int e){
    deg[s]^=1;deg[e]^=1;
    tot++;v[tot]=e;nxt[tot]=head[s];head[s]=tot;
    tot++;v[tot]=s;nxt[tot]=head[e];head[e]=tot;
}
 
int p[V*3],cnt;
void Euler(int u){
    while(head[u]){
        int t=head[u];head[u]=nxt[head[u]];
        if(!vis[(t+1)/2]){
            vis[(t+1)/2]=true;
            Euler(v[t]);
            p[++cnt]=t;
        }
    }
}
 
void calc(vector<int> &g){
    tot=0;memset(head,0,sizeof(int)*(n+2));memset(deg,0,sizeof(bool)*(n+2));
    for(int i=0;i<g.size();++i){
        int id=g[i];
        add_edge(edge[id].s,edge[id].e);
    }
    int pre=tot;
    rep(i,1,n)
        if(deg[i])add_edge(n+1,i);
    memset(vis,false,sizeof(bool)*(tot/2+1));
    cnt=0;Euler(n+1);
    reverse(p+1,p+cnt+1);
    for(int i=1,lst;i<=cnt;i=lst+1)
        if(p[i]>pre){
            lst=i+1;
            while(p[lst]<=pre){
                int t=p[lst],id=g[(t+1)/2-1];
                stk[++top]=id*2-(t&1);
                lst++;
            }
            U[++len]=top;
            sub::add_edge(v[p[i]],v[rev(p[lst])]);
        }
    rep(u,1,n){
        cnt=0;Euler(u);
        if(cnt){
            rep(i,1,cnt){
                int t=p[i],id=g[(t+1)/2-1];
                c[2*id-(t&1)]=true;
            }
        }
    }
}
 
int main(){
	freopen("graph.in","r",stdin);freopen("graph.out","w",stdout); 
    rd(n);rd(m);
    int s,e,t;
    rep(i,1,m){
        rd(edge[i].s);rd(edge[i].e);rd(t);
        vec[t/2].push_back(i);
    }
    rep(i,0,2)calc(vec[i]);
    sub::solve();
    rep(i,1,m)
        if(c[2*i-1])putc('0');
        else putc('1');
    IO::flush();
    return 0;
}

t4:

你得到了一个长度为 \(\mathrm{n}\) 的数列 \(A\), 要求支持以下 2 种操作: 第一种是给 定 \(L, R, X\),要求把区间中比 \(X\) 小的数字全部修改为 \(X\) 第二种是给定 \(L, R, K, X\) 查询区间中比 \(\mathrm{X}\) 小的最小的 \(\mathrm{K}\) 个数,并且将它们升序输出,没有则输出 \(-1\)

对于全部数据, 满足 \(1<=\mathrm{n}, \mathrm{m}<=500000,1<=\mathrm{L}<=\mathrm{R}<=\mathrm{n}, 1<=\mathrm{K}<=\mathrm{n}, 1<=\mathrm{A_i}, \mathrm{X}<=10^9\), 对于所有 操作 2 中的 \(\mathrm{K}, \mathrm{K}\) 的总和不超过 \(5 \times 10^6\)

题解:

Tag: 线段树, 堆

小调查: 有多少选手现场学习并实现了 segment tree beats 呢?

\(\# 1: n, m<=3000\), 直接模拟, 考察选手对 std: : sort 的使用

\(\# 2\) : RMQ 问题, 可以通过 ST 表或者线段树实现, 注意 -1 的判断

# 3:区间把比一个数小的数字变成这个数, 查询区间最小值.

因为查询内容与区间和值等无关, 所以无需 Segment Tree Beats 中的方法实现,
直接在每个线段树节点上维护当前区间 min 值以及区间与哪个数取 max 的 lazy 标记即

# 4: 考虑操作 2 怎么解决, 因为 \(\mathrm{K}\) 的和值不超过 \(5 * 10^{\wedge} 6\), 考虑与其相关的做法.

对序列建一棵线段树, 线段树上每个节点维护当前区间最小值的值和位置, 同时用一个 小根堆去维护最后的答案, 线段树上每次 query \((1, r)\) 返回一组 \(\{v a 1, p o s, 1, r\}\) 表示 \([1, r]\) 区 间中最小值为 val, 位置为 pos, 按照 val 去建立这个小根堆.

开始把 query \((1, n)\) 的结果入堆, 然后重复 \(K\) 次以下操作:

每次弹出堆顶, 显然这时的 \(val\) 是所剩下的数中的最小值,

然后将 \(query ( 1 , pos -1)\) 以及 \(query (pos +1, r)\) 的结果入堆,

这样得到的 \(K\) 个堆顶显然是最小的 \(K\) 个元素, 判断 \(-1\) 后输出即可,

而关于时间复杂度, \(query\) 与入堆的次数都是 \(2 \mathrm{~K}\) 次, 弹出的次数为 \(\mathrm{K}\) 次,

所以总复杂度为 \(0(n+\operatorname{sigma}(K) * \log n)\).

\(\# 5, \# 6\) : 发现在# 4 与 \(\# 3\) 的维护区间 \(\min\) 值并不会产生影响, 于是将 \(\# 3\), # 4一起实现, 即 可通过全部数据, 时间复杂度 \(0((n+\operatorname{sigma}(K)) * \log n)\), 空间复杂度 \(0(n+\operatorname{sigma}(K))\).

对于 \(1<=n, m<=100000\) 的另解:

如果你想不到堆+线段树维护的方法, 可以利用下发课件中开篇提到的平衡树套支持区 间取 max 的线段树做到 \(0((\mathrm{n}+\mathrm{m}) \log^3 \mathrm{n})\), 也可以用分块做到 \(O (n\sqrt n\log n)\), 均可通过 满足 \(1<=n, m<=100000\) 的数据得到高分.

#include<bits/stdc++.h>
#define ls (p<<1)
#define rs (p<<1|1)
#define int long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
const int N=5e5+10;
int n,m,k,T,a[N],laz[N<<2],ans[N],cnt;
vector<int> g[N];
struct tree{
	int mi,pos;
}t[N<<2];
void pushup(int p){
	if(t[ls].mi<=t[rs].mi) t[p]=t[ls];
	else t[p]=t[rs];
}
struct node{
	int x,pos,l,r;
	friend bool operator<(node a,node b){
		return a.x>b.x;
	}
};priority_queue<node> q;
void pushdown(int p){
	if(laz[p]==0) return;
	int k=laz[p];laz[p]=0;
	laz[ls]=max(k,laz[ls]);
	laz[rs]=max(laz[rs],k);
	if(t[ls].mi<laz[ls]) t[ls]={laz[ls],t[ls].pos};
	if(t[rs].mi<laz[rs]) t[rs]={laz[rs],t[rs].pos};
}
void build(int p,int l,int r){
	if(l==r){
		t[p]={a[l],l};return;
	}int mid=l+r>>1;
	build(ls,l,mid);build(rs,mid+1,r);
	pushup(p);
}
void upd(int p,int l,int r,int x,int y,int k){
	if(x<=l&&r<=y){
		laz[p]=max(laz[p],k);
		if(t[p].mi<k) t[p]={k,t[p].pos};
		return;
	}int mid=l+r>>1;pushdown(p);
	if(x<=mid) upd(ls,l,mid,x,y,k);
	if(y>mid) upd(rs,mid+1,r,x,y,k);
	pushup(p);
}
tree query(int p,int l,int r,int x,int y){
	if(x<=l&&r<=y){
		return t[p];
	}int mid=l+r>>1;pushdown(p);
	if(y<=mid) return query(ls,l,mid,x,y);
	else if(x>mid) return query(rs,mid+1,r,x,y);
	else{
		tree res1=query(ls,l,mid,x,y);
		tree res2=query(rs,mid+1,r,x,y);
		if(res1.mi<=res2.mi) return res1;
		else return res2;
	}
}
void solve(int l,int r,int x,int k){
	tree res=query(1,1,n,l,r);
	cnt=0;q.push({res.mi,res.pos,l,r});
	while(k--){
		node t=q.top();q.pop();
		if(t.x>=x){cnt=-1;return;}
		ans[++cnt]=t.x;
		if(t.l!=t.pos){
			tree res1=query(1,1,n,t.l,t.pos-1);q.push({res1.mi,res1.pos,t.l,t.pos-1});
		}if(t.r!=t.pos){
			tree res2=query(1,1,n,t.pos+1,t.r);q.push({res2.mi,res2.pos,t.pos+1,t.r});
		}	
	}
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	freopen("segtree.in","r",stdin);
	freopen("segtree.out","w",stdout);
    cin>>n;
    rep(i,1,n) cin>>a[i];
    build(1,1,n);
    cin>>m;
    while(m--){
    	int op;cin>>op;
    	if(op==1){
    		int l,r,x;cin>>l>>r>>x;
    		upd(1,1,n,l,r,x);
		}else{
			while(!q.empty()) q.pop();
			int l,r,x,k;cin>>l>>r>>x>>k;
			solve(l,r,x,k); 
			if(cnt==-1) cout<<-1;
			else rep(i,1,cnt) cout<<ans[i]<<" ";
			cout<<'\n';
		}
	}
	return 0;
}
posted @ 2025-10-14 14:59  NeeDna  阅读(5)  评论(0)    收藏  举报