NOIP模拟96

T1 树上排列

解题思路

是一个一眼切的题目。。。

看到题目第一眼就是 Deepinc 学长讲的可重集,无序 Hash 。

直接套上一颗线段树再加上树剖, \(nlog^2n\) 直接过,好像也可以树状数组维护。

但是 第二个 Subtask 的数据出锅是真的离大谱,节点不仅有 0 还有编号大于 n 的点。。。

本来是有首切的但是一开始多测没清空险些猝死。。。

由于可重集这道题给我的心理阴影我还是套了一个双 Hash ,然而类似于维护一个区间加和区间乘的做法貌似也是可以的。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<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=25e4+10;
int Tes,n,q,s[N];
int tim,dfn[N],id[N],topp[N],son[N],dep[N],siz[N],fa[N];
int tot=1,head[N],ver[N<<1],nxt[N<<1];
struct Segment_Tree
{
	ull tre[N<<2],has[N],p[N],base;
	#define push_up(x) tre[x]=tre[ls]+tre[rs]
	void init(int lim)
	{
		p[0]=1; for(int i=1;i<=lim;i++) p[i]=p[i-1]*base;
		for(int i=1;i<=lim;i++) has[i]=has[i-1]+p[i];
	}
	void build(int x,int l,int r)
	{
		if(l==r) return tre[x]=p[s[id[l]]],void();
		int mid=(l+r)>>1; build(ls,l,mid); build(rs,mid+1,r);
		push_up(x);
	}
	void update(int x,int l,int r,int pos,int val)
	{
		if(l==r)return tre[x]=p[val],void();
		int mid=(l+r)>>1;
		if(pos<=mid) update(ls,l,mid,pos,val);
		else update(rs,mid+1,r,pos,val);
		push_up(x);
	}
	ull query(int x,int l,int r,int L,int R)
	{
		if(L<=l&&r<=R) return tre[x];
		int mid=(l+r)>>1; ull sum=0;
		if(L<=mid) sum+=query(ls,l,mid,L,R);
		if(R>mid) sum+=query(rs,mid+1,r,L,R);
		return sum;
	}
}T1,T2;
void add_edge(int x,int y)
{
	ver[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
}
ull power(ull x,int y)
{
	ull temp=1;
	for(;y;y>>=1,x=x*x)
		if(y&1) temp=temp*x;
	return temp;
}
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)
{
	topp[x]=tp; dfn[x]=++tim; id[tim]=x;
	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)
{
	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){int lca=LCA(x,y);return dep[x]+dep[y]-dep[lca]-dep[fa[lca]];}
void query(int x,int y)
{
	int dis=dist(x,y); ull t1=0,t2=0;
	while(topp[x]^topp[y])
	{
		if(dep[topp[x]]<dep[topp[y]]) swap(x,y);
		t1+=T1.query(1,1,n,dfn[topp[x]],dfn[x]);
		t2+=T2.query(1,1,n,dfn[topp[x]],dfn[x]);
		x=fa[topp[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	if(dfn[x]<=dfn[y])
		t1+=T1.query(1,1,n,dfn[x],dfn[y]),
		t2+=T2.query(1,1,n,dfn[x],dfn[y]);
	if(t1==T1.has[dis]&&t2==T2.has[dis]) printf("Yes\n");
	else printf("No\n");
}
void solve()
{
	tot=1; tim=0; memset(head,0,sizeof(head));
	memset(dfn,0,sizeof(dfn));
	memset(id,0,sizeof(id));
	memset(topp,0,sizeof(topp));
	memset(son,0,sizeof(son));
	memset(dep,0,sizeof(dep));
	memset(siz,0,sizeof(siz));
	memset(fa,0,sizeof(fa));
	n=read(); q=read(); dep[1]=1;
	for(int i=1;i<=n;i++) s[i]=read();
	for(int i=1,x,y;i<n;i++)
		x=read(),y=read(),
		add_edge(x,y),add_edge(y,x);
	dfs1(1); dfs2(1,1); T1.build(1,1,n); T2.build(1,1,n);
	while(q--)
	{
		int opt,x,y; opt=read(); x=read(); y=read();
		if(opt==1) query(x,y);
		else T1.update(1,1,n,dfn[x],y),T2.update(1,1,n,dfn[x],y);
	}
}
#undef int
int main()
{
	#define int long long
	freopen("a.in","r",stdin); freopen("a.out","w",stdout);
	T1.base=13331ull; T2.base=20; T1.init(250000); T2.init(250000);
	Tes=read(); while(Tes--) solve();
	return 0;
}

T2 连任

解题思路

看出来是按秩合并+可撤销并茶几了,然而都不会。。。

以时间为轴,可以判断出每一条边生效的时间区间,那么直接线段树分治。

对于每个区间中的操作直接加入并进行记录,然后在处理完这个区间以及子区间之后倒序撤回操作。

递归到叶子节点直接记录答案输出就好了。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<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=1e5+10,mod=1e9+7;
int n,m,Ans=1,fa[N],siz[N],inv[N],ans[N];
unordered_map<int,int> mp[N];
pair<int,int> s[N];
vector< pair<int,int> > v[N<<2],typ[N<<2];
inline int find(int x)
{
	if(fa[x]==x) return x;
	return find(fa[x]);
}
void insert(int x,int l,int r,int L,int R,pair<int,int> temp)
{
	if(L<=l&&r<=R) return v[x].push_back(temp),void();
	int mid=(l+r)>>1;
	if(L<=mid) insert(ls,l,mid,L,R,temp);
	if(R>mid) insert(rs,mid+1,r,L,R,temp);
}
void merge(int pos,int x,int y)
{
	if(find(x)==find(y)) return ;
	x=find(x); y=find(y); if(siz[x]>siz[y]) swap(x,y);
	Ans=Ans*inv[siz[x]]%mod*inv[siz[y]]%mod*(siz[x]+siz[y])%mod;
	siz[y]+=siz[x]; fa[x]=y; typ[pos].push_back(make_pair(x,y));
}
void work_back(int pos)
{
	for(int i=(int)typ[pos].size()-1;i>=0;i--)
	{
		int x=typ[pos][i].first,y=typ[pos][i].second;
		Ans=Ans*inv[siz[y]]%mod; fa[x]=x; siz[y]-=siz[x];
		Ans=Ans*siz[x]%mod*siz[y]%mod;
	}
}
void solve(int x,int l,int r)
{
	for(auto it:v[x]) merge(x,it.first,it.second);
	if(l==r) return ans[l]=Ans,work_back(x),void();
	int mid=(l+r)>>1; solve(ls,l,mid); solve(rs,mid+1,r);
	work_back(x);
}
#undef int
int main()
{
	#define int long long
	freopen("b.in","r",stdin); freopen("b.out","w",stdout);
	n=read(); m=read(); inv[1]=fa[1]=siz[1]=1;
	for(int i=2;i<=n;i++) fa[i]=i,siz[i]=1,inv[i]=inv[mod%i]*(mod-mod/i)%mod;
	for(int i=1,opt,x,y;i<=m;i++)
	{
		opt=read(); x=read(); y=read();
		if(x>y) swap(x,y); s[i]=make_pair(x,y);
		if(opt==1){mp[x].insert(make_pair(y,i));continue;}
		insert(1,1,m,mp[x].find(y)->second,i-1,s[i]);
		mp[x].erase(y);
	}
	for(int i=1;i<=m;i++)
		if(mp[s[i].first].find(s[i].second)!=mp[s[i].first].end())
			insert(1,1,m,mp[s[i].first].find(s[i].second)->second,m,s[i]);
	solve(1,1,m); for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
	return 0;
}

T3 排列

解题思路

sort -> 桶排 竟然会快 0.2s 实在是有用的卡常小技巧。。

第一感觉肯定是三维偏序,但是这个东西实际上是可以转化为二维上的!!!

假设一个三元排列 \((a,b,c)\) 满足三维偏序的个数可以通过两两之间的满足二维偏序的对数求出来。

\((a,b),(b,c),(a,c)\) 一共有 \(M\) 对合法的二维偏序对,那么一个对 \((i,j)\) 要么被计算一次要么被计算三次。

于是满足三维偏序的对数就是 \(\dfrac{M-\frac{n\times(n-1)}{2}}{2}\)

那么所有已经确定的数之间的贡献就有了,我们可以对于每一个已经确定的数字计算出它前后的 -1 的期望贡献。

同时也要算一下 -1 与 -1 之间的贡献。

code

#include<bits/stdc++.h>
// #define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<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,mod=998244353;
int n,cnt,ans,inv,inv2,sta[N];
struct Node{int a,b,c;}s[N],t[N];
bool vis[N];
struct BIT
{
	int tre[N];
	#define lowbit(x) (x&(-x))
	inline void clear(){memset(tre,0,sizeof(tre));}
	inline void insert(register int x){for(register int i=x;i<=n;i+=lowbit(i))tre[i]++;}
	inline int query(register int x){register int sum=0;for(register int i=x;i;i-=lowbit(i))sum+=tre[i];return sum;}
}T;
inline int power(register int x,register int y,register int p=mod)
{
	int temp=1;
	for(;y;y>>=1,x=1ll*x*x%p)
		if(y&1) temp=1ll*temp*x%p;
	return temp;
}
#define add(x,y) x=(x+y)%mod
inline bool comp(Node x,Node y){return x.b<y.b;}
// #undef int
int main()
{
	// #define int long long
	freopen("c.in","r",stdin); freopen("c.out","w",stdout);
	n=read(); inv2=(mod+1)>>1; for(register int i=1,x,y;i<=n;i++) s[i].b=read();
	for(register int i=1;i<=n;i++) s[i].a=i,s[i].c=read(),vis[s[i].c==-1?0:s[i].c]=true;
	for(register int i=1;i<=n;i++) T.insert(s[i].b),add(ans,T.query(s[i].b-1)); T.clear();
	for(register int i=1;i<=n;i++) if(!vis[i]) sta[++cnt]=i; inv=power(cnt,mod-2);
	for(register int i=1,tot=0;i<=n;i++)
	{
		if(s[i].c!=-1) T.insert(s[i].c),add(ans,T.query(s[i].c-1));
		register int pos=upper_bound(sta+1,sta+cnt+1,s[i].c)-sta-1;
		if(s[i].c!=-1) add(ans,1ll*pos*inv%mod*tot%mod),add(ans,1ll*(cnt-tot)*inv%mod*(cnt-pos)%mod);
		else add(ans,1ll*tot*inv2%mod); tot+=s[i].c==-1;
	}
	T.clear(); for(int i=1;i<=n;i++) t[s[i].b]=s[i];
	for(register int i=1,tot=0;i<=n;i++)
	{
		if(t[i].c!=-1) T.insert(t[i].c),add(ans,T.query(t[i].c-1));
		register int pos=upper_bound(sta+1,sta+cnt+1,t[i].c)-sta-1;
		if(t[i].c!=-1) add(ans,1ll*pos*inv%mod*tot%mod),add(ans,1ll*(cnt-tot)*inv%mod*(cnt-pos)%mod);
		else add(ans,1ll*tot*inv2%mod); tot+=t[i].c==-1;
	}
	printf("%lld",1ll*(1ll*ans-1ll*(n-1)*n%mod*inv2%mod+mod)%mod*inv2%mod);
	return 0;
}

T4 追逐

解题思路

一个最优的策略显然是把 qjd 逼到一个叶子节点之后,把之后需要的操作全部做了再把他放出来。

那么我们需要计算出每一个节点一开始向子树中走然后绕过子树一圈之后最后回到该节点并且下一步将要走到父亲节点的最小操作次数。

这个东西直接记录一个最大值次大值树形 DP 转移即可。

显然只会让 qjd 到向下走一次,于是我们可以二分出一个最后答案。

对于一个可能的答案值 \(mid\) 计算一下当前可以操作的步数也就是多余的操作步数,如果当前节点的一个儿子的 DP 值加上之前的操作值还有祖先要堵住的节点是大于 \(mid\) 的话,这个儿子就是不合法的。

我们显然是要利用多余的操作步数将这些儿子堵住的,如果不够的话也是不合法的。

最后再判一下加上最大值的子树之后总操作数是否合法就好了。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<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;
int n,root,ending,f[N],fa[N],du[N],bas[N];
int tot=1,head[N],ver[N<<1],nxt[N<<1];
bool vis[N];
void add_edge(int x,int y)
{
	ver[++tot]=y; du[y]++;
	nxt[tot]=head[x]; head[x]=tot;
}
void dfs(int x)
{
	int mx=0,sec=0;
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i]; if(to==fa[x]) continue;
		fa[to]=x; dfs(to);
		if(mx<f[to]) sec=mx,mx=f[to];
		else sec=max(sec,f[to]);
	}
	f[x]=sec+du[x]-1+(!fa[x]);
}
void dfs2(int x)
{
	if(x==root) bas[x]=0; else bas[x]+=du[x];
	for(int i=head[x];i;i=nxt[i])
	{
		int to=ver[i]; if(to==fa[x]) continue;
		bas[to]+=bas[x]; dfs2(to);
	}
}
bool check(int val)
{
	int now=ending,use=0,hav=1,mx=0;
	while(now!=root)
	{
		int cnt=0;
		for(int i=head[now];i;i=nxt[i])
			if(ver[i]!=fa[now]&&!vis[ver[i]])
				if(use+f[ver[i]]+bas[now]>val) cnt++;
				else mx=max(mx,f[ver[i]]);
		if(hav<cnt) return false;
		use+=cnt; hav-=cnt; now=fa[now]; hav++;
	}
	return use+mx<=val;
}
#undef int
int main()
{
	#define int long long
	freopen("d.in","r",stdin); freopen("d.out","w",stdout);
	n=read(); root=read(); ending=read();
	for(int i=1,x,y;i<n;i++)
		x=read(),y=read(),
		add_edge(x,y),add_edge(y,x);
	dfs(root); int x=ending; while(x){vis[x]=true;x=fa[x];}
	for(int i=1;i<=n;i++)
		if(vis[i])
			for(int j=head[i];j;j=nxt[j])
				bas[ver[j]]--;
	int l=0,r=n,ans; dfs2(root);
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(check(mid)) r=mid-1,ans=mid;
		else l=mid+1;
	}
	printf("%lld",ans); return 0;
}
posted @ 2021-11-13 20:49  Varuxn  阅读(48)  评论(1编辑  收藏  举报