1. 程式人生 > 其它 >NOI online #1 提高組

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

*/