NOI online #1 提高组

序列

题意:

给定数组\(A,B\),有两种操作:

选择两个位置\(i,j\),使得\(a_i,a_j\)都加一或减一

选择两个位置\(i,j\)使得\(a_i\)加一,\(a_j\)减一,或者反过来。

给出的操作可以做任意次,问\(A\)数组能否变成\(B\)数组?

\(n,m\leq 10^5\)

题解:

考虑操作二,因为可以在里面任意分配权值,可以缩点。

然后把操作一对应的点连边,对于任意连通块:

如果是二分图,那么左部点和右部点的权值和之差不变,必须为\(0\)

如果不是二分图,那么连通块内\(a_i-b_i\)点数和必须是偶数。

#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define mid ((l+r)>>1)
#define lowbit(i) ((i)&(-i))
	const int N=1e5+10;
	int n,m;
	int a[N],b[N],s[N],c[5];
	int f[N],col[N];
	vector<int> eg[N];
	struct node
	{
		int x,y;
	}q[N];
	int num;
	inline int find(int k){return f[k]==k?k:f[k]=find(f[k]);}
	inline void init()
	{
		num=0;
		for(int i=1;i<=n;++i)
		{
			eg[i].clear();
			f[i]=i;
			s[i]=col[i]=0;
		}
	}
	inline bool dfs(int now,int cc)
	{
		col[now]=cc;
		c[cc]+=s[now];
		bool flag=1;
		for(int t:eg[now])
		{
			if(col[t]==col[now]) flag=0;
			if(!col[t]&&!dfs(t,3-cc)) flag=0;
		}
		return flag;
	}
	inline void main()
	{
		int skx;cin>>skx;
		while(skx--)
		{
			cin>>n>>m;
			init();
			for(int i=1;i<=n;++i) cin>>a[i];
			for(int i=1;i<=n;++i) cin>>b[i];
			for(int i=1;i<=m;++i)
			{
				int opt,x,y;
				cin>>opt>>x>>y;
				if(opt==1) q[++num].x=x,q[num].y=y;
				else f[find(x)]=find(y);
			}
			for(int i=1;i<=n;++i)
			{
				s[find(i)]+=a[i]-b[i];
			}
			for(int i=1;i<=num;++i)
			{
				int x=find(q[i].x),y=find(q[i].y);
				eg[x].emplace_back(y);
				eg[y].emplace_back(x);
			}
			bool flag=1;
			for(int i=1;i<=n;++i) if(!col[i]&&find(i)==i)
			{
				c[1]=c[2]=0;
				bool ok=dfs(i,1);
				if(ok&&c[1]!=c[2]) {flag=0;break;}
				if(!ok&&(c[1]^c[2])&1) {flag=0;break;}
			}
			if(flag) cout<<"YES\n";
			else cout<<"NO\n";
		}
	}
}
signed main()
{
	red::main();
	return 0;
}
/*

*/

冒泡排序

题意:

给定一个排列\(P\)

有两个操作:

\(p[x],p[x+1]\)交换位置。

问这个排列做\(k\)次冒泡排序后的逆序对数。

\(n,m\leq 10^5\)

题解:

假设一个序列的逆序对数是:

\[b_1,b_2,b_3,…b_n \]

那么做一个冒泡排序后的逆序对数就是:

\[b_2-1,b_3-1,…,b_n-1,0 \]

当然,和\(0\)\(max\)

有了这个性质之后怎么做呢,其实不用真的去移位,因为第一位的逆序对数肯定\(<=1\),等于是把第一位剪掉就行了。

\[b_1-1,b_2-1,b_3-1,…,b_n-1 \]

\(k\)次之后就是

\[b_1-k,b_2-k,b_3-k,…,b_n-k \]

所有项和\(0\)\(max\),然后求和,显然可以树状数组。

#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define mid ((l+r)>>1)
#define lowbit(i) ((i)&(-i))
	const int N=3e5+10;
	int n,m;
	int a[N],p[N];
	struct BIT
	{
		int tr[N];
		inline void update(int x,int k)
		{
			for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=k;
		}
		inline int query(int x)
		{
			int sum=0;
			for(int i=x;i>=1;i-=lowbit(i)) sum+=tr[i];
			return sum;
		}
	}T[2];
	inline void main()
	{
		cin>>n>>m;
		for(int i=1;i<=n;++i)
		{
			cin>>p[i];
			T[0].update(p[i],1);
			a[i]=i-T[0].query(p[i]);
		}
		for(int i=1;i<=n;++i) T[0].tr[i]=0;
		for(int i=1;i<=n;++i) T[0].update(n-a[i],1),T[1].update(n-a[i],a[i]);
		for(int i=1;i<=m;++i)
		{
			int opt,x;
			cin>>opt>>x;
			if(opt==1)
			{
				T[0].update(n-a[x],-1);
				T[1].update(n-a[x],-a[x]);
				T[0].update(n-a[x+1],-1);
				T[1].update(n-a[x+1],-a[x+1]);
				if(p[x]>p[x+1]) --a[x+1];
				swap(p[x],p[x+1]);
				swap(a[x],a[x+1]);
				if(p[x]>p[x+1]) ++a[x+1];
				T[0].update(n-a[x],1);
				T[1].update(n-a[x],a[x]);
				T[0].update(n-a[x+1],1);
				T[1].update(n-a[x+1],a[x+1]);
			}
			else
			{
				int sum=T[0].query(n-x);
				int val=T[1].query(n-x);
				cout<<val-x*sum<<'\n';
			}
		}
	}
}
signed main()
{
	red::main();
	return 0;
}
/*

*/

最小环

题意:

\(n\)个数字,每次给一个数字\(k\),要求把\(n\)个数字排成一个环,让环上所有距离为\(k\)的两个数字的乘积的和最大。

\(n,m\leq 2*10^5,k\leq \lfloor \frac{n}{2}\rfloor\)

题解:

每个数字\(k\)会把整个环分成几个不相交的小环,为了让积之和最大,我们尽量让大的乘大的,小的乘小的,所以最大的数字都应该分给同一个环,然后类推。

在一个环内怎么排列呢?我们先把最大的数字放进去,然后去考虑第二大的,让当前数字尽量挨着大的数字放:

\[0,0,6,0,0,0\\ 0,0,6,5,0,0\\ 0,4,6,5,0,0\\ 0,4,6,5,3,0\\ 2,4,6,5,3,0\\ 2,4,6,5,3,1 \]

大概是以上模式插入,其中首尾是相接的。

数字\(k\)是怎么把长度为\(n\)的环分割的呢?其实就是分成个环,每个长度是\(\frac{n}{gcd(n,k)}\)

所以不同的情况其实只有\(d(n)\)种,其中\(d(n)\)\(n\)的约数个数。

所以时间复杂度\(O(nd(n))\sim O(n\sqrt{n})\)

还可以更优,注意对于只有一个环的情况,其他情况和它的区别只在两个环的交界处,对于\(g\)个环,只要\(O(\frac{n}{g})\)的时间算一下交界。

总复杂度\(\sum_{g|n}\frac{n}{g}\leq nH_n\)\(H_n\)是调和级数。约等于\(O(nlogn)\)

#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define mid ((l+r)>>1)
#define lowbit(i) ((i)&(-i))
	const int N=3e5+10;
	int n,m;
	int a[N];
	int st[2][N],top[2];
	int ret[N];
	inline void main()
	{
		ios::sync_with_stdio(0);
		cin.tie(0),cout.tie(0);
		cin>>n>>m;
		int sum=0;
		for(int i=1;i<=n;++i)
		{
			cin>>a[i];
			sum+=a[i]*a[i];
		}
		sort(a+1,a+n+1);
		for(int i=1;i<=m;++i)
		{
			int k;cin>>k;
			if(!k)
			{
				cout<<sum<<'\n';
				continue;
			}
			int t=__gcd(n,k);
			int d=n/t;
			if(ret[d])
			{
				cout<<ret[d]<<'\n';
				continue;
			}
			for(int j=0;j<t;++j)
			{
				int l=j*d+1,r=(j+1)*d;
				top[0]=top[1]=0;
				st[0][++top[0]]=a[r];
				st[1][++top[1]]=a[r--];
				while(r>l)
				{
					int b=0;
					if(st[0][top[0]]<st[1][top[1]]) b=1;
					ret[d]+=a[r]*st[b][top[b]];
					st[b][++top[b]]=a[r--];
				}
				ret[d]+=a[l]*(st[0][top[0]]+st[1][top[1]]);
			}
			cout<<ret[d]<<'\n';
		}
	}
}
signed main()
{
	red::main();
	return 0;
}
/*

*/
posted @ 2022-05-11 23:03  lovelyred  阅读(20)  评论(0编辑  收藏  举报