8.16 模拟赛小结

前言

最____的一集

T1 文件改名

image
\(n\leq 10^5\)

题意简要:有一堆文件要改名 保证初始的和改正后的名字都没重复
且更改过程中不予许出现重复 求最小操作步数

思考:这题推一下就行
若是状态转移 把这个东西丢到图上
发现可以直接跳过 \(s_i = t_i\) 的情况
然后考虑其它情况
如果是一个图 然后每个点只有一条出边 所以这要么是个链套么是个环 是环答案贡献加一即可
考虑并查集维护

时间复杂度 \(O(n\log n)\)
Code

#include<bits/stdc++.h>
#define N 200005
using namespace std;
int n,cnt,ans;
int fa[N];
string s1,s2;
map <string,int> mp;
int find(int x)
{
	if(fa[x]==x) return x;
	return fa[x]=find(fa[x]);
}
void merges(int u,int v)
{
	u=find(u),v=find(v);
	if(u==v)
	{
		ans++;
		return;
	}
	fa[u]=v;
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=2*n;i++)
		fa[i]=i;
	for(int i=1;i<=n;i++)
	{
		cin>>s1>>s2;
		if(s1==s2) continue;
		ans++;
		if(mp[s1]==0) mp[s1]=++cnt;
		if(mp[s2]==0) mp[s2]=++cnt;
		merges(mp[s1],mp[s2]);
	}
	cout<<ans;
	return 0;
}

T2 怪物猎人

image
image
这道题还算简单
题意:给你 \(n\) 个怪物 如果在第 \(j\) 次杀死这个怪物扣需要 \((a_i+(j-1)\times d)\times (b_i+(j-1)\times d)\) 的血 若血量不是正整数就会死 \(m\) 次询问若血量为 \(h\) 最多能杀多少个猎物

这种情况就得大力拆式子
\((a+j\times d)\times (b+j\times d)\)
\(=ab+(a+b)\times jd+(jd)^2\)

然后 \(ab\) 选了是一定会贡献的 所以和顺序无关 \((jd)^2\) 也是固定的 所以就是考虑 \((a+b)\times jd\) 然后 \(d\) 是固定的 因此 贪心一下 若 \(a+b\) 越大 顺序就必须放在最前面打 这样子 \(O(n^2)\) dp 一下即可 查询就是一个 \(\log n\) 的事

时间复杂度 \(O(n\log n)\)

#include<bits/stdc++.h>
#define ll long long
#define N 3005
using namespace std;
int n,m;
ll d,f[N][N],ans[N];
struct node{
	int a,b;
}a[N];
bool cmp(node a,node b)
{
	return a.a+a.b>b.a+b.b;
}
int main()
{
	scanf("%d%d%lld",&n,&m,&d);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i].a);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i].b);
	sort(a+1,a+1+n,cmp);
	fill(&f[0][0],&f[n+1][n+1],(ll)1e18);
	f[0][0]=0;
	for(int i=1;i<=n;i++)
	{
		f[i][0]=0;
		for(int j=1;j<=i;j++)
			f[i][j]=min(f[i-1][j],f[i-1][j-1]+(a[i].a+(j-1)*d)*(a[i].b+(j-1)*d));
	}
	for(int i=1;i<=n;i++)
		ans[i]=f[n][i];
	while(m--)
	{
		ll x;
		scanf("%lld",&x);
		printf("%d ",lower_bound(ans+1,ans+1+n,x)-ans-1);
	}
	return 0; 
}
// f[i][j] 前i怪物 砍j个

T3 魔术帽游戏

image
题意:有 \(n\) 个数字 给出 \(m\) 次操作 每次操作给两个下标 并交换在这两个下标上的数字 然后 \(q\) 次询问 问若操作 \(l\to r\) 区间 最后一个数的下标

其实这道题就是一个可持久化数组 考虑主席树
但一看是离线的 那么能离线的都不是主席树
每次记录一下 \(l-1,r\) 即可
询问区间\(l,r\)\(x\) 数等价于 在 \(l-1\) 中的 \(x\) 数在 \(r\) 时的位置
理解较难 举个例就会恍然大悟
所以离线一下即可

#include<bits/stdc++.h>
#define N 400005
using namespace std;
int n,m,q,l;
int a[N],b[N],x[N],ans[N],id[N],c[N];
struct oper{
	int x,pos,id,opr;
}op[N];
bool cmp(oper a,oper b)
{
	return a.pos<b.pos;
}
int main()
{
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=n;i++)
		x[i]=i,c[i]=i;
	for(int i=1;i<=m;i++)
		scanf("%d%d",&a[i],&b[i]);
	for(int i=1;i<=q;i++)
	{
		int x,l,r;
		scanf("%d%d%d",&x,&l,&r);
		op[i]=(oper){x,l-1,i,0};
		op[i+q]=(oper){x,r,i,1};
	}
	sort(op+1,op+1+q*2,cmp);
	l=1;
	for(int i=0;i<=m;i++)
	{
		c[x[a[i]]]=b[i];
		c[x[b[i]]]=a[i];
		swap(x[a[i]],x[b[i]]);
		while(l<=q*2&&op[l].pos<=i)
		{
			if(op[l].opr==0) id[op[l].id]=x[op[l].x];
			else ans[op[l].id]=c[id[op[l].id]];
			l++;
		}		
	}
	for(int i=1;i<=q;i++)
		printf("%d\n",ans[i]);
	return 0; 
}

T4 计算树

题意:给出一棵树 每个数有一个权值 每条边有一个操作 是 \(+,-,\times\) 之一 它们的运算优先级相同 求一条 \(u\to v\) 路径中计算出的权值是多少

考虑倍增
从一个点到另一个点必定会经过一下边 所意贡献肯定是一些加 一些乘
所以可以算出这些加和乘的标记

这是一个倍增合并标记的过程 思路可以参考一下线段树区间加区间乘的合并 即先乘再加
标记怎么合并呢?
image

根据线段树2的思想 我们用先乘再加 打好标记即可
然后查询也是这个思想

#include<bits/stdc++.h>
#define ll long long
#define N 100005
using namespace std;
int n,m;
ll mod=19491001;
ll val[N];
int dep[N];
int fa[N][20];
ll f[2][N][20],g[2][N][20];
// f 从下往上 0 +tag 1 *tag 
int head[N],tot=1;
struct edge{
	int to,next,w;
}e[N*2];
void add(int u,int v,int w)
{
	e[tot]=(edge){v,head[u],w};
	head[u]=tot++;
}
void dfs(int now,int fath,int fr)
{
	dep[now]=dep[fath]+1;
	fa[now][0]=fath;
	f[0][now][0]=g[0][now][0]=0;
	f[1][now][0]=g[1][now][0]=1;
	if(fr==1) f[0][now][0]=val[fath],g[0][now][0]=val[now];
	if(fr==2) f[0][now][0]=mod-val[fath],g[0][now][0]=mod-val[now];
	if(fr==3) f[1][now][0]=val[fath],g[1][now][0]=val[now];
	for(int i=1;fa[now][i-1];i++)
	{
		int fath=fa[now][i-1];
		fa[now][i]=fa[fath][i-1];
		f[1][now][i]=f[1][now][i-1]*f[1][fath][i-1]%mod;
		f[0][now][i]=(f[0][now][i-1]*f[1][fath][i-1]%mod+f[0][fath][i-1])%mod;
		g[1][now][i]=g[1][now][i-1]*g[1][fath][i-1]%mod;
		g[0][now][i]=(g[0][fath][i-1]*g[1][now][i-1]%mod+g[0][now][i-1])%mod;
	}
	for(int i=head[now];i;i=e[i].next)
	{
		int son=e[i].to;
		if(son==fath) continue;
		dfs(son,now,e[i].w);
	}
}
int lca(int u,int v)
{
	if(dep[u]<dep[v]) swap(u,v);
	for(int i=18;i>=0;i--)
		if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
	if(u==v) return u;
	for(int i=18;i>=0;i--)
		if(fa[u][i]!=fa[v][i]) u=fa[u][i],v=fa[v][i];
	return fa[u][0];
}
ll get1(int u,int v)
{
	ll t=val[u];
	ll s0=0,s1=1;
	for(int i=18;i>=0;i--)
		if(dep[fa[u][i]]>=dep[v])
		{
			s0=(s0*f[1][u][i]+f[0][u][i])%mod;
			s1=s1*f[1][u][i]%mod;
			u=fa[u][i];
		}
	return (t*s1+s0)%mod;
}
ll get2(ll t,int u,int v)
{
	ll s0=0,s1=1;
	for(int i=18;i>=0;i--)
		if(dep[fa[u][i]]>=dep[v])
		{
			s0=(s0+g[0][u][i]*s1)%mod;
			s1=s1*g[1][u][i]%mod;
			u=fa[u][i];
		}
	return (t*s1+s0)%mod;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%lld",&val[i]),val[i]=(val[i]%mod+mod)%mod;
	for(int i=1;i<=n-1;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);
		add(v,u,w);
	}
	dfs(1,0,0);
	while(m--)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		int lcas=lca(u,v);
		ll s1=get1(u,lcas),s2=get2(s1,v,lcas);
		printf("%lld\n",s2);
	}
	return 0; 
}
posted @ 2023-08-16 20:08  g1ove  阅读(7)  评论(0编辑  收藏  举报