8.3考試總結(NOIP模擬19)[最長不下降子序列·完全揹包問題·最近公共祖先]

Varuxn發表於2021-08-03

一定要保護自己的夢想,即使犧牲一切。

前言

把人給考沒了。。。

看出來 T1 是一個週期性的東西了,先是打了一個暴力,想著打完 T2 T3 暴力就回來打。。

然後,就看著 T2 上頭了,後來發現是看錯題了,碼完暴力就已經 2.5h 了

接下來就會開始看 T3 看到了部分分非常令人欣喜(碼起主席樹根本停不下來)。

一直到考試結束都沒想起我那 T1 。

T1 最長不下降子序列

解題思路

對於比較小的資料可以直接 \(mathcal{O(nlogn)}\) 求出來(洗提 30 pts)

發現對於同一遞推式,同一膜數,一定會有一個不大於膜數的週期(抽屜原理)

當出現一個與之前相同的數字時,後面的數其實也是一樣的。

然後整個序列就成了三部分:最前面的散的+可以被劃分成幾個週期的區間+不完整的週期

接下來先對於第一部分以及它後面的一個週期可以直接暴力求出來。

並且以此類推直接將後面的區間中週期數加上就好了。

對於第三部分的轉移不可以只向前倒一個週期,這樣是不對的。

所有可以向前倒 週期個週期 這樣就可以完美地解決這個問題了。

邊界問題需要卡一下。

最後一個問題,如果運算的答案比正確答案少 1 怎麼辦??加上不就好了,有問題麼,沒有問題。(反正我的邊界是這麼卡出來的)

code(除錯資訊未刪)

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0')
	{
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*f;
}
const int N=1e6+10,INF=1e18;
int n,a,b,c,d,T,t0,beg,len,ans,f[N],s[N],pos[N];
int cnt,lsh[N],tre[N];
int lowbit(int x)
{
	return x&(-x);
}
int ask(int x)
{
	int temp=0;
	for(int i=x;i<=cnt+1;i+=lowbit(i))
		temp=max(temp,tre[i]);//,cout<<i<<endl;
	return temp;
}
void add(int x,int num)
{
	for(int i=x;i;i-=lowbit(i))
		tre[i]=max(tre[i],num);//,cout<<i<<endl;
}
void Special_Judge()
{
	lsh[++cnt]=-INF;
	lsh[++cnt]=s[1]=t0;
	for(int i=2;i<=n;i++)
		lsh[++cnt]=s[i]=(a*s[i-1]%d*s[i-1]%d+b*s[i-1]%d+c)%d;
	sort(lsh+1,lsh+cnt+1);
	cnt=unique(lsh+1,lsh+cnt+1)-lsh-1;
	for(int i=1;i<=n;i++)
		s[i]=lower_bound(lsh+1,lsh+cnt+1,s[i])-lsh;
//	for(int i=1;i<=cnt;i++)	cout<<lsh[i]<<' ';	
	for(int i=1;i<=n;i++)
	{
		f[i]=ask(cnt+1-s[i])+1;
//		cout<<f[i]<<' ';
		add(cnt+1-s[i],f[i]);
		ans=max(ans,f[i]);
	}
	printf("%lld",ans);
//	cout<<endl;for(int i=1;i<=n;i++)	cout<<s[i]<<' ';
	exit(0);
}
signed main()
{
	n=read();
	t0=read();
	a=read();
	b=read();
	c=read();
	d=read();
	s[1]=t0;
	for(int i=2;i<=min(n,2*d);i++)
	{
		s[i]=(a*s[i-1]%d*s[i-1]%d+b*s[i-1]%d+c)%d;//,cout<<s[i]<<' ';
//		cout<<s[i]<<endl;
	}
//	f();
	for(int i=1;i<=min(n,2*d);i++)
	{
//		cout<<i<<' '<<s[i]<<endl;
		if(pos[s[i]])
		{
//			cout<<s[i]<<' '<<pos[s[i]]<<endl;
			T=i-pos[s[i]];
			beg=pos[s[i]];
			break;
		}
		else	pos[s[i]]=i;
	}
	if(!beg||T*T+beg-1>=n||n<=1e6)	Special_Judge();
//	f();
//	cout<<T<<' '<<beg<<endl;
	cnt=1000;
	for(int i=1;i<=beg+T-1;i++)
	{
//		f();
//		cout<<s[i]<<' '<<cnt<<endl;
		f[i]=ask(cnt-s[i]+1)+1;
//		if(f[i])	f();
		add(cnt-s[i]+1,f[i]);
	}
//	f();
//	cout<<(n-beg+1)/T<<endl;
	for(int i=1;i<=T;i++)
	{
//		f();
		f[i]=f[i+beg-1]+(n-beg+1)/T-T;
		ans=max(f[i],ans);
//		cout<<f[i]<<endl;
	}
//	for(int i=1;i<=10;i++)	cout<<s[i]<<' ';
//	for(int i=beg;i<=beg+T-1;i++)	cout<<s[i]<<' ';cout<<endl;
	len=(n-beg+1)%T;
	s[0]=s[beg+T-1];
	for(int i=1;i<=T*T+len-1;i++)
		s[i]=(a*s[i-1]%d*s[i-1]%d+b*s[i-1]%d+c)%d;
//	for(int i=1;i<=T+len-1;i++)	cout<<s[i]<<' ';
	memset(tre,0,sizeof(tre));
	/*
	for(int i=1;i<=T;i++)
	{
//		cout<<f[i]<<' ';
		f[i]=max(f[i],ask(cnt-s[i]+1)+1);
//		cout<<ask(cnt-s[i]+1)+1<<endl;
//		cout<<f[i]<<endl;
		add(cnt-s[i]+1,f[i]);
	}
//	*/
//	cout<<T<<' '<<beg<<' '<<len<<endl;
	for(int i=1;i<=T;i++)
	{
		memset(tre,0,sizeof(tre));
		for(int j=i;j<=T;j++)
			add(cnt-s[j]+1,f[j]);
		for(int j=T+1;j<=((len>0)?T*T+len-1:T*T);j++)
		{
//			cout<<s[j]<<' ';
			int temp=ask(cnt-s[j]+1)+1;
//			cout<<temp<<endl;
			ans=max(ans,temp);
			add(cnt-s[j]+1,temp);
		}
	}
	printf("%lld",ans);
	return 0;
}

T2 完全揹包問題

解題思路

有一個新知識:同餘最短路。

通俗來講就是其它的數對於整個序列中最小的數字(這樣時間複雜度較小)取膜後。

所得到的餘數,對於更大的數只要與序列最小數取膜後餘數也是這個,那麼就一定可以通過序列中某些數字的組合得到。

然後發現這個題的 DP 可以對於前幾個物品,選擇了幾個有限制的物品,以及對於序列最小數取膜之後的值進行維護。

有限制的物品比較好考慮,主要是沒有限制的。

如果列舉的話,可以列舉到正無窮,顯然這樣是不可以的。

因此考慮上面提到的同餘最短路,若干個相同數字的加和取膜最終一定會得到之前取膜得到過的數。

對於這個性質,我們可以直接跑最短路,但是也可以對於沒一個“環”進行維護。

每加入一個數,就更新相同數的不同餘數的最小值,可以遞迴實現。

然後應用到此題上就是對於每一個加入的沒有限制的數,直接通過兩個迴圈分別找到這個“環”並且進行更新就好了。

對於所有的都有限制的資料進行特殊處理就好了(題庫好像並沒有這樣的資料)

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0')
	{
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*f;
}
const int N=1e4+10,M=3e5+10,INF=4557430888798830399ll;
int n,m,l,c,s[60],f[60][N];
bool vis[60][N];
bitset<M> bj[60];
void solve1()
{
	bj[0][0]=true;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=c;j++)
			bj[j]|=bj[j-1]<<s[i];
	for(int i=1,x;i<=m;i++)
	{
		bool jud=false;
		x=read();
		for(int j=0;j<=c;j++)
			if(bj[j][x])
			{
				jud=true;
				break;
			}
		if(jud)	printf("Yes\n");
		else	printf("No\n");
	}
}
void solve2()
{
	memset(f,0x3f,sizeof(f));
	f[0][0]=0;
	for(int i=2;i<=n;i++)
		if(s[i]<l)
		{
			memset(vis,false,sizeof(vis));
			for(int j=0;j<=c;j++)
				for(int k=0;k<s[1];k++)
					if(!vis[j][k])
						{
							int temp=k,minn=INF,id,now;
							while(!vis[j][temp])
							{
								vis[j][temp]=true;
								if(minn>f[j][temp])
								{
									minn=f[j][temp];
									id=temp;
								}
								temp=(temp+s[i])%s[1];
							}
							now=(id+s[i])%s[1];
							while(now!=id)
							{
								f[j][now]=min(f[j][now],minn+s[i]);
								minn=f[j][now];
								now=(now+s[i])%s[1];
							}
						}
		}
		else
			for(int j=1;j<=c;j++)
				for(int k=0;k<s[1];k++)
					f[j][k]=min(f[j][k],f[j-1][((k-s[i])%s[1]+s[1])%s[1]]+s[i]);
	for(int i=1,x;i<=m;i++)
	{
		x=read();
		bool jud=false;
		for(int j=0;j<=c;j++)
			if(f[j][x%s[1]]<=x&&f[j][x%s[1]]!=INF)
			{
				jud=true;
				break;
			}
		if(jud)	printf("Yes\n");
		else	printf("No\n");
	}
}
signed main()
{
	n=read();
	m=read();
	for(int i=1;i<=n;i++)
		s[i]=read();
	l=read();
	c=read();
	sort(s+1,s+n+1);
	if(s[1]>=l)	solve1();
	else	solve2();
	return 0;
}

T3 最近公共祖先

解題思路

可以說是本次考試中最水的題了。

不難發現對於每一個新加入的黑點,造成影響的其實只有以下幾部分:

  1. 以該黑色節點為根節點的子樹。

  2. 該黑色節點的所有祖先以及各級祖先除了該節點所在子樹的子樹。

然後直接在 DFS 序上維護線段樹,區間修改單點查詢,然後在已經更改過的節點打上標記就好了。

考場是看上了那個部分分,然後碼了棵主席樹,然後我就炸了。。

code

正解

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
#define ls x<<1
#define rs x<<1|1
using namespace std;
inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0')
	{
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*f;
}
const int N=2e5+10;
int n,m,s[N],top,sta[N];
int tim,id[N],fa[N],siz[N],dfn[N],dep[N];
int all,root[N];
int tot=1,head[N],nxt[N],ver[N];
int vis[N],flag;
string ch;
void add(int x,int y)
{
	ver[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
}
struct Segment_Tree
{
	int dat,laz;
}tre[N<<2];
void dfs(int x)
{
	siz[x]=1;
	dfn[x]=++tim;
	id[tim]=x;
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i];
		if(to==fa[x])	continue;
		fa[to]=x;
		dep[to]=dep[x]+1;
		dfs(to);
		siz[x]+=siz[to];
	}
}
void push_down(int x)
{
	if(!tre[x].laz)	return ;
	tre[ls].laz=max(tre[ls].laz,tre[x].laz);
	tre[rs].laz=max(tre[rs].laz,tre[x].laz);
	tre[ls].dat=max(tre[ls].dat,tre[x].laz);
	tre[rs].dat=max(tre[rs].dat,tre[x].laz);
	tre[x].laz=0;
}
void push_up(int x)
{
	tre[x].dat=max(tre[ls].dat,tre[rs].dat);
}
void update(int x,int l,int r,int L,int R,int num)
{
	if(L>R)	return ;
	if(L<=l&&r<=R)
	{
		tre[x].dat=max(tre[x].dat,num);
		tre[x].laz=max(tre[x].laz,num);
		return ;
	}
	push_down(x);
	int mid=(l+r)>>1;
	if(L<=mid)	update(ls,l,mid,L,R,num);
	if(R>mid)	update(rs,mid+1,r,L,R,num);
	push_up(x);
}
int query(int x,int l,int r,int pos)
{
	if(l==r)	return tre[x].dat;
	push_down(x);
	int mid=(l+r)>>1;
	if(pos<=mid)	return query(ls,l,mid,pos);
	return query(rs,mid+1,r,pos);
}
void change(int x)
{
	update(1,1,tim,dfn[x],dfn[x]+siz[x]-1,s[x]);
	while(x&&!vis[x])
	{
		vis[x]++;
		update(1,1,tim,dfn[fa[x]],dfn[x]-1,s[fa[x]]);
		update(1,1,tim,dfn[x]+siz[x],dfn[fa[x]]+siz[fa[x]]-1,s[fa[x]]);
		x=fa[x];
	}
}
signed main()
{
	vis[1]=true;
	n=read();
	m=read();
	for(int i=1;i<=n;i++)
		s[i]=read();
	for(int i=1,x,y;i<n;i++)
	{
		x=read();
		y=read();
		add(x,y);
		add(y,x);
	}
	dfs(1);
	for(int i=1,opt,x;i<=m;i++)
	{
		cin>>ch>>x;
		if(ch[0]=='M')
		{
			flag=true;
			change(x);
		}
		else	if(!flag)	printf("%d\n",-1);
		else	printf("%lld\n",query(1,1,tim,dfn[x]));
	}
	return 0;
}

35pts 主席樹



#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
#define ls tre[x].l
#define rs tre[x].r
using namespace std;
inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0')
	{
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*f;
}
const int N=2e5+10;
int n,m,s[N],top,sta[N];
int tim,fa[N],siz[N],dfn[N],dep[N],topp[N],son[N];
int all,root[N];
int cnt,lsh[N];
int tot=1,head[N],nxt[N],ver[N];
bool flag=false;
int vis[N];
void add(int x,int y)
{
	ver[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
}
struct Node
{
	int opt,x;
}q[N];
struct Segment_Tree
{
	int l,r,siz;
}tre[N*80];
int insert(int pre,int l,int r,int pos)
{
	int x=++all;
	tre[x]=tre[pre];
	tre[x].siz++;
	if(l==r)	return x;
	int mid=(l+r)>>1;
	if(pos<=mid)	ls=insert(tre[pre].l,l,mid,pos);
	else	rs=insert(tre[pre].r,mid+1,r,pos);
	return x;
}
void dfs(int x)
{
	if(vis[x]>=2)
	{
		for(int i=head[x];i;i=nxt[i])
			if(fa[ver[i]]==x)
			{
				root[ver[i]]=insert(root[x],1,cnt,s[x]);
				dfs(ver[i]);
			}
	}
	else	if(vis[x]==1)
	{
//		f();
//		cout<<"Check:	"<<x<<endl;
		for(int i=head[x];i;i=nxt[i])
			if(fa[ver[i]]==x)
			{
				if(!vis[ver[i]])	root[ver[i]]=insert(root[x],1,cnt,s[x]);
				else	root[ver[i]]=root[x];
				dfs(ver[i]);
			}
	}
	else
	{
		for(int i=head[x];i;i=nxt[i])
			if(fa[ver[i]]==x)
			{
				root[ver[i]]=root[x];
				dfs(ver[i]);
			}
	}
	
}
void dfs3(int x)
{
//	f();
//	cout<<x<<endl;
	if(vis[x])	vis[x]=2;
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i];
		if(to==fa[x])	continue;
		dfs3(to);
		vis[x]+=(vis[to]!=0);
	}
//	cout<<"Check:	"<<x<<' '<<vis[x]<<endl;
}
int query(int pre,int x,int l,int r)
{
	if(l==r)	return l;
	int mid=(l+r)>>1,rsum=tre[rs].siz-tre[tre[pre].r].siz;
	if(rsum)	return query(tre[pre].r,rs,mid+1,r);
	return query(tre[pre].l,ls,l,mid);
}
bool b[N];
void Special_Judge()
{
	add(0,1);
	int tmp=0;
	for(int i=1;i<=m;i++)
		if(!q[i].opt)
			vis[q[i].x]=b[q[i].x]=1,tmp++;
	sort(lsh+1,lsh+cnt+1);
	cnt=unique(lsh+1,lsh+cnt+1)-lsh-1;
	for(int i=1;i<=n;i++)
		s[i]=lower_bound(lsh+1,lsh+cnt+1,s[i])-lsh;
	dfs3(1);
	dfs(0);
	for(int i=1,ans;i<=m;i++)
	{
		if(!q[i].opt)	continue;
		if(b[q[i].x])	ans=lsh[s[q[i].x]];
		else ans=0;
		for(int j=head[q[i].x];j;j=nxt[j])
			if(vis[ver[j]])
			{
				ans=max(ans,lsh[s[q[i].x]]);
				break;
			}
		if(!tmp)	printf("%lld\n",-1ll);
		else	printf("%lld\n",max(ans,lsh[query(root[0],root[q[i].x],1,cnt)]));
	}
	exit(0);
}
void dfs1(int x)
{
	siz[x]=1;
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i];
		if(siz[to])	continue;
		fa[to]=x;
		dep[to]=dep[x]+1;
		dfs1(to);
		siz[x]+=siz[to];
		if(siz[to]>siz[son[x]])
			son[x]=to;
	}
}
void dfs2(int x,int tp)
{
	dfn[x]=++tim;
	topp[x]=tp;
	if(son[x])	dfs2(son[x],tp);
	for(int i=head[x];i;i=nxt[i])
		if(!dfn[ver[i]])
			dfs2(ver[i],ver[i]);
}
int LCA(int x,int y)
{
	if(!x||!y)	return 0;
	while(topp[x]^topp[y])
	{
		if(dep[topp[x]]<dep[topp[y]])
			swap(x,y);
		x=fa[topp[x]];
	}
	if(dep[x]>dep[y])
		swap(x,y);
	return x;
}
signed main()
{
	n=read();
	m=read();
	for(int i=1;i<=n;i++)
		lsh[++cnt]=s[i]=read();
	for(int i=1,x,y;i<n;i++)
	{
		x=read();
		y=read();
		add(x,y);
		add(y,x);
	}
	for(int i=1;i<=m;i++)
	{
		string ch;
		cin>>ch;
		q[i].x=read();
		if(ch[0]=='Q')	q[i].opt=1;
		else	q[i].opt=0;
		if(!q[i].opt&&q[i-1].opt)
			flag=true;
	}
	dfs1(1);
	dfs2(1,1);
	if(!flag)	Special_Judge();
	for(int i=1;i<=m;i++)
		if(!q[i].opt)	sta[++top]=q[i].x;
		else	if(!top)	printf("%lld\n",-1ll);
		else
		{
			int maxn=0;
			for(int j=1;j<=top;j++)
				maxn=max(maxn,s[LCA(sta[j],q[i].x)]);
			printf("%lld\n",maxn);
		}
	return 0;
}

相關文章