“蔚来杯“2022牛客暑期多校训练营5(A,D,G,其余感觉都是数学题就没上这里记录)

本文介绍了三道ACM竞赛题目:A.Don'tStarve使用动态规划处理几何路径问题,B.Birdsinthetree涉及树形动态规划的节点分类,C.KFCCrazyThursday则应用了回文自动机。博主详细讲解了各自问题的解决方案和关键算法技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这场unr了,感觉好多几何题数学题.

null牛客ACM提高训练营是面向ICPC/CCPC/CSP/NOI比赛备战的选手,由金牌选手命题,提高训练高难度的编程比赛,ACM/NOI拔高训练营。icon-default.png?t=M666https://ac.nowcoder.com/acm/contest/33190#question

题目翻译来自牛客群群友

A.Don't Starve(DP)

思路:我们可以把所有的边先处理出来,那么我们走的路线必定包含在其中.设f[x]为到达当前点x的最长的路径长度,初始化f[0]=0.然后针对于每一条边,我们都需要去更新f数组,但是不能直接去更新.因为我们直接更新的话就会直接把上一层的覆盖,而上一层的状态要保留去更新其他节点.比如我们从3到1的一条边更新了f[1],接下来要去用1到4的边去更新f[4],就会发现f[1]就不再是上一层的f[1]而是已经被更新的f[1],这样是不行的,就去采用一个中间变量数组g.

先让

g[edge[k].v]=max(g[edge[k].v],f[edge[k].u]+1);

用当前遍历的边更新上一层的情况并且保留结果,然后再

f[edge[k].v]=max(g[edge[k].v],f[edge[k].v]);    

更新所有情况赋值即可.最后结果就是f中的最大值.

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N =2e3+10,mod=998244353;
int x[N],y[N],f[N],g[N];
struct node
{
    int len,u,v;
};
vector<node>edge;
bool cmp(node a,node b)
{
    return a.len>b.len;
}
//从长到短排序之后,就会符合题目条件,遍历的边长度逐渐缩短
void solve()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>x[i]>>y[i],f[i]=-1e18,g[i]=-1e18;
    f[0]=0;
    for(int i=0;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(i!=j)
                edge.push_back({(x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]),i,j});
    sort(edge.begin(),edge.end(),cmp);
    for(int i=0,j=0;i<edge.size();i=j)
    {   
        for(j=i;j<edge.size()&&edge[i].len==edge[j].len;j++);
//确定相等同长度的边的范围,方便之后遍历
        for(int k=i;k<j;k++)
            g[edge[k].v]=-1e18;
        for(int k=i;k<j;k++)
            g[edge[k].v]=max(g[edge[k].v],f[edge[k].u]+1);
        for(int k=i;k<j;k++)
            f[edge[k].v]=max(g[edge[k].v],f[edge[k].v]);    
    }
    int maxx=0;
    for(int i=1;i<=n;i++)
        maxx=max(maxx,f[i]);
    cout<<maxx<<endl;
    return ;
}
signed main()
{
    cin.tie(0);
    cout.tie(0);
    ios::sync_with_stdio(0);
    solve();
    return 0;
}

D.Birds in the tree (树形dp)

对于这个题我们要对于度数为1的节点分情况讨论,确定此次度数为1的点是什么颜色的情况,跑两次树形dp.f[x]的含义是x节点的子图有多少种符合题目的情况.

那么我们进行分类讨论.

1.如果当前节点(父节点)等于你此次遍历时确定的颜色.那么它就可以作为边界,也就是说它可以作为只含有一个度数的点存在.此时需要考虑的情况,根据乘法原理,就是对于所有子树的可形成子图的个数+1的乘积(这里+1是因为我们可以不选这一颗子树),即为下列式子:

ans+=\prod (f[v]+1)-1+1;(-1是减去什么都不选的空图)

但是还要注意,因为可以所有的子树都不选,只留一个父亲节点(假设所有子树都不选的话只有一个点也可以符合条件,因为这是子图,我们可以保留一条边)所以还需要+1;

2.假设当前的父亲节点颜色不符合此次遍历所确定的颜色,那么他就不能作为端点(度数为1的点),必须选取两个及以上的符合条件的子树的子图才可以进行方案计算,所以式子就是:

ans+=\prod (f[v]+1)-1-\sum f[v];

这里的f[v]的求和的方案数是减去的让当前父亲节点作为端点的情况,这样计算就得到了他链接两个合法子图的所有方案数了.

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N =3e5+10,mod=1e9+7;
int f[N],tot=0,h[N],ans=0;
string s;
struct node
{
    int to,ne;
}edge[2*N];
void add(int x,int y)
{
    edge[++tot].to=y;
    edge[tot].ne=h[x];
    h[x]=tot;
    return ;
}
void dfs(int x,int fa,int col)
{
    int mul=1,sum=0;
    for(int i=h[x];i!=-1;i=edge[i].ne)
    {
        int j=edge[i].to;
        if(j==fa)
            continue;
        dfs(j,x,col);
        mul=mul*(f[j]+1)%mod;
        sum=(sum+f[j])%mod;
    }
    f[x]=((s[x]==col+'0')+mul-1+mod)%mod;
    if((s[x]==col+'0'))
        ans=(ans+f[x])%mod;
    else
        ans=(ans+f[x]-sum+mod)%mod;
    return ;
}
void solve()
{
    memset(f,0,sizeof f);
    memset(h,-1,sizeof h);
    int n,u,v;
    cin>>n>>s;
    s='z'+s;
    for(int i=1;i<n;i++)
    {
        cin>>u>>v;
        add(u,v);
        add(v,u);
    }
    dfs(1,-1,1);
    memset(f,0,sizeof f);
    dfs(1,-1,0);
    cout<<ans<<endl;
    return ;
}
signed main()
{
    cin.tie(0);
    cout.tie(0);
    ios::sync_with_stdio(0);
    solve();
    return 0;
}

G.KFC Crazy Thursday(回文自动机||马拉车算法&&差分前缀和)

 思路:manacher板子,该算法得到的p数组就是以该点为中心,回文串的半径长度(因为有一些预处理,可以学习一下马拉车再看).那么我们只需要每次更新回文串的时候,确定了该处的后半区间都是回文串,直接用差分进行区间修改,表示这个回文的后半段区间都是某个回文串的结尾.然后跑一遍前缀和就可以知道每个字母结尾的回文串有多少个.(用的区间修改,但是最后只需要查询一次,果断差分加前缀和)最后统计即可.

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2000005;
string s,s1="";
int p[N],num[N];
int manacher(int n)
{
    int mr=0,mid=0,sum=0;
    p[0]=0;
    for(int i=1;i<n;i++)
    {
        if(i<mr)
            p[i]=min(p[mid*2-i],mr-i);
        else
            p[i]=1;
        while(s1[i-p[i]]==s1[i+p[i]])
            p[i]++;
        if(i+p[i]>mr)
        {
            mr=i+p[i];
            mid=i;
        }
        num[i]+=1;
        num[i+p[i]]-=1;
    }
    for(int i=1;i<=n+1;i++)
    {
        cout<<num[i]<<" ";
        num[i]+=num[i-1];
        cout<<s1[i]<<" "<<num[i]<<endl;
    }
    return sum-1;
}
void solve()
{
    int n;
    cin>>n>>s;
    s1+='$';
    for(int i=0;i<s.size();i++)
    {
        s1+="#";
        s1+=s[i];
    }
    s1+='#';
    s1+='^';
    manacher((int)s1.size());
    int k=0,f=0,c=0;
    for(int i=0;i<s1.size();i++)
    {
        if(s1[i]=='k')
            k+=num[i];
        if(s1[i]=='f')
            f+=num[i];
        if(s1[i]=='c')
            c+=num[i];
    }
    cout<<k<<" "<<f<<" "<<c<<endl;;
    return ;
}
signed main()
{
    cin.tie(0);
    cout.tie(0);
    ios::sync_with_stdio(0);
    solve();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值