• 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;
    }
    
  • 相关阅读:
    【原】一张图片优化5K的带宽成本
    让手机站点像原生应用的四大途径
    iScroll4下表单元素聚焦及键盘的异常问题
    蜕变·WebRebuild 2013 前端年度交流会邀请
    【原】js实现复制到剪贴板功能,兼容所有浏览器
    【原】css实现两端对齐的3种方法
    【原】常见CSS3属性对ios&android&winphone的支持
    一枚前端开发-页面重构方向的招聘信息
    【原】分享超实用工具给大家
    【原】webapp开发中兼容Android4.0以下版本的css hack
  • 原文地址:https://www.cnblogs.com/wzzyr24/p/12666547.html
Copyright © 2020-2023  润新知