1. 程式人生 > 其它 >2021.11.10考試總結[衝刺NOIP模擬27]

2021.11.10考試總結[衝刺NOIP模擬27]

T1開掛 T2叄仟柒佰萬 T3超級加倍 T4歡樂豆

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