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] 树
状态设计非常巧妙:
- $ f[u][0] $ :在 $ u $ 子树内,选了 $ u $ 点的贡献;
- $ f[u][1] $ :在 $ u $ 子树内,不选 $ u $ 点,但选了 $ u $ 的儿子的贡献;
- $ 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 $ 。
注意两点:
- $ long long $ 数组上限是可以开到 $ 2e8 $ 的;
- 在常数小或者跑不满的情况下, $ 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
总结
- 树形 $ dp $ 套路:先从下往上更新出以 $ u $ 为根的子树内的信息,然后自上而下地更新出节点 $ u $ 关于全局的信息。
- 对于节点u可能存在多种状态的情况,考虑分状态进行转移。
- 利用
(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\)

浙公网安备 33010602011771号