图上问题训练题解

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}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 
#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 
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 
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 
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);	
		}
	}
	//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;
}

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 
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;
}

你可能感兴趣的:(其他,算法)