20261003 DP专题
A. 乐乐
一个 \(2\) x \(n\) 的网格,每个格子可以放置豹子或者牛。四联通相邻(即上下左右)的同种动物会一起乐乐,并且乐乐具有传递性。
求恰好最多有 \(k\) 个格子的动物互相不乐乐的方案数。答案对 \(998244353\) 取模。
-
首先由最多很容易注意到网格中的动物恰好构成 \(k\) 个连通块,同一个块中出现两个格子它们就会一起乐乐,故每个块贡献一个不乐乐的格子;
-
现在考虑如何求方案数。设 \(dp[i][j][1/2][1/2]\) 表示第 \(i\) 列,前 \(1\) ~ \(i\) 列共有 \(j\) 个连通块,第 \(i\) 列的上面放豹子/牛、下面放豹子/牛的方案数;
-
如何转移:枚举即可。
#include<bits/stdc++.h>
using namespace std;
long long g[1003][3][3][2003];
const long long mod=998244353;
int main(){
freopen("A.in","r",stdin);
freopen("A.out","w",stdout);
int n,k;
scanf("%d%d",&n,&k);
memset(g,0,sizeof(g));
g[1][1][1][1]=g[1][2][2][1]=g[1][1][2][2]=g[1][2][1][2]=1;
for(int i=2;i<=n;i++){
g[i][1][1][1]=g[i-1][1][1][1];
g[i][2][2][1]=g[i-1][2][2][1];
g[i][1][1][2]=(g[i-1][1][2][2]+g[i-1][2][1][2]+g[i-1][1][1][2]+g[i-1][2][2][1])%mod;
g[i][2][2][2]=(g[i-1][1][2][2]+g[i-1][2][1][2]+g[i-1][1][1][1]+g[i-1][2][2][2])%mod;
g[i][1][2][2]=(g[i-1][1][1][1]+g[i-1][2][2][1]+g[i-1][1][2][2])%mod;
g[i][2][1][2]=(g[i-1][1][1][1]+g[i-1][2][2][1]+g[i-1][2][1][2])%mod;
for(int t=3;t<=2*i;t++){
g[i][1][1][t]=(g[i-1][1][2][t]+g[i-1][2][1][t]+g[i-1][1][1][t]+g[i-1][2][2][t-1])%mod;
g[i][1][2][t]=(g[i-1][1][1][t-1]+g[i-1][1][2][t]+g[i-1][2][1][t-2]+g[i-1][2][2][t-1])%mod;
g[i][2][1][t]=(g[i-1][1][1][t-1]+g[i-1][1][2][t-2]+g[i-1][2][1][t]+g[i-1][2][2][t-1])%mod;
g[i][2][2][t]=(g[i-1][1][2][t]+g[i-1][2][1][t]+g[i-1][2][2][t]+g[i-1][1][1][t-1])%mod;
}
}
printf("%lld\n",(g[n][1][1][k]+g[n][1][2][k]+g[n][2][1][k]+g[n][2][2][k])%mod);
return 0;
}
B. 于老
一棵无根树,共有 \(2\) x \(k\) 个叶子节点,所有节点均可染成红色或黑色。
定义树的价值为两端点颜色相同的边数。求恰好 \(k\) 个叶子节点染成黑色、余下 \(k\) 个叶子节点染成红色的最大价值。
\(std\) 代码如下:
#include<bits/stdc++.h>
#define N 5005
#define inf 1000000000
using namespace std;
vector<int> e[N];
int f[N][N][2],in[N],sz[N],tmp[N][2];
bool vis[N];
void dfs(int u,int fa){
for(int i=0;i<N;i++)f[u][i][0]=f[u][i][1]=inf;
if(vis[u]){
sz[u]=1;
f[u][1][1]=0;
f[u][0][0]=0;
return;
}
f[u][0][1]=0;
f[u][0][0]=0;
sz[u]=1;
for(int v:e[u]){
if(v==fa)continue;
dfs(v,u);
for(int i=0;i<=sz[u]+sz[v];i++)tmp[i][0]=tmp[i][1]=inf;
for(int k=0;k<=sz[u];k++){
for(int j=0;j<=sz[v];j++){
tmp[k+j][0]=min(tmp[k+j][0],f[u][k][0]+f[v][j][0]);
tmp[k+j][0]=min(tmp[k+j][0],f[u][k][0]+f[v][j][1]+1);
tmp[k+j][1]=min(tmp[k+j][1],f[u][k][1]+f[v][j][1]);
tmp[k+j][1]=min(tmp[k+j][1],f[u][k][1]+f[v][j][0]+1);
}
}
sz[u]+=sz[v];
for(int i=0;i<=sz[u];i++)f[u][i][0]=tmp[i][0],f[u][i][1]=tmp[i][1];
}
}
int main(){
freopen("B.in","r",stdin);
freopen("B.out","w",stdout);
int n;
cin>>n;
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
e[u].push_back(v);
e[v].push_back(u);
in[u]++,in[v]++;
}
int cnt=0;
for(int i=1;i<=n;i++){
if(in[i]==1){
vis[i]=1;
cnt++;
}
}
int root=1;
while(vis[root])root++;
if(root>n)root=1;
dfs(root,0);
if(n==2)cout<<"0";
else cout<<n-1-min(f[root][cnt/2][0],f[root][cnt/2][1]);
return 0;
}
C. 牛来
\(n\) 堆草,第 \(i\) 堆重 \(a_i\)。每次操作可以选择一堆两边都不为空的草堆 \(i\),吃掉并将两边的草堆分别加上 \(a_i\)。
求最终剩下两堆草的最小总重量。
-
首先联想到典中典 · 石子合并,容易知道区间 \(dp\);
-
对于区间 \(i\) ~ \(j\),每次枚举一个断点 \(k\),\(dfs\) 的求解左右两个区间 \(i\) ~ \(k-1\) 和 \(k+1\) ~ \(j\),返回两端点的可能结果组合(因为重量和最小也有可能有不同的组合情况),用 \(vector\) 之类的东西记录,枚举两边区间的搭配,更新最小值;
-
对于上述的两端点的可能结果组合,在存的时候就把一些显然不可能创造出最终结果的搭配优化掉。又因为 \(dfs\) 会有之前搜过的情况,所以记忆化一下即可;
D. 牛不来
有 \(m\) 条弦,一条弦可以抽象为一个二元组 \((u,v)\),你可以进行任意次切割操作,一次切割操作你将选择两个下标 \(i\) 和 \(j\) 满足 \(i,j \in [1,n]\),然后所有满足 \(u>i,v<j\) 的弦 \((u,v)\) 都将被破坏,同时你将付出 \(a_i \times b_j\) 的代价。求破坏所有弦的最小代价和。
-
显然可以将 \((u,v)\) 看成二维平面上的点,然后切割弦的操作可以看成是一个点 \((i,j)\),将它右下的部分所覆盖的点全部删掉;
-
然后显然只有最外围的点有用,内部的点都是滚木;
-
对于剩下来的似凸包又不似凸包的东西,可以考虑设 \(dp[i]\) 表示当前处理到第 \(i\) 个点,且前 \(1\) ~ \(i\) 的点都已经被覆盖并删除。那么可以枚举前一个点 \(j\),\(dp[i]=dp[j]+solve(i)\);
然后据讲题人所述,\(solve(i)\) 就是斜率优化的板子。
E. 数学思维
一个 \(0\) ~ \(m\) 的数轴,其中有 \(n\) 个摊子,每个摊子产出同人都需要 \(t\) 的时间。也就是说第一次抵达某个摊子后,过 \(t\) 时刻再回来,就可以得到一份同人作品。
现有一人从 \(0\) 开始走,需要在 \(m\) 处离开,求获得所有同人的最短时间。
省流:我不会。
只能参考这个了(悲)。

浙公网安备 33010602011771号