WY模拟赛6

WY模拟赛6

T1. 洛谷 P9437 『XYGOI round1』一棵树

原题

一道典型的树形 $ dp $ ,但同时兼具一定的思维难度。

code:

#include <bits/stdc++.h> 
#define i8  __int128
#define int long long 
#define fuck inline
#define lb long double 
using namespace std; 
// typedef long long ll; 
const int N=1e6+23,M=64,mod=998244353;
const int inf=INT_MAX,INF=1e9+7; 
// const int mod1=469762049,mod2=998244353,mod3=1004535809;
// const int G=3,Gi=332748118; 
// const int M=mod1*mod2;
fuck int read()
{
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){if(c=='-'){f=-1;}c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c-'0');c=getchar();}
    return x*f;
}
fuck void write(int x)
{
    if(x<0){putchar('-');x=-x;}
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
struct node
{
    int sz,w,f;
}p[N];
vector<int>g[N];
int n,d[N],sum[N];
int ans=0;
fuck int gtl(int x)
{
    int res=1;
    while(x/=10)res*=10;
    return res*10;
}
fuck void dfs(int u,int fa)
{
    p[u].sz=1;
    p[u].f=p[u].w;
    for(auto v:g[u])
    {
        if(v==fa)continue;
        dfs(v,u);
        p[u].sz+=p[v].sz;
        p[u].f=(p[u].f+(p[u].w*p[v].sz)%mod+gtl(p[u].w)*p[v].f%mod)%mod;
        sum[u]=(sum[u]+p[v].f)%mod;
    }
}
fuck void dp(int u,int fa)
{
    for(auto v:g[u])
    {
        if(v==fa)continue;
        // cout<<n-p[v].sz<<" "<<p[u].w<<" "<<gtl(p[u].w)<<" "<<d[u]<<" "<<sum[u]<<" "<<p[v].f<<endl;
        d[v]=((n-p[v].sz)*p[u].w%mod+gtl(p[u].w)*(d[u]+sum[u]-p[v].f+mod)%mod)%mod;
        // cout<<d[v]<<" "<<p[v].f<<endl;
        ans=(ans+(d[v]+sum[v])*gtl(p[v].w)+n*p[v].w)%mod;
        // cout<<"bbbbbbbbbbbbb"<<endl;
        dp(v,u);
    }
}
fuck void solve()
{
    cin>>n;
    for(int i=1;i<=n;i++)cin>>p[i].w;
    for(int i=1;i<n;i++)
    {
        int x;cin>>x;
        g[x].push_back(i+1);
        g[i+1].push_back(x);
    }
    // cout<<"aaaaaaaaaaaa"<<endl;
    dfs(1,0);
    d[0]=0;
    // for(int i=1;i<=n;i++)cout<<p[i].f<<endl;
    dp(1,0);
    cout<<(ans+p[1].f)%mod<<endl;
}
signed main() 
{ 
    // ios::sync_with_stdio(false); 
    // cin.tie(0); cout.tie(0); 
    // int fuckccf=read();
    // int QwQ=read();
    // while(QwQ--)solve(); 
    solve(); 
    return 0; 
}
//  6666   66666  666666 
// 6    6  6   6      6 
// 6    6  6666      6 
// 6    6  6  6    6 
//  6666   6   6  6666666



T2. 洛谷 P12844 [蓝桥杯 2025 国 A] 树

原题

状态设计非常巧妙:

  1. $ f[u][0] $ :在 $ u $ 子树内,选了 $ u $ 点的贡献;
  2. $ f[u][1] $ :在 $ u $ 子树内,不选 $ u $ 点,但选了 $ u $ 的儿子的贡献;
  3. $ f[u][2] $ :在 $ u $ 子树内,不选 $ u $ 点,也不选 $ u $ 的儿子的贡献。

这样设计状态,区别了 $ u $ 作为不同状态时所具有的不同贡献,同时在转移时满足了任意两点之间的距离大于 $ 2 $ 的限制。

#include <bits/stdc++.h> 
#define i8  __int128
#define int long long 
#define fuck inline
#define lb long double 
using namespace std; 
// typedef long long ll; 
const int N=1e6+23,M=64,mod=998244353;
const int inf=INT_MAX,INF=1e9+7; 
// const int mod1=469762049,mod2=998244353,mod3=1004535809;
// const int G=3,Gi=332748118; 
// const int M=mod1*mod2;
fuck int read()
{
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){if(c=='-'){f=-1;}c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c-'0');c=getchar();}
    return x*f;
}
fuck void write(int x)
{
    if(x<0){putchar('-');x=-x;}
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
int n;
int f[N][5];
struct node
{
    int sz;
}p[N];
vector<int>g[N];
fuck int ksm(int a,int b)
{
    int res=1;
    while(b)
    {
        if(b&1)res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res%mod;
}
fuck int inv(int x){return ksm(x,mod-2);}
int sum[N];
fuck void dfs(int u,int fa)
{
    f[u][0]=f[u][2]=1;f[u][1]=0;
    for(auto v:g[u])
    {
        if(v==fa)continue;
        dfs(v,u);
        f[u][0]=(f[u][0]*f[v][2])%mod;
        f[u][2]=(f[u][2]*(f[v][1]+f[v][2])%mod)%mod;
        // cout<<sum[u]<<" "<<f[v][1]<<" "<<f[v][2]<<endl;
        sum[u]=(sum[u]*(f[v][1]+f[v][2])%mod)%mod;
        // cout<<u<<" "<<f[u][0]<<" "<<f[u][2]<<" "<<sum[u]<<endl;
    }
    for(auto v:g[u])
    {
        if(v==fa)continue;
        f[u][1]=(f[u][1]+(f[v][0]*sum[u]%mod*inv(f[v][1]+f[v][2]))%mod)%mod;
    }
}
fuck void solve()
{
    
    cin>>n;
    for(int i=1;i<=n-1;i++)
    {
        int x,y;cin>>x>>y;
        g[x].push_back(y);
        g[y].push_back(x);
    }
    for(int i=1;i<=n;i++)sum[i]=1;
    // for(int i=1;i<=n;i++)cout<<sum[i]<<endl;
    dfs(1,0);
    // cout<<f[1][0]<<" "<<f[1][1]<<" "<<f[1][2]<<endl;
    cout<<(f[1][0]+f[1][1]+f[1][2]-1)%mod;
}
signed main() 
{ 
    // ios::sync_with_stdio(false); 
    // cin.tie(0); cout.tie(0); 
    // int fuckccf=read();
    // int QwQ=read();
    // while(QwQ--)solve(); 
    solve(); 
    return 0; 
}
//  6666   66666  666666 
// 6    6  6   6      6 
// 6    6  6666      6 
// 6    6  6  6    6 
//  6666   6   6  6666666

T3. 洛谷 P10447 最短 Hamilton 路径

原题

其实是一道比较板子的状压 $ dp $ 。
注意两点:

  1. $ long long $ 数组上限是可以开到 $ 2e8 $ 的;
  2. 在常数小或者跑不满的情况下, $ 4e8 $ 的数据量是可以在 $ 1s $ 内跑过的。
#include <bits/stdc++.h> 
#define i8  __int128
#define int long long 
#define fuck inline
#define lb long double 
using namespace std; 
// typedef long long ll; 
const int N=1e3+5,M=64,mod=998244353;
const int inf=INT_MAX,INF=1e9+7; 
// const int mod1=469762049,mod2=998244353,mod3=1004535809;
// const int G=3,Gi=332748118; 
// const int M=mod1*mod2;
fuck int read()
{
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){if(c=='-'){f=-1;}c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c-'0');c=getchar();}
    return x*f;
}
fuck void write(int x)
{
    if(x<0){putchar('-');x=-x;}
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
int n;
int f[(1<<20)+5][22];
int a[25][25];
fuck void solve()
{
    memset(f,0x3f,sizeof(f));
    cin>>n;
    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)cin>>a[i][j];
    f[1][0]=0;
    for(int k=1;k<(1<<n);k++)
    {
        for(int i=0;i<n;i++)
        {
            if(!(k&(1<<i)))continue;
            for(int j=0;j<n;j++)
            {
                if(k&(1<<j))f[k][i]=min(f[k][i],f[k&~(1<<i)][j]+a[j][i]);
            }
        }
    }
    cout<<f[(1<<n)-1][n-1]<<endl;
}
signed main() 
{ 
    // ios::sync_with_stdio(false); 
    // cin.tie(0); cout.tie(0); 
    // int fuckccf=read();
    // int QwQ=read();
    // while(QwQ--)solve(); 
    solve(); 
    return 0; 
}
//  6666   66666  666666 
// 6    6  6   6      6 
// 6    6  6666      6 
// 6    6  6  6    6 
//  6666   6   6  6666666

T4. 洛谷 P6289 [COCI 2016/2017 #1] Vještica

原题

枚举子集的方式肥肠妙。

#include <bits/stdc++.h> 
#define i8  __int128
#define int long long 
#define fuck inline
#define lb long double 
using namespace std; 
// typedef long long ll; 
const int N=1e6+23,M=64,mod=998244353;
const int inf=INT_MAX,INF=1e9+7; 
// const int mod1=469762049,mod2=998244353,mod3=1004535809;
// const int G=3,Gi=332748118; 
// const int M=mod1*mod2;
fuck int read()
{
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){if(c=='-'){f=-1;}c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c-'0');c=getchar();}
    return x*f;
}
fuck void write(int x)
{
    if(x<0){putchar('-');x=-x;}
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
int sum[20][50];
int dp[(1<<16)+5][30];
int f[(1<<16)+5];
int n;
fuck void solve()
{
    cin>>n;
    memset(f,0x3f,sizeof(f));
    for(int i=1;i<=n;i++)
    {
        string s;cin>>s;
        for(int j=0;j<s.size();j++)sum[i][s[j]-'a'+1]++;
        sum[i][0]=s.size();
        f[1<<(i-1)]=sum[i][0];
    }
    for(int k=1;k<(1<<n);k++)
    {
        for(int i=1;i<=26;i++)dp[k][i]=inf;
        for(int i=1;i<=n;i++)
        {
            if(k&(1<<(i-1)))
            {
                for(int j=1;j<=26;j++)dp[k][j]=min(dp[k][j],sum[i][j]);
            }
        }
        dp[k][0]=0;
        for(int i=1;i<=26;i++)dp[k][0]+=dp[k][i];
        for(int kk=(k-1)&k;kk;kk=(kk-1)&k)
        {
            f[k]=min(f[k],f[kk]+f[kk^k]-dp[k][0]);
        }
    }
    cout<<f[(1<<n)-1]+1<<endl;
}
signed main() 
{ 
    // ios::sync_with_stdio(false); 
    // cin.tie(0); cout.tie(0); 
    // int fuckccf=read();
    // int QwQ=read();
    // while(QwQ--)solve(); 
    solve(); 
    return 0; 
}
//  6666   66666  666666 
// 6    6  6   6      6 
// 6    6  6666      6 
// 6    6  6  6    6 
//  6666   6   6  6666666

总结

  1. 树形 $ dp $ 套路:先从下往上更新出以 $ u $ 为根的子树内的信息,然后自上而下地更新出节点 $ u $ 关于全局的信息。
  2. 对于节点u可能存在多种状态的情况,考虑分状态进行转移。
  3. 利用 (k-1)&k 的技巧枚举二进制的非空子集。

完结收工!!!!!

个人主页

看完点赞,养成习惯

\(\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\Downarrow\)

posted @ 2025-07-09 23:03  Nightmares_oi  阅读(14)  评论(0)    收藏  举报