图上问题训练题解

训练链接

CF360E

题目链接

点击打开链接

题目解法

首先可以发现每条边一定是取 l l l r r r 左右,中间的值是没有用的
我们不妨假设一开始所有边全部取 l l l,然后自然地想到如果 d i s 0 , x > d i s 1 , x dis_{0,x}>dis_{1,x} dis0,x>dis1,x,,那么就把 x → y x\to y xy 的边权改为 r r r(其中 d i s 0 , x dis_{0,x} dis0,x 表示从 S 1 S1 S1 x x x 的最短距离, d i s 1 , x dis_{1,x} dis1,x 为从 S 2 S2 S2 x x x 的最短距离)
但这样其实是有问题,这里借用一下 ‘绝顶我为峰’ 的数据说明一下

Input2              Output2
4 4                 WIN
1 2 4               1 1 5 3
1 3 1 1
2 3 1 1
3 4 1 5
2 4 3 3

考虑这一组数据,如果条件为 d i s 0 , x > d i s 1 , x dis_{0,x}>dis_{1,x} dis0,x>dis1,x,那么是错的
为什么?因为从 S 2 S2 S2 开始的最短路径可能会经过 x → y x\to y xy 这条边,如果把 x → y x\to y xy 的边权改为 z z z 可以使 S 2 S2 S2 T T T 的最小距离更大

这样会让我们想到把 d i s 0 , x > d i s 1 , x dis_{0,x}>dis_{1,x} dis0,x>dis1,x 改成 d i s 0 , x ≥ d i s 1 , x dis_{0,x}\ge dis_{1,x} dis0,xdis1,x 是不是就可以了,事实上也不可以

Input2 :            Output2
4 4                 DRAW
1 2 4               1 1 1 3
1 3 1 1
2 3 1 1
3 4 1 5
1 4 3 3

考虑这一组数据,如果条件为 d i s 0 , x ≥ d i s 1 , x dis_{0,x}\ge dis_{1,x} dis0,xdis1,x,是错的
因为如果从 S 1 S1 S1 开始的最短路经过 x → y x\to y xy,那么这样会使 S 1 S1 S1 开始的最短路更劣,而从 S 2 S2 S2 开始的最短路却不受影响

那怎样才对?可以一开始令边权都为 r r r,然后如果 d i s 0 , x < d i s 1 , x dis_{0,x}<dis_{1,x} dis0,x<dis1,x 就把 x → y x\to y xy 的边权设为 l l l
不难发现,这样对于上述两种情况都能得到正确的答案
时间复杂度为 O ( m k l o g n ) O(mklogn) O(mklogn)

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=10100,K=110,inf=1e18;
typedef pair<int,int> pii;
int n,m,k;
int e[N],w[N],ne[N],h[N],idx;
struct Range{ int x,y,l,r;}bound[K];
int dis[2][N];
priority_queue<pii,vector<pii>,greater<pii> > que;
bool vis[N];
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
void dij(int S,int *d){
	for(int i=1;i<=n;i++) vis[i]=0,d[i]=inf;
	int hh=0,tt=-1;d[S]=0;
	que.push(make_pair(0,S));
	while(!que.empty()){
		int u=que.top().second;que.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(int i=h[u];~i;i=ne[i])
			if(d[u]+w[i]<d[e[i]]){
				d[e[i]]=d[u]+w[i];
				que.push(make_pair(d[e[i]],e[i]));
			}
	}
}
void add(int x,int y,int z){ e[idx]=y,w[idx]=z,ne[idx]=h[x],h[x]=idx++;}
signed main(){
	n=read(),m=read(),k=read();
	int S1=read(),S2=read(),T=read();
    memset(h,-1,sizeof(h));
	for(int i=1;i<=m;i++){
		int x=read(),y=read(),z=read();
		add(x,y,z);
	}
	for(int i=1;i<=k;i++){
		int x=read(),y=read(),l=read(),r=read();
		add(x,y,r),bound[i-1]={x,y,l,r};
	}
	dij(S1,dis[0]),dij(S2,dis[1]);
	while(true){
		bool flg=0;
		for(int i=0;i<k;i++){
			int x=bound[i].x;
			if(dis[0][x]<dis[1][x]&&w[i+m]!=bound[i].l) w[i+m]=bound[i].l,flg=1;
		}
		if(!flg) break;
		dij(S1,dis[0]),dij(S2,dis[1]);
	}
	if(dis[0][T]<dis[1][T]){
		puts("WIN");
		for(int i=0;i<k;i++) printf("%lld ",w[i+m]);
	}
	else if(dis[0][T]==dis[1][T]){
		puts("DRAW");
		for(int i=0;i<k;i++) printf("%lld ",w[i+m]);
	}
	else puts("LOSE");
	fprintf(stderr,"%d ms\n",int64_t(1e3*clock()/CLOCKS_PER_SEC));
	return 0;
}

CF280D

题目链接

点击打开链接

题目解法

考虑一个贪心的想法是每次选择 [ l , r ] [l,r] [l,r] 中的最大和子段,然后加上它的权值和,且把这一段的点权全部取反
这样显然是对的,现在只要考虑一个问题:是否每次选择都会增加一个新的子段?这是对的,假设有 [ l , r ] [l,r] [l,r] 先于 [ l , r ′ ] [l,r'] [l,r] 选择,那么一开始一定会直接选择 [ r + 1 , r ′ ] [r+1,r'] [r+1,r] 才会更优
因为 k k k 很小,所以直接用线段树模拟这个过程即可,如果选出的子段和 < 0 <0 <0 就退出
时间复杂度 O ( k n l o g n ) O(knlogn) O(knlogn),有亿点难写

#include <bits/stdc++.h>
using namespace std;
const int N=100100;
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
struct Node{
	int l,r,sum;
	int lmx,rmx,mx,Lp,Rp,LLp,RRp;
	int lmn,rmn,mn,Lq,Rq,LLq,RRq;
	bool tag;
}seg[N<<2];
Node operator +(const Node &lc,const Node rc){
	Node ret;ret.l=lc.l,ret.r=rc.r,ret.tag=0;
	ret.sum=lc.sum+rc.sum;
	ret.lmx=lc.lmx,ret.LLp=lc.LLp;
	if(lc.sum+rc.lmx>ret.lmx) ret.lmx=lc.sum+rc.lmx,ret.LLp=rc.LLp;
	ret.rmx=rc.rmx,ret.RRp=rc.RRp;
	if(rc.sum+lc.rmx>ret.rmx) ret.rmx=rc.sum+lc.rmx,ret.RRp=lc.RRp;
	ret.mx=lc.rmx+rc.lmx,ret.Lp=lc.RRp,ret.Rp=rc.LLp;
	if(lc.mx>ret.mx) ret.mx=lc.mx,ret.Lp=lc.Lp,ret.Rp=lc.Rp;
	if(rc.mx>ret.mx) ret.mx=rc.mx,ret.Lp=rc.Lp,ret.Rp=rc.Rp;
	ret.lmn=lc.lmn,ret.LLq=lc.LLq;
	if(lc.sum+rc.lmn<ret.lmn) ret.lmn=lc.sum+rc.lmn,ret.LLq=rc.LLq;
	ret.rmn=rc.rmn,ret.RRq=rc.RRq;
	if(rc.sum+lc.rmn<ret.rmn) ret.rmn=rc.sum+lc.rmn,ret.RRq=lc.RRq;
	ret.mn=lc.rmn+rc.lmn,ret.Lq=lc.RRq,ret.Rq=rc.LLq;
	if(lc.mn<ret.mn) ret.mn=lc.mn,ret.Lq=lc.Lq,ret.Rq=lc.Rq;
	if(rc.mn<ret.mn) ret.mn=rc.mn,ret.Lq=rc.Lq,ret.Rq=rc.Rq;
	return ret;
}
void down(int x){
	swap(seg[x].lmx,seg[x].lmn),swap(seg[x].rmx,seg[x].rmn),swap(seg[x].mx,seg[x].mn);
	swap(seg[x].Lp,seg[x].Lq),swap(seg[x].Rp,seg[x].Rq),swap(seg[x].LLp,seg[x].LLq),swap(seg[x].RRp,seg[x].RRq);
	seg[x].sum*=-1,seg[x].lmx*=-1,seg[x].rmx*=-1,seg[x].mx*=-1,seg[x].lmn*=-1,seg[x].rmn*=-1,seg[x].mn*=-1;
	seg[x].tag^=1;
}
void pushdown(int x){
	if(seg[x].tag) down(x<<1),down(x<<1^1);
	seg[x].tag=0;
}
void update(int l,int r,int x,int pos,int val){
	if(l==r){ seg[x]={l,l,val,val,val,val,l,l,l,l,val,val,val,l,l,l,l,0};return;}
	pushdown(x);
	int mid=(l+r)>>1;
	if(mid>=pos) update(l,mid,x<<1,pos,val);
	else update(mid+1,r,x<<1^1,pos,val);
	seg[x]=seg[x<<1]+seg[x<<1^1];
}
Node query(int l,int r,int x,int L,int R){
	if(L<=l&&r<=R) return seg[x];
	pushdown(x);
	int mid=(l+r)>>1;
	if(mid>=L&&mid<R) return query(l,mid,x<<1,L,R)+query(mid+1,r,x<<1^1,L,R);
	if(mid>=L) return query(l,mid,x<<1,L,R);
	return query(mid+1,r,x<<1^1,L,R);
}
void revers(int l,int r,int x,int L,int R){
	if(L<=l&&r<=R){ down(x);return;}
	pushdown(x);
	int mid=(l+r)>>1;
	if(mid>=L) revers(l,mid,x<<1,L,R);
	if(mid<R) revers(mid+1,r,x<<1^1,L,R);
	seg[x]=seg[x<<1]+seg[x<<1^1];
}
int main(){
	int n=read();
	for(int i=1,x;i<=n;i++) x=read(),update(1,n,1,i,x);
    int m=read();
	for(int i=1;i<=m;i++){
		int op=read();
		if(op){
			int l=read(),r=read(),k=read();
			int ans=0;
			vector<pair<int,int> > upd;upd.clear();
			while(k--){
				Node ret=query(1,n,1,l,r);
				if(ret.mx<0) break;
				ans+=ret.mx;
				revers(1,n,1,ret.Lp,ret.Rp);
				upd.push_back(make_pair(ret.Lp,ret.Rp));
			}
			for(int i=0;i<upd.size();i++) revers(1,n,1,upd[i].first,upd[i].second);
            printf("%d\n",ans);
		}
		else{
			int x=read(),y=read();
			update(1,n,1,x,y);
		}
	}
	fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
	return 0;
}

CF1062F

题目链接

点击打开链接

题目解法

考虑这道题的等价问题即为:判断一个点正图上能到的点与反图上能到的点的数目之和为 n n n n − 1 n-1 n1
这个问题的强化问题是 D A G DAG DAG 可达性统计,最优复杂度是 O ( n 2 ω ) O(\frac{n^2}{\omega}) O(ωn2),显然是无法通过这道题的
考虑用到这一题的特殊要求:只要知道是否能到达所有点或 n − 1 n-1 n1 个点
考虑拓扑排序,考虑把 u u u 弹出后的操作
如果当前的队列中剩下 0 0 0 个元素,那么 u u u 一定可以到达后面的所有点,这个用反证法不难证出
如果当前的队列中剩下 > 1 >1 >1 个元素,那么 u u u 一定不会是答案
如果当前的队列中剩下 1 1 1 个元素,如果令另外一个点为 v v v,那么即需要判断是否 v v v 能到达的点 u u u 都能到达
这个其实只要看 v v v 的出点是否当前入度为 1 1 1 即可,这个可以这样理解:如果 i n d e g = 1 indeg=1 indeg=1 那么 u u u 肯定不会是答案,如果 i n d e g > 1 indeg>1 indeg>1,因为当前队列中只有 2 2 2 个点,那么 u u u 一定可以到达那个点
正图和反图上分别跑一下即可,时间复杂度 O ( n ) O(n) O(n)

#include <bits/stdc++.h>
using namespace std;
const int N=300100;
int n,m,tot,deg[N],nums[N];
vector<int> G[N],H[N];
queue<int> que;
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
void work(int u,int v){
    bool flg=1;
	for(int w:G[v]) if(deg[w]==1){ flg=0;break;}
	if(flg) nums[u]+=n-tot;
}
void solve(){
    tot=0;
	memset(deg,0,sizeof(deg));
    for(int i=1;i<=n;i++) for(int j:G[i]) deg[j]++;
	for(int i=1;i<=n;i++) if(!deg[i]) que.push(i),tot++;
	while(!que.empty()){
		int u=que.front();que.pop();
		if(que.size()==1) work(u,que.front());
		if(!que.size()) nums[u]+=n-tot;
		for(int v:G[u]) if(!(--deg[v])) que.push(v),tot++;
	}
}
int main(){
	n=read(),m=read();
	for(int i=1;i<=m;i++){
		int x=read(),y=read();
		G[x].push_back(y);
	}
    solve();
    for(int i=1;i<=n;i++) for(int j:G[i]) H[j].push_back(i);
    for(int i=1;i<=n;i++) swap(G[i],H[i]);
    solve();
	int ans=0;
	for(int i=1;i<=n;i++) if(nums[i]>=n-2) ans++;
	printf("%d\n",ans);
	fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
	return 0;
}

CF235D

题目链接

点击打开链接

题目解法

根据期望的线性性,我们计算以 u u u 为分治中心的连通块期望大小
在用期望的线性性 ,我们只需要计算以 u u u 为分治中心和 v v v 连接的期望之和,因为赋权为 1 1 1,所以只要计算概率即可
先考虑一棵树的情况,显然以 x x x 为分治中心和 y y y 连通的概率为 1 l \frac{1}{l} l1,其中 l l l u → v u\to v uv 路径上点的个数(因为 x x x 为这条路径上第一个被选中的点)
然后考虑基环树的情况,即计算两个不在同一棵树内的点的概率,这里需要考虑从环有两种走法, 令第一种走法的长度为 l 1 l1 l1,第二种走法的长度为 l 2 l2 l2,概率为 1 l 1 + 1 l 2 \frac{1}{l1}+\frac{1}{l2} l11+l21,因为这样会算重(即环上所有点都选的情况),容斥即可,减去 1 l 3 \frac{1}{l3} l31,其中 l 3 l3 l3 为环的大小 +    x , y +\;x,y +x,y 分别到环的距离
时间复杂度 O ( n 2 l o g n ) O(n^2logn) O(n2logn),因为我写了倍增求 l c a lca lca

#include <bits/stdc++.h>
using namespace std;
const int N=3100;
int n;
int e[N<<1],ne[N<<1],h[N],idx;
int stk[N],top,oncir[N];
bool vis[N],flg;
int cir[N],cnt;
int depth[N],up[N][12];
vector<int> nodes[N];
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
bool find_cir(int u,int from){
	stk[++top]=u,vis[u]=1;
	for(int i=h[u];~i;i=ne[i]){
		int v=e[i];
		if(v==from) continue;
        if(flg) return true;
		if(vis[v]){
			while(top&&stk[top]!=v) oncir[stk[top]]=1,cir[++cnt]=stk[top--];
            oncir[v]=1,cir[++cnt]=v;
            flg=1;
			return true;
		}
		if(find_cir(v,u)) return true;
	}
	vis[u]=0,top--;
	return false;
}
void get_nodes(int rt,int u,int fa){
	nodes[rt].push_back(u),depth[u]=depth[fa]+1,up[u][0]=fa;
	for(int i=h[u];~i;i=ne[i]) if(e[i]!=fa&&!oncir[e[i]]) get_nodes(rt,e[i],u);
}
int get_lca(int x,int y){
	if(depth[x]>depth[y]) swap(x,y);
	for(int i=11;i>=0;i--) if(depth[up[y][i]]>=depth[x]) y=up[y][i];
	if(x==y) return x;
	for(int i=11;i>=0;i--) if(up[x][i]!=up[y][i]) x=up[x][i],y=up[y][i];
	return up[x][0];
}
void add(int x,int y){ e[idx]=y,ne[idx]=h[x],h[x]=idx++;}
int main(){
	n=read();
    memset(h,-1,sizeof(h));
	for(int i=1;i<=n;i++){
		int x=read(),y=read();x++,y++;
		add(x,y),add(y,x);
	}
	//找环
	for(int i=1;i<=n;i++) if(!vis[i]) find_cir(i,-1);
	double ans=0;
	//solve 树内部
	for(int i=1;i<=cnt;i++){
        int rt=cir[i];
		get_nodes(rt,rt,0);
		for(int j=1;j<=11;j++) for(int x:nodes[rt]) up[x][j]=up[up[x][j-1]][j-1];
		for(int x:nodes[rt]) for(int y:nodes[rt]){
            if(x==y) continue;
			int lca=get_lca(x,y);
			ans+=1.0/(depth[x]+depth[y]-2*depth[lca]+1);	
		u}
	}
	//solve 两棵树之间
	for(int i=1;i<=cnt;i++) for(int j=1;j<=cnt;j++){
        if(i==j) continue;
		for(int x:nodes[cir[i]]) for(int y:nodes[cir[j]]){
			int lenth1=max(j,i)-min(i,j)+depth[x]+depth[y]-1;
			int lenth2=cnt-max(i,j)+min(i,j)+depth[x]+depth[y]-1;
			ans+=1.0/lenth1+1.0/lenth2-1.0/(cnt+depth[x]+depth[y]-2);
		}
    }
	ans+=n;
	printf("%.6lf\n",ans);
	fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
	return 0;
}

CF1240F

题目链接

点击打开链接

题目解法

考虑把 u → v u\to v uv 的边拆分二分图,即变成 u → v + n u\to v+n uv+n 的边
考虑需要把 u u u u + n u+n u+n 的比赛全部合起来之后极差不超过 2 2 2,所以我们的目标是对于每个点 i ∈ [ 1 , 2 n ] i\in[1,2n] i[1,2n],需要满足它连出去的边极差 ≤ 1 \le 1 1
考虑把它练出去的边每 k k k 个分成 1 1 1 组,不满 k k k 个的分成一组,这样我们可以把点 i i i 拆成 ⌈ d e g i k ⌉ \lceil\frac{deg_i}{k}\rceil kdegi 个点,需要满足每个点连出去的边颜色都不同

一条一条边考虑,设当前边为 ( u , v ) ,    ( u ∈ [ 1 , n ] , v ∈ [ n + 1 , 2 n ] ) (u,v),\;(u\in[1,n],v\in[n+1,2n]) (u,v),(u[1,n],v[n+1,2n])

  1. 如果 u , v u,v u,v 有颜色 c o l col col 都未用过,那么直接把这条边染成 c o l col col
  2. 否则,我们任意找到 u u u 未用过的颜色 c o l 1 col1 col1 v v v 未用过的颜色 c o l 2 col2 col2
    然后考虑将这条边染成 c o l 1 col1 col1,然后再调整
    调整的过程类似以二分图找增广路
    这里直接上图:
    在这里插入图片描述考虑图上的过程,然后模拟即可
    最终一定可以找到一个点需要修改的边未被染色
    时间复杂度 O ( n 2 m ) O(n^2m) O(n2m)
#include <bits/stdc++.h>
using namespace std;
const int N=2300;
int n,m,k,deg[N],ans[N];
int id[N],ext;
int ne[N][N];//拆分后第i个点对应的颜色j指向哪里
int e[N][N];//拆分后第i个点对应的颜色j对应的是哪一条边
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
void dfs(int x,int col1,int col2){
	swap(ne[x][col1],ne[x][col2]);
	swap(e[x][col1],e[x][col2]);
    ans[e[x][col2]]=col2;
	if(ne[x][col2]) dfs(ne[x][col2],col2,col1);
}
int main(){
	n=read(),m=read(),k=read();
	for(int i=1;i<=n;i++) int x=read();
	for(int i=1;i<=m;i++){
		int x=read(),y=read()+n;
		if(deg[x]%k==0) id[x]=++ext;
		if(deg[y]%k==0) id[y]=++ext;
		deg[x]++,deg[y]++;
        x=id[x],y=id[y];
		int col=0;
		for(int j=1;j<=k;j++) if(!ne[x][j]&&!ne[y][j]){ col=j;break;}
		if(!col){
			int col1=0,col2=0;
			for(int j=1;j<=k;j++) if(!ne[x][j]){ col1=j;break;}
			for(int j=1;j<=k;j++) if(!ne[y][j]){ col2=j;break;}
			dfs(y,col1,col2),col=col1;
		}
		ne[x][col]=y,ne[y][col]=x;
		e[x][col]=e[y][col]=i;
		ans[i]=col;
	}
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
	fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
	return 0;
}

CF1148G

题目链接

点击打开链接

题目解法

好题!
考虑补集转化,把所有 g c d = 1 gcd=1 gcd=1 的边都连上,那么现在的问题是找到一个集合满足

  1. 每个元素没有连边 或
  2. 每个元素的连通块大小 ≥ 2 \ge 2 2

考虑一个一个加元素,如果这个元素与当前集合内所有的数都互质,那么就加入
这样可以找到一个包含所有 a a a 数组中因子的数的集合,但不一定是元素最多的互质集合
如果当前集合内元素数量 > k >k >k,就结束了

我们现在需要找到不在当前互质集合中的元素与互质集合中第一个元素不互质的位置,然后把它归到那一类集合中去
这个过程可以用整体二分完成
然后输出即可(要注意每个互质集合中的元素对应的集合输出的个数需要满足 = 0 =0 =0 ≥ 2 \ge 2 2
因为 2 k ≥ n 2k\ge n 2kn,所以易证得有一组合法的解

#include <bits/stdc++.h>
using namespace std;
const int N=100100,MAXV=10000100;
int n,k,a[N],id[N];
int v[MAXV],pr[MAXV],tot,mu[MAXV],cnt[MAXV];
int v1[N],v2[N],tmp[N];
vector<int> inc[N],fact[N];
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
void sieve(int n){
	mu[1]=1;
	for(int i=2;i<=n;i++){
		if(!v[i]) v[i]=i,pr[++tot]=i,mu[i]=-1;
		for(int j=1;j<=tot&&pr[j]<=n/i;j++){
			v[pr[j]*i]=pr[j];
			if(v[i]==pr[j]) break;
			mu[pr[j]*i]=-mu[i];
		}
	}
}
void add(int i,int c,int dep,int val){
    if(dep>=fact[i].size()){ cnt[c]+=val;return;}
    add(i,c,dep+1,val),add(i,c*fact[i][dep],dep+1,val);
}
int ask(int i,int c,int dep){
    if(dep>=fact[i].size()) return mu[c]*cnt[c];
    return ask(i,c,dep+1)+ask(i,c*fact[i][dep],dep+1);
}
void solve(int l,int r,int ql,int qr){
	if(ql>qr) return;
	if(l==r){
		for(int i=ql;i<=qr;i++) inc[l].push_back(v2[i]);
		return;
	}
	int mid=(l+r)>>1;
	for(int i=l;i<=mid;i++) add(v1[i],1,0,1);
	int ll=ql,rr=qr;
	for(int i=ql;i<=qr;i++)
		if(!ask(v2[i],1,0)) tmp[rr--]=v2[i];
		else tmp[ll++]=v2[i];
	for(int i=l;i<=mid;i++) add(v1[i],1,0,-1);
	for(int i=ql;i<=qr;i++) v2[i]=tmp[i];
	solve(l,mid,ql,ll-1),solve(mid+1,r,ll,qr);
}
int main(){
	sieve(1e7);
	n=read(),k=read();
	for(int i=1;i<=n;i++) a[i]=read();
	int sz1=0,sz2=0;
	for(int i=1;i<=n;i++){
        int x=a[i];
        while(x>1){
            int t=v[x];fact[i].push_back(t);
            while(x%t==0) x/=t;
        }
		if(!ask(i,1,0)) add(i,1,0,1),v1[++sz1]=i;
		else v2[++sz2]=i;
    }
	memset(cnt,0,sizeof(cnt));
	if(sz1>=k){
		for(int i=1;i<=k;i++) printf("%d ",v1[i]);
		exit(0);
	}
	solve(1,sz1,1,sz2);
    for(int i=1;i<=sz1;i++) id[i]=i;
    sort(id+1,id+sz1+1,[](const int &x,const int &y){ return inc[x].size()>inc[y].size();});
    int res=0;
    for(int i=1;i<=sz1;i++){
        res+=inc[id[i]].size()+1;
        if(res>=k){
            vector<int> ans;
            for(int j=1;j<=i;j++) ans.push_back(v1[id[j]]),ans.push_back(inc[id[j]].back()),inc[id[j]].pop_back();
            for(int j=1;j<=i;j++)
                while(ans.size()<k&&!inc[id[j]].empty())
                    ans.push_back(inc[id[j]].back()),inc[id[j]].pop_back();
            for(int j:ans) printf("%d ",j);
            break;
        }
    }
	fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值