Codeforces Round #665 (Div. 2) 题解 (CDEF)

C. Mere Array

大意:给定一个序列,你可以交换两个数,要求这两个数的gcd等于整个序列的最小值。问是否可以让序列单调不下降

显然,想要交换两个数a,b,我们可以让a与最小元素交换,再让b与最小元素交换,再让a与最小元素交换。因此,一个数能否自由移动,就看它与最小元素的gcd是否等于最小元素。我们把可以自由移动的数排个序就好了

#include <bits/stdc++.h>
using namespace std;
#define repeat(i,a,b) for(int i=(a),_=(b);i<_;i++)
#define repeat_back(i,a,b) for(int i=(b)-1,_=(a);i>=_;i--)
int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
const int N=200010; typedef long long ll; const int inf=~0u>>2; const ll INF=~0ull>>2; ll read(){ll x; if(scanf("%lld",&x)==-1)exit(0); return x;} typedef double lf; const lf pi=acos(-1.0); lf readf(){lf x; if(scanf("%lf",&x)==-1)exit(0); return x;} typedef pair<ll,ll> pii; template<typename T> void operator<<(vector<T> &a,T b){a.push_back(b);}
const ll mod=(1?1000000007:998244353);
#define int ll
int a[N],b[N];
void Solve(){
	int n=read(),f=1;
	repeat(i,0,n)
		a[i]=b[i]=read();
	int m=*min_element(a,a+n);
	sort(b,b+n);
	repeat(i,0,n)
	if(b[i]!=a[i] && __gcd(a[i],m)!=m)
		f=false;
	cout<<(f?"YES":"NO")<<endl;
}
signed main(){
	//freopen("data.txt","r",stdin);
	int T=1; T=read();
	while(T--)Solve();
	return 0;
}

D. Maximum Distributed Tree

大意:给边赋值,使得所有不同的路径的边权之和之和最大。还要求边权之积等于k且边权里的1的数量最小

首先,如果边权数不足n-1,那就补1;如果边权数超过n-1,那就把最大的(边权数-n+2)个数删除,补上被删除的数乘积。这样边权数就是n-1了

一条边的贡献为边权乘以(整个树被这条边分割成的两个子树大小的乘积)。我们先算出所有边权、统计次数,放到两个数组里,对这两个数组分别排个序,依次匹配(排序不等式)

注意排序前不要取模!

#include <bits/stdc++.h>
using namespace std;
#define repeat(i,a,b) for(int i=(a),_=(b);i<_;i++)
#define repeat_back(i,a,b) for(int i=(b)-1,_=(a);i>=_;i--)
int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
const int N=200010; typedef long long ll; const int inf=~0u>>2; const ll INF=~0ull>>2; ll read(){ll x; if(scanf("%lld",&x)==-1)exit(0); return x;} typedef double lf; const lf pi=acos(-1.0); lf readf(){lf x; if(scanf("%lf",&x)==-1)exit(0); return x;} typedef pair<ll,ll> pii; template<typename T> void operator<<(vector<T> &a,T b){a.push_back(b);}
const ll mod=(1?1000000007:998244353);
#define int ll
vector<int> a[N],w,v;
int sz[N],n;
void dfs(int x,int fa=-1){
	sz[x]=1;
	for(auto p:a[x])if(p!=fa){
		dfs(p,x);
		v<<(sz[p]*(n-sz[p])); //"<<" means push_back
		sz[x]+=sz[p];
	}
}
void Solve(){
	n=read();
	repeat(i,1,n+1)a[i].clear(); w.clear(); v.clear();
	repeat(i,0,n-1){
		int x=read(),y=read();
		a[x]<<y; a[y]<<x; //"<<" means push_back
	}
	int m=read();
	repeat(i,0,m)w<<read(); //"<<" means push_back
	repeat(i,m,n-1)w<<1ll; //"<<" means push_back
	dfs(1);
	ll ans=0;
	sort(w.begin(),w.end());
	while((int)w.size()>n-1){
		int a=w.back(); w.pop_back();
		int b=w.back(); w.pop_back();
		w<<(a*b%mod); //"<<" means push_back
	}
	sort(v.begin(),v.end());
	repeat(i,0,n-1)ans+=v[i]%mod*w[i]%mod;
	ans%=mod;
	cout<<ans<<endl;
}
signed main(){
	//freopen("data.txt","r",stdin);
	int T=1; T=read();
	while(T--)Solve();
	return 0;
}

E. Divide Square

大意:边长为1000000的矩形内有一些竖线或横线,所有线都会碰到矩形边界。问这些线把矩形分成几部分

找了一下规律,发现答案为1+交点数+一下就可以把大矩形分成两块的线段数。主要难点是统计交点数

根据扫描线的操作,我们让一条水平的扫描线从下往上扫过整个大矩形。如果扫描线碰到竖线的下端点,我们让这个线段树的(竖线的横坐标)这个位置加1,碰到上端点,那就这个位置减1。如果扫描线碰到横线,那就统计线段树里的区间和

#include <bits/stdc++.h>
using namespace std;
#define repeat(i,a,b) for(int i=(a),_=(b);i<_;i++)
#define repeat_back(i,a,b) for(int i=(b)-1,_=(a);i>=_;i--)
int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
const int N=200010; typedef long long ll; const int inf=~0u>>2; const ll INF=~0ull>>2; ll read(){ll x; if(scanf("%lld",&x)==-1)exit(0); return x;} typedef double lf; const lf pi=acos(-1.0); lf readf(){lf x; if(scanf("%lf",&x)==-1)exit(0); return x;} typedef pair<ll,ll> pii; template<typename T> void operator<<(vector<T> &a,T b){a.push_back(b);}
const ll mod=(1?1000000007:998244353);
#define int ll
struct LR{
	int y,l,r;
}a[N];
struct event{
	int x,y,d;
};
struct seg{
	#define U(a,b) (a+b)
	ll a0=0;
	int n; ll a[1024*1024*2];
	void init(int inn){
		for(n=1;n<inn;n<<=1);
		repeat(i,0,n)a[n+i]=0;
		repeat_back(i,1,n)up(i);
	}
	void up(int x){
		a[x]=U(a[x<<1],a[(x<<1)^1]);
	}
	void update(int x,ll k){
		a[x+=n]+=k;
		while(x>>=1)up(x);
	}
	ll query(int l,int r){
		ll ans=a0;
		for(l+=n-1,r+=n+1;l^r^1;l>>=1,r>>=1){
			if(~l & 1)ans=U(ans,a[l^1]);
			if(r & 1)ans=U(ans,a[r^1]);
		}
		return ans;
	}
}tr;
vector<event> e;
void Solve(){
	int n=read(),m=read(); tr.init(1000010);
	ll ans=1;
	repeat(i,0,n){
		a[i].y=read();
		a[i].l=read();
		a[i].r=read();
	}
	sort(a,a+n,[](LR a,LR b){return a.y<b.y;});
	repeat(i,0,m){
		int x=read(),u=read(),d=read();
		int t=(u==0)+(d==1000000);
		e<<(event){x,u,1}; //"<<" means push_back
		e<<(event){x,d+1,-1}; //"<<" means push_back
		ans+=(t==2);
	}
	sort(e.begin(),e.end(),[](event a,event b){return a.y>b.y;});
	repeat(i,0,n){
		int t=(a[i].l==0)+(a[i].r==1000000);
		while(!e.empty() && e.back().y<=a[i].y){
			tr.update(e.back().x,e.back().d);
			e.pop_back();
		}
		ans+=tr.query(a[i].l,a[i].r);
		ans+=(t==2);
	}
	cout<<ans<<endl;
}
signed main(){
	//freopen("data.txt","r",stdin);
	int T=1; //T=read();
	while(T--)Solve();
	return 0;
}

F. Reverse and Swap

大意:维护一个数据结构,可以实现单点修改、区间求和、翻转、交换(具体看原题)

因为翻转、交换操作满足交换率和结合律,因此这题就是一个魔改线段树。对于翻转操作来说,相当于在某一层的所有节点,交换左右儿子,然后懒标记下放,让儿子们也交换一下左右儿子。对于交换操作来说,相当于交换某一层的所有节点的左右儿子,不用下放懒标记。但是某一层的节点数是O(n)显然不行。因此对于这两种操作,需要一个特殊的懒标记,表示这个节点往下走多少层后,需要执行翻转/交换操作

什么,你说你的线段树不能交换左右儿子?那就换成可持久化动态树套仙人掌吧

#include <bits/stdc++.h>
using namespace std;
#define repeat(i,a,b) for(int i=(a),_=(b);i<_;i++)
#define repeat_back(i,a,b) for(int i=(b)-1,_=(a);i>=_;i--)
int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
const int N=1000010; typedef long long ll; const int inf=~0u>>2; const ll INF=~0ull>>2; ll read(){ll x; if(scanf("%lld",&x)==-1)exit(0); return x;} typedef double lf; const lf pi=acos(-1.0); lf readf(){lf x; if(scanf("%lf",&x)==-1)exit(0); return x;} typedef pair<ll,ll> pii; template<typename T> void operator<<(vector<T> &a,T b){a.push_back(b);}
#define int ll
int in[N];
struct seg{
	#define U(x,y) (x+y)
	#define a0 0
	void toz(ll x1,ll x2){
		z1^=x1,z2^=x2;
	}
	ll a,z1,z2; seg *lc,*rc;
	void init(int,int);
	void up(){a=U(lc->a,rc->a);}
	void down(int l,int r){
		if(z1&1)swap(lc,rc),z1^=3;
		if(z2&1)swap(lc,rc);
		if(l<r){
			lc->toz(z1>>1,z2>>1);
			rc->toz(z1>>1,z2>>1);
		}
		z1=z2=0;
	}
	void update(int l,int r,ll k1,ll k2){
		z1^=k1;
		z2^=k2;
		down(l,r);
	}
	void change(int l,int r,ll x,ll y){
		if(l==x && r==x){
			down(l,r);
			a=y;
			return;
		}
		if(x<l || x>r){down(l,r); return;}
		int m=(l+r)/2; down(l,r);
		lc->change(l,m,x,y);
		rc->change(m+1,r,x,y);
		up();
	}
	ll query(int l,int r,int x,int y){
		x=max(x,l); y=min(y,r); if(x>y)return a0;
		down(l,r);
		if(x==l && y==r)return a;
		int m=(l+r)/2;
		return U(lc->query(l,m,x,y),rc->query(m+1,r,x,y));
	}
}tr[N*2],*pl;
void seg::init(int l,int r){
	if(l==r){a=in[l]; return;}
	int m=(l+r)/2;
	lc=++pl; lc->init(l,m);
	rc=++pl; rc->init(m+1,r);
	up();
}
void init(int l,int r){
	pl=tr; tr->init(l,r);
}
void Solve(){
	int nn=read(),q=read(); int n=1<<nn;
	repeat(i,0,n)in[i]=read();
	init(0,n-1);
	while(q--){
		int op=read();
		if(op==1){
			int x=read()-1,y=read();
			tr->change(0,n-1,x,y);
		}
		else if(op==2){
			int x=read(); x=nn-x;
			tr->update(0,n-1,1<<x,0);
		}
		else if(op==3){
			int x=read(); x=max(0ll,(nn-x)-1);
			tr->update(0,n-1,0,1<<x);
		}
		else{
			int x=read()-1,y=read()-1;
			cout<<tr->query(0,n-1,x,y)<<endl;
		}
	}
}
signed main(){
	//freopen("data.txt","r",stdin);
	int T=1; //T=read();
	while(T--)Solve();
	return 0;
}
原文地址:https://www.cnblogs.com/axiomofchoice/p/13544054.html