7.29考試總結(NOIP模擬27)[牛半仙的妹子圖·Tree·序列]

Varuxn發表於2021-07-29

千載流年一如夢,月落山河一世傾。

前言

從思路上來講是比較成功的,從分數上就比較令人失望了。

考場上是想到了前兩個題的正解思路,其實最後一個題是半個原題,只可惜是我看不懂題。。。

這波呀,這波又是 語文素養限制OI水平。。

改題的時候連官方題解都沒看一眼就碼過了,感覺不錯。

總感覺出題人的題目名字有點。。。(T2的wrxcsd是啥意思????)

T1 牛半仙的妹子圖

解題思路

做法有很多,比如什麼:最小生成樹,魔改拓撲排序,等等。

我的做法是 Dij 最短路,看題目第一眼就是某個點到所有點路徑上困難值最大值的最小值。

跑一個 Dij 之後直接對於每一個距離離散化一下,進而求出當每種最大的可以克服的困難度可以到達的種類數。

觀察到問題是一個區間的值,線段樹就算了,畢竟我們不需要修改。

考慮字首和,查詢兩個端點在離散化陣列裡的值,對於整個的部分直接字首和處理。

對於剩餘的部分用剩餘部分的區間長度乘上貢獻即可。

這種打法對於 \(l=r\) 似乎是不可做的,因此直接特判就行了(逃。

可惜我考場上一時糊塗,把離線和線上分開處理了,然後離線錯了,還沒特判,\(100pts\rightarrow 55pts\)

本來以為切了的,我裂開。。。

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=2e6+10,M=6e3+10;
int n,m,ans,fro,T,opt,mod,dis[N],all[N],qzh[N],pre[N];
int tot,head[N],ver[N<<1],edge[N<<1],nxt[N<<1];
int cnt,lsh[N<<2];
bool vis[N];
priority_queue<pair<int,int> > que; 
struct Node
{
	int dis,col;
}s[N];
struct Ques
{
	int l,r;
}q[N];
bool comp(Node x,Node y)
{
	return x.dis<y.dis;
}
void add_edge(int x,int y,int val)
{
	ver[++tot]=y;
	edge[tot]=val;
	nxt[tot]=head[x];
	head[x]=tot;
}
void Dij()
{
	que.push(make_pair(0,fro));
	memset(dis,0x3f,sizeof(dis));
	dis[fro]=0;
	while(!que.empty())
	{
		int x=que.top().second;
		que.pop();
		if(vis[x])	continue;
		vis[x]=true;
		for(int i=head[x];i;i=nxt[i])
		{
			int to=ver[i],val=edge[i];
			if(dis[to]>max(dis[x],val))
			{
				dis[to]=max(dis[x],val);
				que.push(make_pair(-dis[to],to));
			}
		}
	}
}
signed main()
{
	n=read();
	m=read();
	T=read();
	fro=read();
	opt=read();
	if(opt)	mod=read();
	for(int i=1;i<=n;i++)
		s[i].col=read();
	for(int i=1,x,y,val;i<=m;i++)
	{
		x=read();
		y=read();
		val=read();
		add_edge(x,y,val);
		add_edge(y,x,val);
	}
	for(int i=1;i<=T;i++)
	{
		q[i].l=read();
		q[i].r=read();
	}
	Dij();
	for(int i=1;i<=n;i++)
		lsh[++cnt]=s[i].dis=dis[i];
	sort(lsh+1,lsh+cnt+1);
	cnt=unique(lsh+1,lsh+cnt+1)-lsh-1;
	for(int i=1;i<=n;i++)
		s[i].dis=lower_bound(lsh+1,lsh+cnt+1,s[i].dis)-lsh;
	sort(s+1,s+n+1,comp);
	for(int i=1;i<=n;i++)
	{
		all[s[i].col]++;
		int temp=0;
		if(all[s[i].col]==1)	temp=1;
		qzh[s[i].dis]=qzh[s[i-1].dis]+temp;
	}
	for(int i=1;i<=cnt;i++)
		pre[i]=pre[i-1]+qzh[i-1]*(lsh[i]-lsh[i-1]-1)+qzh[i];
	for(int i=1,li,ri;i<=T;i++)
	{
		li=q[i].l;
		ri=q[i].r;
		if(opt)
		{
			li=(li^ans)%mod+1;
			ri=(ri^ans)%mod+1;
			if(li>ri)	swap(li,ri);
		}
		if(li!=ri)
		{
			int ls=upper_bound(lsh+1,lsh+cnt+1,li)-lsh;
			int rs=upper_bound(lsh+1,lsh+cnt+1,ri)-lsh;
			ans=pre[rs-1]-pre[ls]+qzh[ls];
			ans+=(lsh[ls]-li)*qzh[ls-1]+(ri-lsh[rs-1])*qzh[rs-1];
		}
		else
		{
			if(li<lsh[cnt])
			{
				int temp=upper_bound(lsh+1,lsh+cnt+1,li)-lsh-1;
				if(temp<=0)	ans=0;
				else	ans=qzh[temp];
			}
			else	ans=qzh[cnt];
		}
		printf("%lld\n",ans);
	}
	return 0;
}

T2 牛半仙的妹子Tree

解題思路

反正我這個法是個暴力,但是能切題就行

題庫資料是可以直接打過去,但是 yspm 和 zero4338 造了兩組資料,成功 Hack 掉了我。。

我又看了一下資料,誒,又是鏈,我直接來一套 “組合拳”,其實就是對於不同的採用不同的打法。

主要思路

這個可以切掉除了Hack資料之外的點,但是優化之前容易被各種鏈給卡掉(優化思想來自 戰神 )

首先,對於每一個開始不喜歡的點(以後簡稱為插入)其實需要的只是他插入的時間和位置。

因此我們開一個陣列記錄下這兩個值。

然後在每次查詢的時候暴力列舉陣列裡每一個點與現在查詢的點的距離與時間差的關係。

距離可以用 RMQ 或者 樹鏈剖分 來搞,理論上來講 RMQ 是更快的。

但是實際上,因為 RMQ 初始化時間比較長,所以還是 樹鏈剖分 可能更快一些。

這樣的預估分數是 \(40pts\sim 60pts\) 但是出乎意料地搞到了 90pts!!。

優化插入部分,我們發現其實有些節點可以傳到的點是可以在別的節點也傳到的。

那麼我們就沒有必要把它給插進去,因此,在每一次插入之前進行一次類似於查詢的操作就好了。

特殊判斷思路

卡掉上面的做法只需要讓我們的儲存陣列裡的元素足夠多,並且每一次查詢都查詢最遠的兩個端點就好了。

因此,WindZR 想出了這樣一種做法,適用於所有的類似於鏈(一個深度十分大的主幹上有一些小的分支)

其實就是優化後的擴散。。。

發現每個點有用的擴散只有一次,因此我們記錄上一次陣列裡擴散到的位置。

然後再此基礎上接著擴散一個表示每一個是否被擴散到就好了。

清空操作直接清空陣列就好了。

其它

這兩種做法(不要在意程式碼裡的\(\dfrac{5}{8}\),本來想卡個邊界的但是好像不卡也行)的時間複雜度都有一點玄學(至少我是這麼想的),我最快卡到了 500ms+(資料加強後)

當然也有強者卡進了500ms以內,但畢竟是我的做法大眾普及了。。。

也不知道為啥機房裡有不少人都在 Hack 我。。。。。

好像這兩種做法的結合也可以被卡掉,可以搞一個類似於神經元的圖(其實就是鏈+菊花圖)。

或者一種長菊花圖(就是讓多條鏈的端點連線到一個節點上)(可fengwu說卡不掉,有異議的請線下解決)

但是這種資料有點難造,所以歡迎各位前來 Hack 。

另外,yspm的線段樹做法(\(code\))或者其他的點分治還有 火羽白日生 的虛樹\(code\))好像也可以。。。

code

#include<bits/stdc++.h>
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=1e5+10;
int n,m,las,top;
int tot,head[N],nxt[N<<1],ver[N<<1];
int tim,dep[N],topp[N],siz[N],dfn[N],son[N],fa[N];
int cnt,st[N],mxdep;
bool flag,vis[N];
struct Node
{
	int pos,tim;
}sta[N];
void add_edge(int x,int y)
{
	ver[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
}
void dfs1(int x)
{
	mxdep=max(mxdep,dep[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;
}
int dist(int x,int y)
{
	return dep[x]+dep[y]-2*dep[LCA(x,y)];
}
void Special_Judge()
{
	for(int i=1,opt,x;i<=m;i++)
	{
		opt=read();
		x=read();
		int pre=cnt;
		for(int k=las+1;k<=pre;k++)
			for(int j=head[st[k]];j;j=nxt[j])
			{
				if(!vis[ver[j]])	st[++cnt]=ver[j];
				vis[ver[j]]=true;
			}
		las=pre;
		if(opt==2)
		{
			memset(vis,false,sizeof(vis));
			cnt=las=0;
		}
		else	if(opt==1)
		{
			if(!vis[x])	st[++cnt]=x,vis[x]=true;
		}
		else
		{
			if(vis[x])	printf("wrxcsd\n");
			else	printf("orzFsYo\n");
		}
	}
	exit(0);
}
signed main()
{
	n=read();
	m=read();
	for(int i=1,x,y;i<n;i++)
	{
		x=read();
		y=read();
		add_edge(x,y);
		add_edge(y,x);
		if(x!=y+1&&y!=x+1)	flag=true;
	}
	dfs1(1);
	dfs2(1,1);
	if(!flag||mxdep>=n*5/8)	Special_Judge();
	for(int i=1,opt,x;i<=m;i++)
	{
		opt=read();
		x=read();
		if(opt==2)	top=0;
		else	if(opt==1)
		{
			bool jud=true;
			for(int j=1;j<=top;j++)
				if(dist(sta[j].pos,x)<=i-sta[j].tim)
				{
					jud=false;
					break;
				}
			if(jud)	sta[++top]=(Node){x,i};
		}
		else
		{
			bool jud=false;
			for(int j=1;j<=top;j++)
				if(dist(sta[j].pos,x)<=i-sta[j].tim)
				{
					jud=true;
					break;
				}
			if(jud)	printf("wrxcsd\n");
			else	printf("orzFsYo\n");
		}
	}
	return 0;
}

T3 牛半仙的妹子序列

解題思路

其實就是極長上升序列。

和之前做的God Kowns幾乎是一個題,還是李超線段樹或者線段樹維護單調棧。。

今天剛打了疫苗有點困,可能口胡說不清,所以不懂的請移步 God Kowns

code

#include<bits/stdc++.h>
#define int long long
#define ls x<<1
#define rs x<<1|1
#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=2e5+10,mod=998244353;
int n,mx,s[N],q[N<<2];
struct Segment_Tree
{
	int mx,sum;
}tre[N<<2];
int work(int x,int l,int r,int num)
{
	if(l==r)
	{
		if(tre[x].mx>num)	return tre[x].sum%mod;
		return 0;
	}
	int mid=(l+r)>>1;
	if(tre[rs].mx>num)	return (q[ls]+work(rs,mid+1,r,num)%mod)%mod;
	return work(ls,l,mid,num)%mod;
}
int query(int x,int l,int r,int L,int R)
{
	if(L<=l&&r<=R)
	{
		int temp=work(x,l,r,mx);
		mx=max(mx,tre[x].mx);
		return temp%mod;
	}
	int mid=(l+r)>>1,sum=0;
	if(R>mid)	sum=query(rs,mid+1,r,L,R)%mod;
	if(L<=mid)	sum=(sum+query(ls,l,mid,L,R)%mod)%mod;
	return sum%mod;
}
void update(int x,int l,int r,int pos,int num1,int num2)
{
	if(l==r)
	{
		tre[x].sum=q[x]=num1;
		tre[x].mx=num2;
		return ;
	}
	int mid=(l+r)>>1;
	if(pos<=mid)	update(ls,l,mid,pos,num1,num2);
	else	update(rs,mid+1,r,pos,num1,num2);
	tre[x].mx=max(tre[ls].mx,tre[rs].mx);
	q[ls]=work(ls,l,mid,tre[rs].mx)%mod;
	tre[x].sum=(q[ls]+tre[rs].sum)%mod;
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
		s[i]=read();
	for(int i=1;i<=n;i++)
	{
		mx=-1;
		int val=query(1,0,n+1,0,s[i]-1)%mod;
		if(!val)	val=1;
		update(1,0,n+1,s[i],val,i);
	}
	mx=-1;
	printf("%lld",query(1,0,n+1,0,n)%mod);
	return 0;
}

相關文章