NOI Online 提高组 题解

来补坑了……
个人认为三道题难度差不多……
还有要说一嘴,为啥我在其他网站代码都好好的,复制到 cnblogs 上 Tab 就成 8 空格了?不过也懒得改了。


T1 序列

首先,遇到这种加一减一还带附加条件的基本都是图论题,所以我们用图论的思维去想这道题。将每个 (a_i) 看成一个点,并把每个点赋一个新的权值 (b_i-a_i),这样最终就是问是否可以把每个点权变为 (0)
先考虑操作二,对每个操作二的点连无向边建图,同一连通块的点可以互相在总和不变的情况下改变为任意值(因为操作二一个加 (1) 一个减 (1) 是具有传递性的),于是我们可以把连通块用并查集缩点,当成一个权值不变的点看待。
再考虑操作一,在之前缩点的图上对每个操作一连无向边,建好的图必是二分图或非二分图的情况之一。对于二分图,我们可以在左部点与右部点总和之差不变的前提下修改点权,那要保证答案为YES则必然要两边相等;对于非二分图,我们仍从二分图的角度去考虑,相当于左部点或右部点内部出现了连边,那么此时其便无法增加或减少奇数值,只能增减偶数值,进而整幅图的总和都无法增减奇数值。那么要保证答案为YES只能是总和为偶数。
单次复杂度 (O(n))

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=1e5+7;
int n,m,cnt,a[N],b[N],p[N],q[N],fa[N];
bool vis[N];
ll s[N],c[3];
vector<int> G[N];

int find(int x) {return fa[x]==x?x:fa[x]=find(fa[x]);}

bool dfs(int u,int col)
{
	vis[u]=col,c[col]+=s[u];
	bool fl=1;
	for(int i=0,v;i<G[u].size();++i)
	{
		if(c[u]==c[v=G[u][i]]) fl=0;
		if(!vis[v]&&!dfs(v,3-col)) fl=0;
	}
	return fl;
}

int main()
{
	int T; scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m); cnt=0;
		for(int i=1;i<=n;++i) fa[i]=i,G[i].clear(),scanf("%d",a+i);
		for(int i=1;i<=n;++i) s[i]=vis[i]=0,scanf("%d",b+i);
		for(int i=1,op,x,y;i<=m;++i)
		{
			scanf("%d%d%d",&op,&x,&y);
			if(op==2) fa[find(x)]=find(y);
			else p[++cnt]=x,q[cnt]=y;
		}
		for(int i=1;i<=n;++i) s[find(i)]+=b[i]-a[i];
		for(int i=1;i<=cnt;++i)
		{
			int u=find(p[i]),v=find(q[i]);
			G[u].push_back(v);
			G[v].push_back(u);
		}
		bool ok=1;
		for(int i=1;i<=n;++i)
			if(find(i)==i&&!vis[i])
			{
				c[1]=c[2]=0;
				bool fl=dfs(i,1);
				if(fl&&c[1]!=c[2]) {ok=0; break;}
				if(!fl&&((c[1]+c[2])&1)) {ok=0; break;}
			}
		puts(ok?"YES":"NO");
	}
	return 0;
}

T2 冒泡排序

首先来盘一盘冒泡排序的本质,手玩了几次冒泡排序之后可以很容易发现:一轮冒泡排序就是把每个数的逆序对数减一(如果为零则不用减)。那么我们这道题实际上就是给你一个序列要支持以下操作:单点增减(交换操作实际上只影响了一个点的逆序对个数),全局增减(如果是 (0) 则忽略),以及求全局和。忽略 (0) 这一个好像不太好搞,但是仍可以用线段树做,比较麻烦,这里不讲。
止步于此,没想出来,遂去无耻地看了题解。其实我们可以用时间为下标维护逆序对个数。具体来说,建立一个树状数组,在 1 号点插入初始序列的逆序对总数,然后再往后的第 (i+1) 号点代表了“第 (i) 轮冒泡排序后序列的逆序对总数”,这个可以用桶差分 (O(nlog n)) 预处理,我们记cnt[i]为“逆序对个数等于 (i) 的数的个数”(比较拗口,别咬到舌头了),那么每一轮(假设为第 (i) 轮)要减去的逆序对个数就是所有“逆序对个数大于 (i) 的数的个数”(还是比较拗口)。对于询问操作,直接求第 (k) 轮后的树状数组前缀和即可。
现在来考虑交换操作。分类讨论,假设要交换 (a_x)(a_{x+1}),如果交换后 (a_x<a_{x+1}),那么实际上初始序列的逆序对个数便要减一,那么这个减一的贡献什么时候消失呢?显然是在第 (b_{x}+2) 轮(这里b[]是记录每个数逆序对个数的数组,(b_x) 是已经交换且减一的值),在这里对应的树状数组上把贡献加回来即可。对于 (a_x>a_{x+1}) 可采用同样的逻辑分析,这里不再赘述。
复杂度 (O(nlog n))

#include <bits/stdc++.h>
#define lb(x) (x&(-x))
using namespace std;
typedef long long ll;

const int N=2e5+5;
int n,m,a[N],b[N],cnt[N];
ll tot,c[N];

void add(int x,ll k) {for(;x<=n;x+=lb(x)) c[x]+=k;}
ll ask(int x) {ll res=0; for(;x;x-=lb(x)) res+=c[x]; return res;}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
	{
		scanf("%d",a+i);
		b[i]=i-1-ask(a[i]);
		tot+=b[i],++cnt[b[i]];
		add(a[i],1);
	}
	memset(c,0,sizeof(c));
	add(1,tot); tot=0;
	for(int i=1;i<=n;++i)
	{
		tot+=cnt[i-1];
		add(i+1,-(n-tot));
	}
	while(m--)
	{
		int op,x; scanf("%d%d",&op,&x);
		x=min(x,n-1);
		if(op==1)
		{
			swap(a[x],a[x+1]);
			swap(b[x],b[x+1]);
			if(a[x]<a[x+1])
			{
				--b[x];
				add(1,-1);
				add(b[x]+2,1);
			}
			else
			{
				++b[x+1];
				add(1,1);
				add(b[x+1]+1,-1);
			}
		}
		else printf("%lld
",ask(x+1));
	}
	return 0;
}

T3 最小环

此题不少人说简单,这只是这道题贪心思路好想却不好证所带来的错觉。(也有可能是我真的菜
首先这道题其实是把一堆数分成若干个环(对于每一个 (k),有 (gcd(n,k)) 个环,不知道的话可以做 luogu5887 学习一下)。然后对于每一个环就又回到了 (k=1) 的情况。
现在面临两个问题:第一,(n) 个数该如何分配到这些环中;第二,每个环中的数该如何排布。
对于问题一,我只知道结论:将 (n) 个数排序后,连续的一段分到一个环中((a_{1sim n/gcd(n,k)}) 在一个环里,其他以此类推)。现在这个结论我只搜到了 EI 的证明,属实没看懂,太菜了,其他人的题解都省略了(摊手.jpg
对于问题二,继续将在环里的 (p=n/gcd(n,k)) 个数排好序,那么环的排布一定是这样(假设 (p) 为奇数,偶数情况类似):

[a_1,a_3,a_5,dots,a_p,a_{p-2},a_{p-4},dots,a_2 ]

证明:使用归纳法。对于 (p=1,2,3) 的情况显然成立,现假设对于 (p=k) 的情况成立,考虑 (p=k+1) 的情况。
现在 (k) 个数已排好,考虑将 (a_{k+1}) 插入其中。我们把将 (a_{k+1}) 插入 (a_k,a_{k-1}) 之间的情况与插入任意两个数 (a_b,a_c) 的情况做对比,得到

[a_{k+1} imes a_k+a_{k+1} imes a_{k-1}-a_{k} imes a_{k-1}ge a_{k+1} imes a_b+a_{k+1} imes a_c-a_b imes a_c ]

[a_{k+1} imes (a_k+a_{k-1}-a_b-a_c)ge a_{k} imes a_{k-1}-a_b imes a_c ]

[{ m LHS}ge a_k^2+a_k imes a_{k-1}-a_k imes a_b-a_k imes a_c ]

重新移项得

[a_k^2+a_b imes a_cge a_{k} imes a_b+a_k imes a_c ]

由排序不等式,证毕。
活整完了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=2e5+5;
int n,m,a[N];
ll f[N];

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
		scanf("%d",a+i),f[0]+=1LL*a[i]*a[i];
	sort(a+1,a+n+1);
	while(m--)
	{
		ll ans=0; int k; scanf("%d",&k);
		if(!k) {printf("%lld
",f[k]); continue;}
		int p=n/__gcd(n,k);
		if(f[p]) {printf("%lld
",f[p]); continue;}
		for(int i=1;i<=n;i+=p)
		{
			for(int j=0;j<p-2;++j)
				ans+=1LL*a[i+j]*a[i+j+2];
			ans+=1LL*a[i]*a[i+1]+1LL*a[i+p-1]*a[i+p-2];
		}
		printf("%lld
",f[p]=ans);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/wzzyr24/p/12666547.html