8.15 模拟赛小结

前言

最自闭的一集

T1 这一切

题意:给你一个图,定义操作为 若一个点只有一条白边 那么剩余的白边就会自动变黑 求至少要提前染几条边 通过若干次操作后使所有边都变黑

思考:画一下图就知道 满足所有点只有一条出边 其实就是一棵树 所以将每个联通快多余的边删掉即可

Code

#include<bits/stdc++.h>
#define N 100005
using namespace std;
int n,m,ans;
int head[N],tot=1;
struct edge{
	int to,next;
}e[N*4];
void add(int u,int v)
{
	e[tot]=(edge){v,head[u]};
	head[u]=tot++;
}
int vis[N],cnt,p;
void dfs(int now)
{
	p++;
	vis[now]=1;
	for(int i=head[now];i;i=e[i].next)
	{
		int to=e[i].to;
		cnt++;
		if(!vis[to]) dfs(to);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);
		add(v,u);
	}
	for(int i=1;i<=n;i++)
		if(!vis[i]) cnt=0,p=0,dfs(i),ans+=cnt/2-p+1;
	cout<<ans;
	return 0;
}

T2 都是 原题

题意:你可以随意交换数列两个数 使得最后的数列成为单峰数列 即存在一个 \(a_x\) 满足:

  • \(i<k\) \(a_i>a_{i-1}\)
  • \(i<k\) \(a_i<a_{i-1}\)

e.g 1 1 4 5 4 1
考场上是问了同学的 一开始一直在想逆序对
其实不妨这样思考:
\(1\) 开始考虑 \(1\) 必须扔到最前面或者最后面 扔完就变成了一个子问题
这样一想这问题就变成一个弟弟题了
所以往前找到有几个数和往后找到有几个数 哪里少就往哪里扔就行
树状数组维护即可

Code

#include<bits/stdc++.h>
#define ll long long
#define N 300005
using namespace std;
int n,a[N];
ll ans;
vector <int> pos[N];
struct tree{
	ll tr[N];
	void add(int x,int v)
	{
		while(x<=n)
		{
			tr[x]+=v;
			x+=x&-x; 
		}
	}
	ll ask(int x)
	{
		ll sum=0;
		while(x)
		{
			sum+=tr[x];
			x-=x&-x;
		}
		return sum;
	}
}tr;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int x;
		scanf("%d",&x);
		pos[x].push_back(i);
		tr.add(i,1);
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<pos[i].size();j++) tr.add(pos[i][j],-1);
		for(int j=0;j<pos[i].size();j++)
		{
			int x=pos[i][j];
			ans+=min(tr.ask(x-1),tr.ask(n)-tr.ask(x));
		}
	}
	cout<<ans;
	return 0;
}

T3

呜呜呜我好菜写了一天
题意:有 \(n\) 个笼子 每个笼子可能会有动物 \(A,B,C\) \(A\) 能吃 \(B\),\(B\) 能吃 \(C\),\(C\) 能吃 \(A\).明显最初有 \(3^n\) 种状态
image
我好菜 不会在线算法 被吊打了
想想在线怎么做
考虑两个并查集 \(u,v\) 要合并到 \(u\)
那么 \(u\) 子树所有胜利状态应该乘上 \(\frac{2}{3}\) \(v\) 子树所有节点胜利状态应该乘上 \(\frac{1}{3}\) 这是题目给定的胜利概率 然后两棵树再并起来

然后因为并查集动态搞我太菜了不会 所以只能离线把原树拍到树上 dfs 序解决
时间复杂度还带个 \(log\) 我太菜了

#include<bits/stdc++.h>
#define ll long long
#define N 400005
using namespace std;
ll mod=998244353,inv[5],pw=1;
int n,m;
int trs[N][2],fa[N],cnt,pre[N];
struct prob{
	int opr,u,v;
}op[N];
int in[N],out[N],dfn;
void dfs(int now)
{
	if(!now) return;
	in[now]=++dfn;
	dfs(trs[now][0]);
	dfs(trs[now][1]);
	out[now]=dfn;
}
int tr[N];
void add(int x,ll v)
{
	while(x<=dfn)
	{
		tr[x]=tr[x]*v%mod;
		x+=x&-x;
	}
}
ll ask(int x)
{
	ll sum=1;
	while(x)
	{
		sum=tr[x]*sum%mod;
		x-=x&-x;
	}
	return sum;
}
int main()
{
	inv[2]=(mod-mod/2);
	inv[3]=(mod-mod/3)*inv[2]%mod;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		pre[i]=i,pw=pw*3%mod;
	cnt=n;
	for(int i=1;i<=m;i++)
	{
		scanf("%d",&op[i].opr);
		if(op[i].opr==1)
		{
			scanf("%d%d",&op[i].u,&op[i].v);
			int node=++cnt;
			fa[pre[op[i].u]]=node,fa[pre[op[i].v]]=node;
			trs[node][0]=pre[op[i].u],trs[node][1]=pre[op[i].v];
			pre[op[i].u]=node;
		}
		else
			scanf("%d",&op[i].u);
	}
	for(int i=1;i<=cnt;i++)
	{
		if(fa[i]==0) dfs(i);
	}
	for(int i=1;i<=n;i++)
		pre[i]=i;
	for(int i=1;i<=dfn;i++)
		tr[i]=1;
	add(1,pw);
	for(int i=1;i<=m;i++)
	{
		if(op[i].opr==1)
		{	
			int u=pre[op[i].u],v=pre[op[i].v];
			add(in[u],inv[3]*2ll%mod),add(out[u]+1,3ll*inv[2]%mod);
			add(in[v],inv[3]),add(out[v]+1,3);
			pre[op[i].u]=fa[u];
		}
		else
			printf("%lld\n",ask(in[op[i].u]));
	}
	return 0;
}

T4 选择

image
image

数据范围: \(n,q\leq 10^5\space k\leq L\leq200\)
这就是一个√8题目 错解跑的比正解快
题目建议:退背包
题目大意:有一个树 每次选点 \(u,v\) 作为一条路径的起点和终点 然后选 \(k\) 条路径 要求 \(k\) 条路径都必须且只能经过这条路径 求方案数

思路:容易发现 一个点合法方案数就是不经过父亲的边的其它子树中选一个点即可 如果取两个就会有交集 所以这样子很容易做个 dp 就行了 用 \(f_i\) 表示选了 \(i\) 个子树 然后记得要预处理一下全部选根的情况 就是 \(1\) 容易得到 \(O(nL^2)\) 的代码 因为它是有顺序取的 所以要乘上排列数

最离谱的是这个比正解快 只 T 一个点
但是正解 T 了我四个点 无语

95pts 代码

#include<bits/stdc++.h>
#define N 100005
#define ll long long
using namespace std;
ll mod=998244353;
int n,m,t;
//
int head[N],tot=1;
struct edge{
	int to,next;
}e[N*2];
void add(int u,int v)
{
	e[tot]=(edge){v,head[u]};
	head[u]=tot++;
}
//f[i] 表示选了i次根的情况 
int in[N],out[N],dfn,size[N],f[N];
void dfs(int now,int fa)
{
	in[now]=++dfn;
	size[now]=1;
	for(int i=head[now];i;i=e[i].next)
	{
		int son=e[i].to;
		if(son==fa) continue;
		dfs(son,now);
		size[now]+=size[son];
	} 
	out[now]=dfn;
}
ll solve(int u,int v,int w)
{
	for(int i=1;i<=w;i++)
		f[i]=1;
	int t=0,fa=0; 
	f[0]=f[1]=1;
	for(int i=head[u];i;i=e[i].next)
	{
		int son=e[i].to,siz=size[son];
		if(siz>size[u]) 
		{
			fa=u;
			siz=n-size[u];
			continue;
		}
		else
			if(in[son]<=in[v]&&in[v]<=out[son]) 
			{
				t=1;
				continue;
			}
		for(int j=w;j>=1;j--)
			f[j]=(f[j]+f[j-1]*1ll*siz%mod*j%mod)%mod;
	}
	if(t==1)
		for(int j=w;j>=1;j--)
			f[j]=(f[j]+f[j-1]*1ll*(n-size[u])%mod*j%mod)%mod;
	return f[w];
}
int main()
{
	scanf("%d%d%d",&n,&m,&t);
	for(int i=1;i<n;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);
		add(v,u);
	}
	dfs(1,0);
	while(m--)
	{
		int u,v,k;
		scanf("%d%d%d",&u,&v,&k);
		printf("%lld\n",solve(u,v,k)*solve(v,u,k)%mod);
	}
	return 0;
}

然后正解
发现每次都做一次背包很多余 考虑退背包
每次求的是
\(f_i=f_i+f_{i-1}\times size \times i\)
如果去掉一个数 怎么办?
假设答案是 \(g\) 数组 已知 \(size\)
要退其实很简单 因为 \(f_0=g_0=1\)
然后一次次从 \(0\)\(L\) 往前推就行
详细:
\(g_i=f_i+f_{i-1}\times size \times i\)
\(f_i=g_i-f_{i-1}\times size \times i\)
递推 \(f\) 即可

吐了不知道为什么 T 四个点 无语

#include<bits/stdc++.h>
#define N 100005
using namespace std;
int mod=998244353;
int n,m,w;
int f[N][505];
//
int head[N],tot=1;
struct edge{
	int to,next;
}e[N*2];
void add(int u,int v)
{
	e[tot]=(edge){v,head[u]};
	head[u]=tot++;
}
//f[i] 表示选了i次根的情况 
int in[N],out[N],dfn,size[N];
void dfs(int now,int fa)
{
	in[now]=++dfn;
	size[now]=1;
	for(int i=0;i<=w;i++)
		f[now][i]=1;
	int s=0;
	for(int i=head[now];i;i=e[i].next)
	{
		int son=e[i].to;
		if(son==fa) continue;
		dfs(son,now);
		size[now]+=size[son];
		for(int j=w;j>=1;j--)
			f[now][j]=(f[now][j]+1ll*f[now][j-1]*size[son]*j%mod)%mod;
	} 
	for(int j=w;j>=1;j--)
		f[now][j]=(f[now][j]+1ll*f[now][j-1]*(n-size[now])*j%mod)%mod;
	out[now]=dfn;
}
int solve(int u,int v,int w)
{
	int t=0,fa=0,siz=0; 
	for(int i=head[u];i;i=e[i].next)
	{
		int son=e[i].to;
		if(size[son]>size[u])
		{
			fa=u;
			continue;
		}
		if(in[son]<=in[v]&&in[v]<=out[son]) 
		{
			t=son;
			break;
		}
	}
	if(t==0) siz=n-size[u];
	else siz=size[t];
	int tmp=1;
//	for(int j=0;j<=w;j++) g[j]=f[u][j];
	for(int j=1;j<=w;j++) tmp=(f[u][j]-tmp*1ll*siz*j%mod+mod)%mod;
	return tmp;
}
int main()
{
	scanf("%d%d%d",&n,&m,&w);
	for(int i=1;i<n;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);
		add(v,u);
	}
	dfs(1,0);
	while(m--)
	{
		int u,v,k;
		scanf("%d%d%d",&u,&v,&k);
		printf("%d\n",1ll*solve(u,v,k)*solve(v,u,k)%mod);
	}
	return 0;
}
posted @ 2023-08-15 20:13  g1ove  阅读(9)  评论(0编辑  收藏  举报