题解 洛谷P6185 6186 6187 NOI Online(全)

洛谷P6185 [NOI Online 提高组]序列

问题相当于有一个序列\(c_i=a_i-b_i\),要进行若干次操作使得\(c_i\)每一项都为\(0\)

有一档\(t=2\)的部分分,说明\(2\)肯定是比\(1\)好处理的。不妨先考虑只有\(2\)的情况。对于操作\(2\ u\ v\),我们在\(u,v\)之间连一条无向边。这样整张图形成了若干个连通块。不难发现在每个连通块内部,对于任意两个节点\(u,v\),我们都可以实现\(u\texttt{++},v\texttt{--}\)\(u\texttt{--},v\texttt{++}\)。也就是说在每个连通块内部,节点的权值可以相互转运。那么只有\(2\)操作时,有解当且仅当每个连通块内的权值和都是\(0\)

在既有\(1\)也有\(2\)时,先用同样的方法把\(n\)个节点缩成若干个连通块,每个连通块的权值就是其中所有节点的权值和。这样,每个\(1\)操作就转化为了连通块之间的操作(特别地,当\(1\)操作的两个节点在同一连通块内时,这次操作的效果相当于让这个连通块的权值\(\pm2\))。这样,把每个连通块看做一个点,则原问题转化为了对一个新序列,只有\(1\)操作的情况。

对于一次操作\(1\ u\ v\),我们在\(u,v\)之间连一条无向边。发现连通块之间相互独立,因此全局有解当且仅当每个连通块都有解。考虑一个连通块。因为一次\(1\)操作会造成这个连通块内的权值和\(\pm2\),因此有解的一个必要条件是连通块内所有节点的权值和为偶数

考虑两个节点之间,若存在一条路径(可以有重边/自环)长度为偶数,则这两个节点的权值可以实现相互转运。如果一个连通块内存在奇环,则任意两个节点的距离都可以是偶数,此时该连通块有解。否则这个连通块是一个二分图。我们对它黑白染色后,每条边一定连接两个不同颜色的节点。发现我们一次\(1\)操作可以让黑、白点的权值和同时\(+1\)或同时\(-1\),且同种颜色的节点之间距离一定是偶数。因此此时有解当且仅当黑、白点的权值和相等

时间复杂度\(O(n)\)

参考代码:

//problem:P6185
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

namespace Fread{
const int MAXN=1<<20;
char buf[MAXN],*S,*T;
inline char getchar(){
	if(S==T){
		T=(S=buf)+fread(buf,1,MAXN,stdin);
		if(S==T)return EOF;
	}
	return *S++;
}
}//namespace Fread
#ifdef ONLINE_JUDGE
	#define getchar Fread::getchar
#endif
inline int read(){
	int f=1,x=0;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
inline ll readll(){
	ll f=1,x=0;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
/*  ------  by:duyi  ------  */ // myt天下第一
const int MAXN=1e5;
int n,m,a[MAXN+5],fa[MAXN+5],cnt_e,cnt_id,id[MAXN+5],col[MAXN+5];
pii e[MAXN+5];
int get_fa(int x){return x==fa[x]?x:(fa[x]=get_fa(fa[x]));}
vector<int>G[MAXN+5];
ll val[MAXN+5],Sum,sum1,sum2;
bool flag;
void dfs(int u){
	if(col[u]==1)sum1+=val[u];else sum2+=val[u];
	Sum+=val[u];
	for(int i=0;i<(int)G[u].size();++i){
		int v=G[u][i];
		if(col[v]){
			if(col[v]!=3-col[u])flag=0;
		}else{
			col[v]=3-col[u];dfs(v);
		}
	}
}
int main() {
	int Testcases=read();while(Testcases--){
		n=read();m=read();
		for(int i=1;i<=n;++i)fa[i]=i,a[i]=read();
		for(int i=1;i<=n;++i)a[i]-=read();
		cnt_e=0;
		for(int i=1;i<=m;++i){
			int t=read(),u=read(),v=read();
			if(t==1){
				e[++cnt_e]=mk(u,v);
			}else{
				u=get_fa(u),v=get_fa(v);
				if(u!=v)fa[u]=v;
			}
		}
		cnt_id=0;
		for(int i=1;i<=n;++i)if(get_fa(i)==i)id[i]=++cnt_id;
		for(int i=1;i<=cnt_id;++i)vector<int>().swap(G[i]),col[i]=0,val[i]=0;
		for(int i=1;i<=n;++i)val[id[get_fa(i)]]+=a[i];
		for(int i=1;i<=cnt_e;++i){
			int u=id[get_fa(e[i].fst)],v=id[get_fa(e[i].scd)];
			G[u].pb(v);G[v].pb(u);
		}
		bool yes=1;
		for(int i=1;i<=cnt_id;++i)if(!col[i]){
			flag=1;Sum=sum1=sum2=0;
			col[i]=1;dfs(i);
			if(Sum%2!=0){
				yes=0;
				break;
			}
			if(flag&&sum1!=sum2){//是二分图
				yes=0;
				break;
			}
		}
		if(yes)puts("YES");else puts("NO");
	}
	return 0;
}

洛谷P6186 [NOI Online 提高组]冒泡排序

考虑一轮冒泡排序会对序列产生什么影响。设位置\(i\)前面值大于\(a_i\)的位置数量为\(s_i\)。则一轮冒泡排序后,首先所有\(s_i\)向左移一位,然后所有大于\(0\)\(s_i\)都减\(1\)。即:\(s_i=\max(s_{i+1}-1,0)\)

维护两个树状数组。下标均为\(s_i\)的值域(显然所有\(s_i<n\))。第一个树状数组存\(s_j=i\)\(j\)的个数,第二个树状数组存\(s_j=i\)\(s_j\)之和。交换操作相当于是在树状数组上做简单的单点修改。查询时,显然只有\(s_i>k\)的这些位置会影响答案(其他位置减\(k\)次之后取\(\max\)后就都变为\(0\)了)。用第二个树状数组求后缀和,减去第一个树状数组的后缀和乘以\(k\),就是答案了。

时间复杂度\(O(m\log n)\)

参考代码:

//problem:P6186
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

namespace Fread{
const int MAXN=1<<20;
char buf[MAXN],*S,*T;
inline char getchar(){
	if(S==T){
		T=(S=buf)+fread(buf,1,MAXN,stdin);
		if(S==T)return EOF;
	}
	return *S++;
}
}//namespace Fread
#ifdef ONLINE_JUDGE
	#define getchar Fread::getchar
#endif
inline int read(){
	int f=1,x=0;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
inline ll readll(){
	ll f=1,x=0;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
/*  ------  by:duyi  ------  */ // myt天下第一
const int MAXN=2e5;
int n,m,a[MAXN+5],s[MAXN+5];
struct FenwickTree{
	ll c[MAXN+5];
	void modify(int p,int v){
		if(!p)return;
		for(;p<=n;p+=(p&(-p)))c[p]+=v;
	}
	ll query(int p){
		ll res=0;
		for(;p;p-=(p&(-p)))res+=c[p];
		return res;
	}
	void clr(){
		memset(c,0,sizeof(c));
	}
	FenwickTree(){}
}T,T1;
int main() {
	n=read();m=read();
	for(int i=1;i<=n;++i)a[i]=read(),s[i]=T.query(n)-T.query(a[i]),T.modify(a[i],1);
	T.clr();
	for(int i=1;i<=n;++i)T.modify(s[i],1),T1.modify(s[i],s[i]);
	while(m--){
		int op=read(),x=read();
		if(op==1){
			T.modify(s[x],-1);T.modify(s[x+1],-1);
			T1.modify(s[x],-s[x]);T1.modify(s[x+1],-s[x+1]);
			int t2=s[x]+(a[x+1]>a[x]);
			int t1=s[x+1]-(a[x]>a[x+1]);
			s[x]=t1;s[x+1]=t2;
			swap(a[x],a[x+1]);
			T.modify(s[x],1);T.modify(s[x+1],1);
			T1.modify(s[x],s[x]);T1.modify(s[x+1],s[x+1]);
			//cout<<"* ";for(int i=1;i<=n;++i)cout<<s[i]<<" ";cout<<endl;
		}else{
			if(x>=n){printf("%d\n",0);continue;}
			ll res=T1.query(n)-T1.query(x)-(ll)x*(T.query(n)-T.query(x));
			printf("%lld\n",res);
		}
	}
	return 0;
}

洛谷P6187 [NOI Online 提高组]最小环

观察样例,猜测构造策略。

首先,距离为\(k\)的位置一定构成了若干个长度相等的环,每个环上相邻两个点距离都是\(k\),环与环之间不存在距离为\(k\)的点。因此每个环是独立的。容易发现,每个环的长度都是\(\frac{n}{\gcd(n,k)}\)

原则上我们要尽量把大的数放在一起,这样才能使乘积之和最大化。

因此如果环长为\(len\),则一定把前\(len\)大的数放在同一个环中,接下来\(len\)个数放下一个环中,以此类推......。

对于一个环,设它上面的数字分别为\(s_1,\dots,s_{len}\),则它对答案的贡献是\(\sum_{i=1}^{len}s_is_{i\bmod len+1}\)。我们任选一个位置放最大的数,然后在它两边分别放次大的和更次大的数。例如,\(len=5\)时的最优放法为:\((2,4,5,3,1)\)

因为可能的环长一定是\(n\)的约数,我们预处理每个\(len\)的答案。总时间复杂度\(O(n\sqrt{n})\)

参考代码:

//problem:P6187
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

namespace Fread{
const int MAXN=1<<20;
char buf[MAXN],*S,*T;
inline char getchar(){
	if(S==T){
		T=(S=buf)+fread(buf,1,MAXN,stdin);
		if(S==T)return EOF;
	}
	return *S++;
}
}//namespace Fread
#ifdef ONLINE_JUDGE
	#define getchar Fread::getchar
#endif
inline int read(){
	int f=1,x=0;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
inline ll readll(){
	ll f=1,x=0;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
/*  ------  by:duyi  ------  */ // myt天下第一
const int MAXN=2e5;
int n,m,a[MAXN+5],s[MAXN+5],len;
ll ans[MAXN+5];
inline int pre(int i){return i==1?len:i-1;}
inline int nxt(int i){return i==len?1:i+1;}
inline int gcd(int x,int y){return !y?x:gcd(y,x%y);}
int main() {
	n=read();m=read();
	for(int i=1;i<=n;++i)a[i]=read();
	sort(a+1,a+n+1);
	for(len=1;len<=n;++len)if(n%len==0){
		//环长:len
		//cout<<"len "<<len<<endl;
		int cur=n;
		while(cur){
			int l=1,r=1;s[1]=a[cur--];
			for(int i=2;i<=len;++i){
				if(i&1)l=pre(l),s[l]=a[cur--];
				else r=nxt(r),s[r]=a[cur--];
			}
			//for(int i=1;i<=len;++i)cout<<s[i]<<" ";cout<<endl;
			for(int i=1;i<=len;++i)ans[len]+=(ll)s[i]*s[nxt(i)];
		}
	}
	while(m--){
		int k=read();
		int len=n/gcd(n,k);
		printf("%lld\n",ans[len]);
	}
	return 0;
}
posted @ 2020-03-08 21:35  duyiblue  阅读(310)  评论(2编辑  收藏  举报