NOI Online 提高组 题解(非官方非主流)


A

  • 加减相异的玩意,一个此类连通块内可以任意连这类边。这样一个连通块可以把欠的账堆到一个点上。

  • 加减相同的操作,分连通块内有没有环、有没有奇环讨论:

    • 无环:最简单,就是一棵树,一定有叶子。每次操作叶子就好了。
    • 有奇环(包括自环):选出奇环上一个点,所有账扔到这个点上。这个点可以通过环自救(滑稽),不过不能改变奇偶性。
    • 二部子图:同一个集合内的点相当于连了加减相异的边。

大言不惭放代码!写挂了别找我麻烦!

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
typedef long long i64;
const int N=1e5+5;

struct Edg{
	int u,v;
};

int a[N],b[N];
int cl[N],scl;
i64 aw[N],bw[N];
i64 sa,sb,sa1,sb1,sa0,sb0;
int bk[N],vs[N];
int f;
vector<Edg>e1;
vector<int>e2[N];
vector<int>e[N];

void dfs2(int v){
	int i;
	cl[v]=scl;
	aw[scl]+=a[v],
	bw[scl]+=b[v];
	for(i=0;i<e2[v].size();i++)
		if(!cl[e2[v][i]])dfs2(e2[v][i]);
}

void dfs1(int v,int p){
	int i;
	for(i=0;i<e[v].size();i++)
		if(e[v][i]!=p){
			if(!bk[e[v][i]]){
				bk[e[v][i]]=-bk[v];
				dfs1(e[v][i],v);
			}
			else{
				if(bk[e[v][i]]==bk[v])f=1;
				else if(!f)f=2;
			}
		}
}

void clc0(int v,int p){
	int i;
	vs[v]=1;
	for(i=0;i<e[v].size();i++)
		if(e[v][i]!=p&&!vs[e[v][i]])
			clc0(e[v][i],v);
	aw[p]+=bw[v]-aw[v];
}

void clc1(int v){
	int i;
	vs[v]=1;
	sa+=aw[v],sb+=bw[v];
	for(i=0;i<e[v].size();i++)
		if(!vs[e[v][i]])clc1(e[v][i]);
}

void clc2(int v){
	int i;
	vs[v]=1;
	if(bk[v]>0)sa1+=aw[v],sb1+=bw[v];
	else sa0+=aw[v],sb0+=bw[v];
	for(i=0;i<e[v].size();i++)
		if(!vs[e[v][i]])clc2(e[v][i]);
}

void clr(int n){
	int i;
	scl=0;
	vector<Edg>().swap(e1);
	for(i=1;i<=n;i++){
		cl[i]=0,aw[i]=0,bw[i]=0,
		bk[i]=0,vs[i]=0;
		vector<int>().swap(e2[i]);
		vector<int>().swap(e[i]);
	}
}

void rlm(){
	int n,m,i,t,u,v;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)
		scanf("%d",&a[i]);
	for(i=1;i<=n;i++)
		scanf("%d",&b[i]);
	for(i=1;i<=m;i++){
		scanf("%d%d%d",&t,&u,&v);
		if(t==1)
			e1.push_back((Edg){u,v});
		else
			e2[u].push_back(v),
			e2[v].push_back(u);
	}
	for(i=1;i<=n;i++)
		if(!cl[i]){
			++scl;
			dfs2(i);
		}
	for(i=0;i<e1.size();i++)
		e[cl[e1[i].u]].push_back(cl[e1[i].v]),
		e[cl[e1[i].v]].push_back(cl[e1[i].u]);
	for(i=1;i<=scl;i++)
		if(!bk[i]){
			f=0;
			bk[i]=1;
			dfs1(i,0);
			if(f==1){
				sa=sb=0;
				clc1(i);
				if(sa%2!=sb%2){
					printf("NO\n");
					clr(n);
					return;
				}
			}
			else if(f==2){
				sa1=sb1=0,
				sa0=sb0=0;
				clc2(i);
				if(sb1-sa1!=sb0-sa0){
					printf("NO\n");
					clr(n);
					return;
				}
			}
			else{
				clc0(i,0);
				if(aw[i]!=bw[i]){
					printf("NO\n");
					clr(n);
					return;
				}
			}
		}
	printf("YES\n");
	clr(n);
	return;
}

int main()
{
	freopen("sequence.in","r",stdin);
	freopen("sequence.out","w",stdout);
	int T;
	scanf("%d",&T);
	while(T--)rlm();
	return 0;
}

B

一轮冒排能把一个数之前的比它大的数中的一个(最大的那个)弄到它后面去,从这个角度入手就很好做了。注意\(k\le 2^{31}\)

(今天下午发现CE,求我的心理阴影面积)

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long i64;
const int N=2e5+5;

int n,m;
int p[N],a[N];

int Eclc(int c){
	if(c>n)c=n;
	int i,j;
	for(i=1;i<=n;i++)
		a[i]=p[i];
	for(i=1;i<=c;i++)
		for(j=1;j<n;j++)
			if(a[j]>a[j+1])swap(a[j],a[j+1]);
	int ans=0;
	for(i=1;i<n;i++)
		for(j=i+1;j<=n;j++)
			if(a[i]>a[j])++ans;
	return ans;
}

void EasyVersion(){
	int i,t,c;
	for(i=1;i<=n;i++)
		scanf("%d",&p[i]);
	for(i=1;i<=m;i++){
		scanf("%d%d",&t,&c);
		if(t==1)swap(p[c],p[c+1]);
		else printf("%d\n",Eclc(c));
	}
}

int s[N];

int Mclc(int c){
	int i,ans=0;
	for(i=1;i<=n;i++)
		ans+=max(0,s[i]-c);
	return ans;
}

void MidVersion(){
	int i,j,t,c;
	for(i=1;i<=n;i++)
		scanf("%d",&p[i]);
	for(i=1;i<=n;i++)
		for(j=1;j<i;j++)
			if(p[j]>p[i])++s[i];
	for(i=1;i<=m;i++){
		scanf("%d%d",&t,&c);
		if(t==1){
			swap(s[c],s[c+1]);
			if(p[c]>p[c+1])--s[c];
			else ++s[c+1];
			swap(p[c],p[c+1]);
		}
		else{
			printf("%d\n",Mclc(c));
		}
	}
}

int btx[N];

int Pgtsm(int x){
	int s=0;
	for(;x;x-=x&-x)
		s+=btx[x];
	return s;
}

void Padd(int x){
	for(;x<N;x+=x&-x)
		++btx[x];
}

i64 x1[N],x2[N];

void add(int x){
	if(!x){++x1[0];return;}
	int y;
	for(int y=x;y<N;y+=y&-y)
		++x1[y],x2[y]+=x;
}

void dec(int x){
	if(!x){--x1[0];return;}
	for(int y=x;y<N;y+=y&-y)
		--x1[y],x2[y]-=x;
}

i64 q1(int x){
	i64 s=0;
	for(;x;x-=x&-x)
		s+=x1[x];
	return s+x1[0];
}

i64 q2(int x){
	i64 s=0;
	for(;x;x-=x&-x)
		s+=x2[x];
	return s;
}

i64 Hclc(int c){
	if(c>n)c=n;
	return q2(n)-q2(c)-1ll*c*(n-q1(c));
}

void HardVersion(){
	int i,t,c;
	for(i=1;i<=n;i++)
		scanf("%d",&p[i]),
		a[p[i]]=i;
	for(i=n;i;i--)
		s[a[i]]=Pgtsm(a[i]),Padd(a[i]);
	for(i=1;i<=n;i++)
		add(s[i]);
	for(i=1;i<=m;i++){
		scanf("%d%d",&t,&c);
		if(t==1){
			swap(s[c],s[c+1]);
			if(p[c]>p[c+1])dec(s[c]),--s[c],add(s[c]);
			else dec(s[c+1]),++s[c+1],add(s[c+1]);
			swap(p[c],p[c+1]);
		}
		else{
			printf("%lld\n",Hclc(c));
		}
	}
}

int main()
{
	freopen("bubble.in","r",stdin);
	freopen("bubble.out","w",stdout);
	scanf("%d%d",&n,&m);
	if(n<=100&&m<=100)EasyVersion();
	else if(n<=2000&&m<=2000)MidVersion();
	else HardVersion();
	return 0;
}

C

任何一个询问都能拆成\(\gcd(n,k)\)个环。环内和环之间的放法调整法就能证明。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long i64;
const int N=2e5+5;

int a[N];
i64 ans[N];
int y[N];
int n,m;

void EasyVersion(){
	int d,i,k;
	for(d=0;d<=n/2;d++){
		for(i=1;i<=n;i++)y[i]=i;
		ans[d]=0;
		do{
			i64 t=0;
			for(i=1;i<=n;i++)t+=1ll*a[y[i]]*a[y[(i+d-1)%n+1]];
			ans[d]=max(ans[d],t);
		}while(next_permutation(y+1,y+n+1));
	}
	for(i=1;i<=m;i++)
		scanf("%d",&k),
		printf("%lld\n",ans[k]);
}

int gcd(int n,int m){
	return m?gcd(m,n%m):n;
}

void HardVersion(){
	int d,l,i,k;
	for(d=1;d<=n;d++)
		if(n%d==0){
			l=n/d;
			int ll=l,rr=1;
			for(i=1;i<=l;i++)
				if(i&1)y[rr++]=i;
				else y[ll--]=i;
			y[0]=y[l];
			i64 t=0;
			int x;
			for(x=0;x<n;x+=l)
				for(i=1;i<=l;i++)
					t+=1ll*a[y[i]+x]*a[y[i-1]+x];
			ans[d]=t;
		}
	for(i=1;i<=m;i++)
		scanf("%d",&k),
		printf("%lld\n",ans[gcd(n,k)]);
}

int main()
{
	freopen("ring.in","r",stdin);
	freopen("ring.out","w",stdout);
	int i;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)
		scanf("%d",&a[i]);
	sort(a+1,a+n+1);
	HardVersion();
	return 0;
}


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM