20241215比赛总结

谨此纪念2024挂分最多的一场

T1 Promises I Can't Keep

https://www.gxyzoj.com/d/hzoj/p/LG6554

死因:初值赋错,数组混用

显然换根dp,统计到每个叶子节点的权值即可

T2 [COCI2016-2017#4] Rima

https://www.gxyzoj.com/d/hzoj/p/4425

死因:算错数组空间,数组开大

显然,先倒序建trie树,如果一个点被选择,它的同父亲的点也可以被选择

所以,在一个联通快内,有一种可能的情况就是像样例2一样,将串长逐渐变短,挑出一条链,再将与这些点同父亲的点加入

但是,在这组数据中就会出错:

input:

8
ask
psk
krafna
sk
k
tk
atk
ptk

output:

7

因为在取到k时,可以返回取tk-atk-ptk,再取一个分支

所以,可以记录在这个联通快内,以i为根,在它的子节点中至多能取多少

T3 [CEOI2020] 星际迷航

https://www.gxyzoj.com/d/hzoj/p/4426

抽象dp,可以记0为必败态,1为必胜态,r[i]为i子树内有多少点的后面接上一个必败点后会改变i的状态

树形dp即可

对于统计答案,计算可以经过一次到0和一次到1的数量,然后矩阵快速幂统计答案即可

https://www.gxyzoj.com/d/hzoj/p/4426/solution

#include<cstdio>
#define ll long long
using namespace std;
const int mod=1e9+7;
int n,head[100005],edgenum;
ll d;
struct edge{
	int to,nxt;
}e[200005];
void add_edge(int u,int v)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	head[u]=edgenum;
}
ll cnt[100005],sumr[100005][2],r[100005];
int f[100005];
void dfs(int u,int fa)
{
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		dfs(v,u);
		cnt[u]+=(!f[v]);
		sumr[u][f[v]]+=r[v];
	}
	f[u]=(cnt[u]>0);
	if(cnt[u]==1) r[u]=sumr[u][0];
	else if(cnt[u]==0) r[u]=sumr[u][1]+1; 
}
int cnt1;
ll r2[100005],f2[100005];
void dfs2(int u,int fa)
{
	if(!f[u]) cnt1++;
	r2[u]=r[u],f2[u]=f[u];
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa) continue;
		int fv=f[v],fu=f[u];
		int cntu=cnt[u],cntv=cnt[v];
		int sumu0=sumr[u][0],sumu1=sumr[u][1],sumv0=sumr[v][0],sumv1=sumr[v][1];
		int ru=r[u],rv=r[v];
		cnt[u]-=(!f[v]);
		f[u]=(cnt[u]>0);
		sumr[u][f[v]]-=r[v];
		if(cnt[u]==1) r[u]=sumr[u][0];
		else if(!cnt[u]) r[u]=sumr[u][1]+1;
		else r[u]=0; 
		cnt[v]+=(!f[u]);
		f[v]=(cnt[v]>0);
		sumr[v][f[u]]+=r[u];
		if(cnt[v]==1) r[v]=sumr[v][0];
		else if(!cnt[v]) r[v]=sumr[v][1]+1;
		else r[v]=0;
		dfs2(v,u);
		f[v]=fv,f[u]=fu,cnt[u]=cntu,cnt[v]=cntv;
		sumr[u][0]=sumu0,sumr[u][1]=sumu1,sumr[v][0]=sumv0,sumr[v][1]=sumv1;
		r[u]=ru,r[v]=rv;
	}
}
struct mat{
	ll a[2][2];
	mat operator *(const mat &x)const{
		mat ans;
		for(int i=0;i<2;i++)
		{
			for(int j=0;j<2;j++)
			{
				ans.a[i][j]=(a[i][0]*x.a[0][j]%mod+a[i][1]*x.a[1][j]%mod)%mod;
			}
		}
		return ans;
	}
}T,ans;
mat qpow(mat x,ll y)
{
	mat res;
	res.a[0][0]=res.a[1][1]=1;
	res.a[0][1]=res.a[1][0]=0;
	while(y)
	{
	//	printf("%d ",y);
		if(y&1) res=res*x;
		x=x*x;
		y>>=1;
	}
	return res;
}
int main()
{
	scanf("%d%lld",&n,&d);
	for(int i=1;i<n;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add_edge(u,v);
		add_edge(v,u);
	}
	dfs(1,0);
	dfs2(1,0);
//	for(int i=1;i<=n;i++)
//	{
//		printf("%d ",f2[i]);
//	}
//	printf("%d\n",cnt1);
	for(int i=1;i<=n;i++)
	{
		if(!f2[i])
		{
			T.a[0][0]=(T.a[0][0]+n-r2[i])%mod;
			T.a[1][0]=(T.a[1][0]+n)%mod;
			T.a[0][1]=(T.a[0][1]+r2[i])%mod;
		}
		else
		{
			T.a[0][0]=(T.a[0][0]+r2[i])%mod;
			T.a[0][1]=(T.a[0][1]+n-r2[i])%mod;
			T.a[1][1]=(T.a[1][1]+n)%mod;
		}
	//	printf("%d %d %d %d\n",T.a[0][0],T.a[0][1],T.a[1][0],T.a[1][1]);
	//	printf("%d ",r[i]);
	}
	ans.a[0][0]=cnt1,ans.a[0][1]=n-cnt1;
	ans=ans*qpow(T,d-1);
//	printf("1");
	ll s1=ans.a[0][0],s2=ans.a[0][1];
	if(f[1])
	{
		printf("%lld",(1ll*(n-r2[1])*s1%mod+s2*n%mod)%mod);
	}
	else
	{
		printf("%lld",r2[1]*s1%mod);
	}
	return 0;
}

T4 「SMOI-R1」Company

https://www.gxyzoj.com/d/hzoj/p/LG10406

对于中间的树,贡献必然只有2种情况

  1. 根->最深的叶子

  2. 直径

而对于带有关键点的树,贡献情况则为:

  1. 根->关键点

  2. 关键点->距关键点最远的叶子节点

记a[i]表示第i棵树的情况1,b[i]为第i棵树的情况2,此时,b[i]必然只出现1次,所以,记录b[i]-a[i]的最大值即可

posted @   wangsiqi2010916  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程
点击右上角即可分享
微信分享提示