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种情况
-
根->最深的叶子
-
直径
而对于带有关键点的树,贡献情况则为:
-
根->关键点
-
关键点->距关键点最远的叶子节点
记a[i]表示第i棵树的情况1,b[i]为第i棵树的情况2,此时,b[i]必然只出现1次,所以,记录b[i]-a[i]的最大值即可
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程