区间dp,树形dp,状压dp 总结
A.Sue 的小球
Description
在平面直角坐标系上给你
Solution
因为可以在瞬间收集小球,所以当经过小球
设
因为我们要将所有的小球都收集到,所以我们可以统计一下所有小球的下落速度前缀和,转移的时候只需要加上所有未被收集的小球的下坠速度之和乘转移的时间即可。
Code
#include<bits/stdc++.h>
#define N 1005
using namespace std;
int n,xs,f1[N][N],f2[N][N],ans,s[N];
struct node{
int x,y,v;
}a[N];
int cmp(node a,node b){return a.x<b.x;}
signed main(){
cin>>n>>xs;
for(int i=1;i<=n;i++)cin>>a[i].x;
for(int i=1;i<=n;i++)cin>>a[i].y;
for(int i=1;i<=n;i++)cin>>a[i].v;
a[++n].x=xs;
sort(a+1,a+1+n,cmp);
memset(f1,0x3f,sizeof f1);
memset(f2,0x3f,sizeof f2);
for(int i=1;i<=n;i++){
ans+=a[i].y;
if(a[i].x==xs&&a[i].y==0&&a[i].v==0)f1[i][i]=f2[i][i]=0;
s[i]=s[i-1]+a[i].v;
}
for(int len=2;len<=n;len++){
for(int i=1;i<=n-len+1;i++){
int j=i+len-1;
f1[i][j]=min(f1[i][j],f1[i+1][j]+(a[i+1].x-a[i].x)*(s[n]-s[j]+s[i]));
f1[i][j]=min(f1[i][j],f2[i+1][j]+(a[j].x-a[i].x)*(s[n]-s[j]+s[i]));
f2[i][j]=min(f2[i][j],f1[i][j-1]+(a[j].x-a[i].x)*(s[n]-s[j-1]+s[i-1]));
f2[i][j]=min(f2[i][j],f2[i][j-1]+(a[j].x-a[j-1].x)*(s[n]-s[j-1]+s[i-1]));
}
}
printf("%.3lf",(ans-min(f1[1][n],f2[1][n]))/1000.0);
return 0;
}
B.Replace on Segment
Description
给你一个数组,你每次可以选择一个区间,并将这个区间内的所有数字变成一个不属于这个区间内的数,求将整个区间变为同一个数的最小步数。
Solution
很明显的区间 DP。用两个数组
转移方程为:
Code
#include<bits/stdc++.h>
#define N 105
using namespace std;
int T,n,x,a[N],f[N][N][N],g[N][N][N],ans;
signed main(){
cin>>T;
while(T--){
cin>>n>>x;
memset(f,0x3f,sizeof f);
memset(g,0x3f,sizeof g);
for(int i=1;i<=n;i++){
cin>>a[i];
for(int j=1;j<=x;j++){
f[i][i][j]=(j!=a[i]);
g[i][i][j]=(j==a[i]);
}
}
for(int len=1;len<=n;len++){
for(int i=1;i<=n-len+1;i++){
int j=i+len-1,mn=0x3f3f3f3f;
for(int k=1;k<=x;k++){
for(int m=i;m<j;m++)g[i][j][k]=min(g[i][j][k],g[i][m][k]+g[m+1][j][k]);
mn=min(mn,g[i][j][k]);
}
for(int k=1;k<=x;k++)g[i][j][k]=min(g[i][j][k],mn+1);
for(int k=1;k<=x;k++){
for(int m=i;m<j;m++)f[i][j][k]=min(f[i][j][k],f[i][m][k]+f[m+1][j][k]);
f[i][j][k]=min(f[i][j][k],g[i][j][k]+1);
}
}
}
ans=0x3f3f3f3f;
for(int i=1;i<=x;i++)ans=min(ans,f[1][n][i]);
cout<<ans<<"\n";
}
return 0;
}
C.Candles
Description
给你
Solution
因为熄灭蜡烛不需要时间,所以只要经过蜡烛
设
但我们发现
我们给
那么
- 当前要熄灭的蜡烛已经灭了,也就是直接由
转移。 - 当前要熄灭的蜡烛还在燃烧,而就是从
转移。
转移方程如下:
最终答案为
Code
#include<bits/stdc++.h>
#define int long long
#define N 305
using namespace std;
int n,f1[N][N][N],f2[N][N][N],ans;
struct node{int x,l;}a[N];
int cmp(node a,node b){return a.x<b.x;}
signed main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i].x>>a[i].l;
if(abs(a[i].x)>=a[i].l)a[i].l=0;
}
++n;
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)for(int k=0;k<=n;k++)f1[i][j][k]=f2[i][j][k]=-0x3f3f3f3f3f3f3f3f;
for(int i=1;i<=n;i++){
if(!a[i].x&&!a[i].l){
for(int k=0;k<n;k++)f1[i][i][k]=f2[i][i][k]=0;
break;
}
}
for(int len=2;len<=n;len++){
for(int i=1;i<=n-len+1;i++){
int j=i+len-1;
for(int k=0;k<=n-len;k++){
f1[i][j][k]=max(f1[i][j][k],f1[i+1][j][k]-k*(a[i+1].x-a[i].x));//i 已经熄灭
f1[i][j][k]=max(f1[i][j][k],f2[i+1][j][k]-k*(a[j].x-a[i].x));
f1[i][j][k]=max(f1[i][j][k],a[i].l+f1[i+1][j][k+1]-(k+1)*(a[i+1].x-a[i].x));//i 还未熄灭
f1[i][j][k]=max(f1[i][j][k],a[i].l+f2[i+1][j][k+1]-(k+1)*(a[j].x-a[i].x));
//
f2[i][j][k]=max(f2[i][j][k],f2[i][j-1][k]-k*(a[j].x-a[j-1].x));//j 已经熄灭
f2[i][j][k]=max(f2[i][j][k],f1[i][j-1][k]-k*(a[j].x-a[i].x));
f2[i][j][k]=max(f2[i][j][k],a[j].l+f2[i][j-1][k+1]-(k+1)*(a[j].x-a[j-1].x));//j 还未熄灭
f2[i][j][k]=max(f2[i][j][k],a[j].l+f1[i][j-1][k+1]-(k+1)*(a[j].x-a[i].x));
}
}
}
cout<<max(f1[1][n][0],f2[1][n][0]);
return 0;
}
D.Vertex Deletion
Description
给定一棵树,其中有
- 把
和连接 的边全部删除后得到的图的最大匹配与原树的最大匹配相等。
Solution
我们可以先跑一遍 DP,求出整棵树的最大匹配,然后通过换根的方式来判断每一个
设
然后换根转移即可。
Code
#include<bits/stdc++.h>
#define N 200005
#define int long long
using namespace std;
int n,vis[N],dp[N][2],cnt,ans,sum[N],num[N],snm[N],nid[N],son[N];
vector<int>g[N];
void dfs(int x){
num[x]=snm[x]=-0x3f3f3f3f;
if(g[x].size()>1||x==1)dp[x][1]=1;
if(g[x].size()==1&&x!=1)num[x]=0;
for(int y:g[x]){
if(vis[y])continue;
vis[y]=1;
++son[x];
dfs(y);
dp[x][0]+=dp[y][1];
dp[x][1]+=dp[y][1];
if(dp[y][0]-dp[y][1]>num[x]){
snm[x]=num[x],nid[x]=y;
num[x]=dp[y][0]-dp[y][1];
}
else if(dp[y][0]-dp[y][1]>snm[x])snm[x]=dp[y][0]-dp[y][1];
}
dp[x][1]+=num[x];
}
void dfs1(int x){
for(int y:g[x]){
if(vis[y])continue;
vis[y]=1;
sum[x]+=max(dp[y][0],dp[y][1]);
int a=dp[x][0],b=dp[x][1];
--son[x],++son[y];
dp[x][0]-=max(dp[y][0],dp[y][1]);
dp[x][1]-=dp[y][1]+(!son[x]);
if(nid[x]==y){
dp[x][1]-=num[x];
if(son[x])dp[x][1]+=snm[x];
}
sum[y]+=max(dp[x][0],dp[x][1]);
dp[y][0]+=max(dp[x][0],dp[x][1]);
dp[y][1]+=dp[x][1]+(son[y]==1);
int nw=num[y];
if(dp[x][0]-dp[x][1]>num[y]){
snm[y]=num[y],nid[y]=x;
num[y]=dp[x][0]-dp[x][1];
}
else if(dp[x][0]-dp[x][1]>snm[y])snm[y]=dp[x][0]-dp[x][1];
dp[y][1]+=num[y]-nw;
dfs1(y);
++son[x],--son[y];
dp[x][0]=a,dp[x][1]=b;
}
if(sum[x]==cnt)++ans;
}
signed main(){
cin>>n;
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
vis[1]=1;
dfs(1);
cnt=max(dp[1][0],dp[1][1]);
memset(vis,0,sizeof vis);
vis[1]=1;
dfs1(1);
cout<<ans;
return 0;
}
E.Perils in Parallel
Description
有
Solution
因为题目要求的是改变电灯状态,所以可以很容易想到用异或来表示。我们可以通过异或差分数组来保存。最后要求的是全部为零,即差分数组中的所有元素都应为
我们想到每次修改
但是最后建成的可能不是以棵棵的树,所以我们需要通过生成树来保证 DP 可以跑下去。
Code
#include<bits/stdc++.h>
#define N 200005
using namespace std;
int n,m,d[N],vis[N],f[N];
vector<pair<int,int> >g[N];
vector<int>ans;
struct node{
int a,b;
}t[N];
int cmp(node a,node b){
return a.a<b.a;
}
int findf(int x){
if(x==f[x])return x;
return f[x]=findf(f[x]);
}
int findl(int x){
int l=1,r=n,res=1;
while(l<=r){
int mid=(l+r)>>1;
if(t[mid].a<x)l=mid+1;
else r=mid-1,res=mid;
}
return res;
}
int findr(int x){
int l=1,r=n,res=n;
while(l<=r){
int mid=(l+r)>>1;
if(t[mid].a>x)r=mid-1;
else l=mid+1,res=mid;
}
return res;
}
void dfs(int x){
for(auto p:g[x]){
int y=p.first;
if(vis[y])continue;
vis[y]=1;
dfs(y);
if(d[y])ans.push_back(p.second),d[y]=0,d[x]^=1;
}
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>t[i].a>>t[i].b;
sort(t+1,t+1+n,cmp);
for(int i=1;i<=n+1;i++)d[i]=(t[i].b^t[i-1].b),f[i]=i;
for(int i=1;i<=m;i++){
int l,r;
cin>>l>>r;
l=findl(l),r=findr(r);
if(l>r)continue;
int ln=findf(l),rn=findf(r+1);
if(ln==rn)continue;
f[ln]=findf(rn);
g[l].push_back({r+1,i});
g[r+1].push_back({l,i});
}
for(int i=1;i<=n+1;i++){
if(!vis[i]){
vis[i]=1;
dfs(i);
if(d[i]&&i<=n){
cout<<"-1";
return 0;
}
}
}
sort(ans.begin(),ans.end());
cout<<ans.size()<<"\n";
for(int out:ans)cout<<out<<" ";
return 0;
}
F.Optimal Path Decomposition
Description
给定一个
Solution
个人认为这是最难的一道。首先这题一眼看上去就是二分,这样我们就将求最小值问题表示成了求可行性问题。因为只能是同一条路径上的颜色相同,所以某个节点最多只能与两个子节点颜色相同。二分
转移方程很麻烦,具体看代码。
Code
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define int long long
#define N 200005
using namespace std;
int n,dp[N][3],vis[N],ans;
vector<int>g[N];
void dfs(int x,int k){
dp[x][0]=dp[x][1]=dp[x][2]=0;
for(int y:g[x]){
if(vis[y])continue;
int a0=inf,a1=inf,a2=inf;
vis[y]=1;
dfs(y,k);
if(dp[x][0]+dp[y][1]<k)a1=min(a1,max(dp[x][0],dp[y][1]));
if(dp[x][1]+dp[y][1]<k)a2=min(a2,max(dp[x][1],dp[y][1]));
if(dp[x][0]+dp[y][2]+1<k)a0=min(a0,max(dp[x][0],dp[y][2]+1));
if(dp[x][1]+dp[y][2]+1<k)a1=min(a1,max(dp[x][1],dp[y][2]+1));
if(dp[x][2]+dp[y][2]+1<k)a2=min(a2,max(dp[x][2],dp[y][2]+1));
dp[x][0]=a0,dp[x][1]=a1,dp[x][2]=a2;
dp[x][1]=min(dp[x][1],dp[x][0]),dp[x][2]=min(dp[x][2],dp[x][1]);
}
}
int check(int mid){
memset(vis,0,sizeof vis);
vis[1]=1;
dfs(1,mid);
if(dp[1][2]<=mid)return 1;
return 0;
}
signed main(){
cin>>n;
for(int i=1;i<n;i++){
int a,b;
cin>>a>>b;
g[a].push_back(b);
g[b].push_back(a);
}
int l=1,r=n;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid))r=mid-1,ans=mid;
else l=mid+1;
}
cout<<ans;
return 0;
}
G.Cut and Reorder
Description
给你长为
- 将
加上 ,代价为 。 - 将
数组划分为任意 个子段,并将其随意排列,代价为 。
求将
Solution
看到
其中
Code
#include<bits/stdc++.h>
#define N 22
#define lowbit(x) (x&(-x))
#define ll long long
using namespace std;
int n;
ll a[N+1],b[N+1],c,dp[1<<N];
signed main(){
cin>>n>>c;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)cin>>b[i];
memset(dp,0x3f,sizeof dp);
dp[0]=-c;
for(int s=0;s<(1<<n);s++){
int cnt=0;
for(int t=s;t;t-=lowbit(t))++cnt;
for(int i=1;i<=n;i++){
if(s&(1<<(i-1)))continue;
int t=s;
ll num=c;
for(int j=i;!(s&(1<<(j-1)))&&j<=n;j++){
t|=(1<<(j-1));
num+=abs(a[j]-b[cnt+j-i+1]);
dp[t]=min(dp[t],dp[s]+num);
}
}
}
cout<<dp[(1<<n)-1];
return 0;
}
E.E or m
Description
开始你有一个
- 选择任意一行,将其从最左端开始的连续一段染成 1。
- 选择任意一列,将其从最上端开始的连续一段染成 1。
如果一个矩阵可以由此得到,那么这个矩阵被称为好的。
现在你有一个 01? 矩阵
Solution
我们看到
Code
#include<bits/stdc++.h>
#define N 18
#define mod 998244353
using namespace std;
int n,m,dp[N+1][N+1][1<<N][2],ans;
char c[N+1][N+1];
signed main(){
cin>>n>>m;
for(int i=1;i<=m;i++)c[0][i]='1';
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>c[i][j];
}
}
for(int i=2;i<=n;i++){
for(int j=2;j<=m;j++){
if(c[i][j]=='1'&&c[i-1][j]=='0'&&c[i][j-1]=='0'){
cout<<0;
return 0;
}
}
}
if(c[1][1]!='0')dp[1][1][(1<<m)-1][1]=1;
if(c[1][1]!='1')dp[1][1][(1<<m)-1][0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(i==n&&j==m)break;
for(int s=0;s<(1<<m);s++){
for(int k=0;k<2;k++){
int x=i,y=j+1;
if(y>m)x=i+1,y=1;
if(c[x][y]!='1')(dp[x][y][s&(~(1<<(y-1)))][0]+=dp[i][j][s][k])%=mod;
if(c[x][y]!='0'&&(k||(s&(1<<(y-1)))||y==1)){
if(y>1)(dp[x][y][s][k]+=dp[i][j][s][k])%=mod;
else (dp[x][y][s][1]+=dp[i][j][s][k])%=mod;
}
}
}
}
}
for(int s=0;s<(1<<m);s++){
(ans+=(dp[n][m][s][0]+dp[n][m][s][1])%mod)%=mod;
}
cout<<ans;
return 0;
}
I. Vowels
Description
给出n个长度为3的由′a′~′x′组成的单词,一个单词是正确的当且仅当其包含至少一个元音字母。 这里的元音字母是a-x的一个子集。 对于所有元音字母集合,求这n个单词中正确单词的数量平方的异或和。
Solution
看题解之前觉得自己是个不会做,看了题解第一句话后觉得还觉得自己是个连这题都不会做。
题解的第一句话是:正难则反。
什么意思呢?因为只有 24 个字母,所以肯定是状压。我们原来关注的是选择
转移的时候需要用到这样一个东西:高维前缀和。我们发现如果直接用
for(int i=0;i<n;i++){
for(int s=0;s<(1<<n);s++){
if(s&(1<<i))dp[s]+=dp[s^(1<<i)];
}
}
我们发现他是先枚举位数,再枚举状态,为什么是这样呢? 我们发现这个时候转移的
010 -> 011
100 -> 101
110 -> 111
001 -> 011
100 -> 110
101 -> 111
001 -> 101
010 -> 110
011 -> 111
我们关注 111,最后转移到他身上的就相当于是完整的 001 和 110,不存在容斥的问题。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,dp[1<<24],ans;
string str;
signed main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>str;
int s=((1<<(str[0]-'a'))|(1<<(str[1]-'a'))|(1<<(str[2]-'a')));
dp[s]++;
}
for(int i=0;i<24;i++){
for(int s=0;s<(1<<24);s++){
if(s&(1<<i))(dp[s]+=dp[s^(1<<i)]);
}
}
for(int s=0;s<(1<<24);s++)ans^=(n-dp[s])*(n-dp[s]);
cout<<ans;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】