2021.11.10考试总结[冲刺NOIP模拟27]

T1 开挂

因为可以随便选 \(b\) ,所以可以先对 \(a\) \(b\) 排序。不难想到最优策略是尽量把操作集中到一个点上,再把较小的 \(b\) 分配给它。

因此应让开始偏小的 \(a\) 最终尽量大。这个倒序扫描就可以实现。中间会出现若干空隙,考虑最优策略,肯定是先将数填到最近的空隙里,用栈维护可以完成。

\(code:\)

T1
#include<bits/stdc++.h>
using namespace std;

namespace IO{
   typedef long long LL; typedef long double LD;
   typedef unsigned long long ULL; typedef double DB;
   typedef pair<int,int> PII;
   #define fi first
   #define se second
   #define mpr make_pair
   #define int ULL
   #define pb push_back
   const int Mxdt=100000;
   inline char gc(){
   	static char buf[Mxdt],*p1=buf,*p2=buf;
   	return p1==p2&&(p2=(p1=buf)+fread(buf,1,Mxdt,stdin),p1==p2)?EOF:*p1++;
   }
   inline int read(){
   	int t=0,f=0;char v=gc();
   	while(v<'0')f|=(v=='-'),v=gc();
   	while(v>='0')t=(t<<3)+(t<<1)+v-48,v=gc();
   	return f?-t:t;
   }
   void write(int x,char sp){
   	char ch[50]; int len=0;
   	if(x<0) x=-x,putchar('-');
   	do{ ch[len++]=x%10+'0'; x/=10; }while(x);
   	for(int i=len-1;~i;i--) putchar(ch[i]); putchar(sp);
   }
   void ckmin(int& x,int y){ x=x<y?x:y; }
   void ckmax(int& x,int y){ x=x>y?x:y; }
} using namespace IO;

const int NN=1000010;
int n,ans,a[NN],b[NN];
int it,top,pre;
PII stk[NN];
vector<int>op;

signed main(){
   freopen("openhook.in","r",stdin);
   freopen("openhook.out","w",stdout);
   n=read();
   for(int i=1;i<=n;i++) a[i]=read();
   for(int i=1;i<=n;i++) b[i]=read();
   sort(a+1,a+n+1); sort(b+1,b+n+1);
   pre=a[n];
   for(it=n-1;a[it]==a[n];it--)
   	++pre,op.pb(pre-a[it]);
   while(it){
   	int lst=it,cnt=-1;
   	if(a[it]<a[it+1]-1) stk[++top]=mpr(a[it]+1,a[it+1]-1);
   	while(a[it]==a[lst]) --it,++cnt;
   	while(cnt&&top){
   		while(cnt&&stk[top].fi<=stk[top].se)
   			--cnt,op.pb(stk[top].fi-a[lst]),++stk[top].fi;
   		if(stk[top].fi>stk[top].se) --top;
   	}
   	while(cnt) --cnt,++pre,op.pb(pre-a[lst]);
   }
   sort(op.begin(),op.end(),[](int x,int y)->bool{return x>y;});
   for(int i=0;i<op.size();i++) ans+=op[i]*b[i+1];
   write(ans,'\n');
   return 0;
}

T2 叁仟柒佰万

发现序列中合法的 \(mex\) 值只能有一个,可以用桶找出这个 \(mex\) 。

考虑暴力 \(n^2\) DP,可以预处理出所有 \(mex\) 合法的区间,设 \(f_i\) 为当前最后一个区间右端点为 \(i\) 的方案数,转移时枚举右端点对应的左端点,将方案数加和。

在右端点 \(r\) 增加时,最靠右的合法左端点是单调不降的,因此预处理可以通过单调指针加桶 \(O(n)\) 完成。转移时,每个右端点的合法左端点一定是从 \(1\) 开始的一段连续点。前缀和优化可以达到 \(O(n)\) 。

\(code:\)

T2
# pragma GCC optimize(12)
#include<bits/stdc++.h>
using namespace std;

namespace IO{
	typedef long long LL; typedef long double LD;
	typedef unsigned long long ULL; typedef double DB;
	const int Mxdt=100000;
	inline char gc(){
		static char buf[Mxdt],*p1=buf,*p2=buf;
		return p1==p2&&(p2=(p1=buf)+fread(buf,1,Mxdt,stdin),p1==p2)?EOF:*p1++;
	}
	inline int read(){
		int t=0,f=0;char v=gc();
		while(v<'0')f|=(v=='-'),v=gc();
		while(v>='0')t=(t<<3)+(t<<1)+v-48,v=gc();
		return f?-t:t;
	}
	void write(int x,char sp){
		char ch[50]; int len=0;
		if(x<0) x=-x,putchar('-');
		do{ ch[len++]=x%10+'0'; x/=10; }while(x);
		for(int i=len-1;~i;i--) putchar(ch[i]); putchar(sp);
	}
	void ckmin(int& x,int y){ x=x<y?x:y; }
	void ckmax(int& x,int y){ x=x>y?x:y; }
} using namespace IO;

const int NN=37000010,mod=1e9+7;
int t,n,mex,a[NN],f[NN];
int buc[NN];
int qmod(int a){ return a<0?(a+mod):(a>=mod?a-mod:a); }
int qpow(int a,int b,int res=1){
	for(;b;b>>=1,a=1ll*a*a%mod)
		if(b&1) res=1ll*res*a%mod;
	return res;
}
void read_a(){
	f[1]=-1;
	if(n<37000000)
		for(int i=1;i<=n;i++) buc[(a[i]=read())]=1,f[i]=-1;
	else{
		int x=read(),y=read();
		for(int i=2;i<=n;i++)
			buc[(a[i]=(1ll*a[i-1]*x+y+i)&262143)]=1,f[i]=-1;
	}
	while(buc[mex]) ++mex;
}
void clear(){
	mex=0;
	for(int i=1;i<=n;i++)
		buc[i]=f[i]=buc[a[i]]=0;
}
void init(){
	if(n<37000000) for(int i=1;i<=n;i++) buc[a[i]]=0;
	else memset(buc,0,sizeof(buc));
	int it=0,now=0,pos=0;
	while(now!=mex){
		++buc[a[++pos]];
		while(buc[now]) ++now;
	} --buc[a[pos]];
	for(int i=pos;i<=n;i++){
		++buc[a[i]];
		while(buc[a[it+1]]>1||a[it+1]>mex)
			++it,--buc[a[it]];
		f[i]=it;
	}
	f[0]=buc[0]=1;
}

signed main(){
	freopen("clods.in","r",stdin);
	freopen("clods.out","w",stdout);
	t=read();
	while(t--){
		n=read(); read_a();
		if(!mex){ write(qpow(2,n-1),'\n'); goto nxt; }
		init();
		for(int i=1;i<=n;i++){
			if(f[i]>=0) f[i]=buc[f[i]];
			else f[i]=0;
			buc[i]=qmod(buc[i-1]+f[i]);
		}
		write(f[n],'\n');
nxt:	clear();
	}
	return 0;
}

T3 超级加倍

一个牛B的东西: \(kruscal\) 重构树。按点权大小建树,可以满足原树上路径 \((x,y)\) 上最大或最小值在重构树上为 \(x,y\) 的 \(lca\) 。

因此建出树 \(t1,t2\) ,分别为按点权递增,递减建出的重构树。那么要求的答案实际上就是满足 \(x\) 在 \(t1\) 中为 \(y\) 祖先,在 \(t2\) 中为 \(y\) 后代的点对 \((x,y)\) 个数。本质上是个偏序问题,求出 \(t1\) 中的 \(dfs\) 序,在 \(t2\) 中用树状数组维护祖先链并查询即可。

\(code:\)

T3
#include<bits/stdc++.h>
using namespace std;

namespace IO{
	typedef long long LL; typedef long double LD;
	typedef unsigned long long ULL; typedef double DB;
	#define pb push_back
	const int Mxdt=100000;
	inline char gc(){
			static char buf[Mxdt],*p1=buf,*p2=buf;
			return p1==p2&&(p2=(p1=buf)+fread(buf,1,Mxdt,stdin),p1==p2)?EOF:*p1++;
	}
	inline int read(){
			int t=0,f=0;char v=gc();
			while(v<'0')f|=(v=='-'),v=gc();
			while(v>='0')t=(t<<3)+(t<<1)+v-48,v=gc();
			return f?-t:t;
	}
	void write(LL x,char sp){
		char ch[50]; int len=0;
		if(x<0) x=-x,putchar('-');
		do{ ch[len++]=x%10+'0'; x/=10; }while(x);
		for(int i=len-1;~i;i--) putchar(ch[i]); putchar(sp);
	}
	void ckmin(int& x,int y){ x=x<y?x:y; }
	void ckmax(int& x,int y){ x=x>y?x:y; }
} using namespace IO;

const int NN=2000010;
int n,idx,prt[NN],heade[NN],headx[NN],headn[NN];
int cnt,up[NN],dn[NN];
LL ans;
struct Edge{
	int to,nex;
	Edge(){}
	Edge(int t,int x){ to=t; nex=x; }
}tx[NN<<1],tn[NN<<1],te[NN<<1];
void addx(int a,int b){
	tx[++idx]=Edge(b,headx[a]); headx[a]=idx;
	tx[++idx]=Edge(a,headx[b]); headx[b]=idx;
}
void addn(int a,int b){
	tn[++idx]=Edge(b,headn[a]); headn[a]=idx;
	tn[++idx]=Edge(a,headn[b]); headn[b]=idx;
}
void adde(int a,int b){
	te[++idx]=Edge(b,heade[a]); heade[a]=idx;
	te[++idx]=Edge(a,heade[b]); heade[b]=idx;
}

namespace DSU{
	int FA[NN];
	int getf(int x){ return FA[x]==x?x:FA[x]=getf(FA[x]); }
	void merge(int x,int y){
		x=getf(x); y=getf(y);
		if(x==y) return;
		FA[y]=x;
	}
	void build(){
		idx=0;
		for(int i=1;i<=n;i++) FA[i]=i;
		for(int i=1;i<=n;i++)
			for(int j=heade[i],v=te[j].to;j;j=te[j].nex,v=te[j].to)
				if(v<i){ addx(i,getf(v)); merge(i,v); }
		idx=0;
		for(int i=1;i<=n;i++) FA[i]=i;
		for(int i=n;i>=1;i--)
			for(int j=heade[i],v=te[j].to;j;j=te[j].nex,v=te[j].to)
				if(v>i){ addn(i,getf(v)); merge(i,v); }
	}
} using namespace DSU;

namespace BIT{
	LL c[NN];
	void insert(int pos,int x){ while(pos<=n){ c[pos]+=x; pos+=pos&-pos; } }
	LL query(int pos,LL x=0){ while(pos){ x+=c[pos]; pos-=pos&-pos; } return x; }
	LL calc(int l,int r){ return query(r)-query(l-1); }
} using namespace BIT;

void dfsx(int s,int f){
	ans+=calc(up[s],dn[s]);
	insert(up[s],1);
	for(int v,i=headx[s];i;i=tx[i].nex)
		if((v=tx[i].to)!=f) dfsx(v,s);
	insert(up[s],-1);
}
void dfsn(int s,int f){
	up[s]=++cnt;
	for(int v,i=headn[s];i;i=tn[i].nex)
		if((v=tn[i].to)!=f) dfsn(v,s);
	dn[s]=cnt;
}

signed main(){
	freopen("charity.in","r",stdin);
	freopen("charity.out","w",stdout);
	n=read(); idx=read();
	for(int a,i=2;i<=n;i++)
		a=read(),adde(i,a);
	build(); dfsn(1,0); dfsx(n,0);
	write(ans,'\n');
	return 0;
}
上一篇:C语言:简单排序


下一篇:快速搭建CNN(卷积神经网络),实现分类MINST数据集(学习笔记三)