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\)
冷静分析
- \(a_i,b_i\)互不相同
- \(\forall_{1\leqslant i\leqslant l},deg(a_i)\equiv deg(b_i)\equiv 1(mod \ 2)\)
- 对于$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;
}

浙公网安备 33010602011771号