动态规划专题 - 解题报告
https://acm.uestc.edu.cn/contest/15/summary/?tdsourcetag=s_pctim_aiomsg
dp专题要刷完!!
A - oy环游世界 - 解题报告
状态压缩dp入门题
注意要开long long
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll dp[1<<18][18];
ll e[20][20];
ll x[20],y[20];
const ll INF=1e18;
int main()
{
int n,s;
scanf("%d%d",&n,&s);
int tot=0;
for(int i=1; i<=n; i++)
{
ll a,b;
scanf("%lld%lld",&a,&b);
if(s==i)
{
x[n-1]=a;
y[n-1]=b;
}
else
{
x[tot]=a;
y[tot]=b;
tot++;
}
}
for(int i=0; i<tot; i++)
for(int j=0; j<tot; j++)
{
e[i][j]=abs(x[i]-x[j])+abs(y[i]-y[j]);
}
for(int s=0;s<(1<<tot);s++)
for(int i=0;i<tot;i++)
dp[s][i]=INF;
for(int i=0;i<tot;i++)
dp[1<<i][i]=abs(x[i]-x[n-1])+abs(y[i]-y[n-1]);
for(int s=0;s<(1<<tot);s++)
for(int i=0;i<tot;i++)
for(int j=0;j<tot;j++)
{
if(i!=j&&((1<<i)&s)&&(((1<<j)&s)==0))
{
dp[s|1<<j][j]=min(dp[s|1<<j][j],dp[s][i]+e[i][j]);
}
}
ll ans=INF;
for(int i=0;i<tot;i++)
{
ans=min(ans,dp[(1<<tot)-1][i]);
// printf("%d\n",dp[(1<<tot)-1][i]);
}
printf("%lld",ans);
}
B - 挖矿攻略 - 解题报告
棋盘dp问题
日哦,没说矿道不能拐弯?只能直西直北?浪费时间!
#include<bits/stdc++.h>
using namespace std;
int a[505][505];
int b[505][505];
int xi[505][505];
int bei[505][505];
int f[505][505];
int main()
{
int n,m;
while(scanf("%d%d",&n,&m)&&n)
{
memset(f,0,sizeof(f));
memset(xi,0,sizeof(xi));
memset(bei,0,sizeof(bei));
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
{
scanf("%d",&a[i][j]);
xi[i][j]+=xi[i][j-1]+a[i][j];
}
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
{
scanf("%d",&b[i][j]);
bei[i][j]+=bei[i-1][j]+b[i][j];
}
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
f[i][j]=max(xi[i][j]+f[i-1][j],bei[i][j]+f[i][j-1]);
printf("%d\n",f[n][m]);
}
}
C - 手办 - 解题报告
四位数组DP好题,找了好久BUG,这道题要优化,时间空间上都需要。
思路:状态f[i][j][k][l],考虑了i个物品,取了j次,状压当前书架上剩余的高度状态k,此时书架最后一本书高度j,的最小混乱度。
状态转移:
a[i]==l,一定不取,f[i][j][k][l]=min(f[i][j][k][l],f[i-1][j][k][l])
a[i]!=l,取第i个物品f[i][j+1][k][l]=f[i-1][j][k][l],不取的话f[i][j][新的k][a[i]]=f[i-1][j][k][a[i]]+1
之后枚举i,j,k,l得到的混乱度加上该状态下未出现的高度,求最小值。
#include<bits/stdc++.h> using namespace std; const int INF=0x3f3f3f3f; int f[2][105][1<<8+1][9];// 如果第一维105会达到1e9超内存,需要滚动数组优化 int a[105]; int v[10]; int num[1<<9+10]; int init(int x) { int ret = 0; while(x) { if(x&1) ret++; x >>= 1; } return ret; } int main() { int it=0; int n,m,now; for (int i = 0; i <= 1<<8; i++) num[i] = init(i); while(~scanf("%d%d",&n,&m)&&n+m) { int tot=0; // tot记录所有出现的高度个数 int all=0; // all这里用来记录出现的最大状态 memset(v,0,sizeof(v)); for(int i=1; i<=n; i++) { scanf("%d",&a[i]); a[i]-=114514; all|=(1<<a[i]); if(v[a[i]]==0) { tot++; v[a[i]]=1; } } memset(f[0],INF,sizeof(f[0])); f[0][0][0][8]=0; for(int i=1; i<=n; i++) { now=i%2; memset(f[now],INF,sizeof(f[now])); for(int j=0; j<=min(m,i); j++) for(int s=0; s<=all; s++) for(int k=0; k<=8; k++) { if(f[!now][j][s][k]==INF) continue; if(k==a[i]) f[now][j][s][k]=min(f[now][j][s][k],f[!now][j][s][k]); else { f[now][j][s|(1<<a[i])][a[i]]=min(f[now][j][s|(1<<a[i])][a[i]],f[!now][j][s][k]+1); f[now][j+1][s][k]=min(f[now][j+1][s][k],f[!now][j][s][k]); } } } int ans=INF; for(int j=0; j<=m; j++) for(int s=0; s<=all; s++) for(int k=0; k<=8; k++) { if(f[now][j][s][k]!=INF) ans=min(ans,f[now][j][s][k]+tot-num[s]); // 所有出现的高度tot-状态含的num[s]就是未出现的高度 } printf("Case %d: %d\n\n",++it,ans); } }
D - 序列 - 解题报告
最长上升路径nlogn做法
正反求一次到每个数字且末尾数字为该数字的最长上升子序列,然后枚举每个端点作为中间值。注意!初始化box为负无穷,注意数据范围
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
#define ll long long
int lt[maxn],rt[maxn];
int box[maxn];
int a[maxn];
int main()
{
int n;
scanf("%d",&n);
for(int i=1; i<=n; i++)
scanf("%d",&a[i]);
int len=0;
box[0]=-1e9-10;
for(int i=1; i<=n; i++)
{
if(a[i]>box[len])
{
box[++len]=a[i];
lt[i]=len;
}
else
{
int pos=lower_bound(box+1,box+1+len,a[i])-box;
box[pos]=a[i];
lt[i]=pos;
}
}
len=0;
memset(box,0,sizeof(box));
box[0]=-1e9-10;
for(int i=n; i>=1; i--)
{
if(a[i]>box[len])
{
box[++len]=a[i];
rt[i]=len;
}
else
{
int pos=lower_bound(box+1,box+1+len,a[i])-box;
box[pos]=a[i];
rt[i]=pos;
}
}
ll ans=0;
for(int i=1; i<=n; i++)
ans=max(ans,1ll*min(lt[i],rt[i])*2-1);
printf("%lld",ans);
}
E - 神 - 解题报告
线性DP,设f[i][j]表示,到第i块中j的位置作为最后一个字符的最小划分数。
状态转移:枚举i块所有位置j作为最后一个位置,再枚举i-1块中所有位置k作为最后一个位置,如果i-1块中字母在i中出现,同时满足s[j]!=s[k]或者如果s[j]==s[k]&&cnt=1,cnt是i块中字母种类数,那么这个i的块数产生的划分数可以为cnt-1,否则只能是cnt。
#include<bits/stdc++.h> using namespace std; int f[1005][1005]; char s[1005]; int v[30]; int main() { int t; scanf("%d",&t); while(t--) { int k; scanf("%d%s",&k,s+1); int len=strlen(s+1); int duan=len/k; int cnt=0; memset(v,0,sizeof(v)); for(int i=1; i<=k; i++) { if(v[s[i]-'a'+1]==0) cnt++; v[s[i]-'a'+1]=1; } memset(f,0x3f,sizeof(f)); for(int i=1; i<=k; i++) f[1][i]=cnt; for(int i=2; i<=duan; i++) { memset(v,0,sizeof(v)); cnt=0; for(int j=(i-1)*k+1; j<=i*k; j++) { if(v[s[j]-'a'+1]==0) cnt++; v[s[j]-'a'+1]=1; } for(int j=1; j<=k; j++) { for(int kk=1; kk<=k; kk++) { int jt=(i-1)*k+j; int jk=(i-2)*k+kk; if(v[s[jk]-'a'+1]==1&&(cnt==1||s[jt]!=s[jk])) f[i][j]=min(f[i][j],f[i-1][kk]+cnt-1); else f[i][j]=min(f[i][j],f[i-1][kk]+cnt); } } // for(int j=1;j<=k;j++) // printf("%d ",f[i][j]); // printf("\n"); } int ans=0x3f3f3f3f; for(int i=1;i<=k;i++) ans=min(ans,f[duan][i]); printf("%d\n",ans); } }
F - 苇名欧一郎 - 解题报告
状压dp,做完这道题感觉对状压的DP又加深了理解。
思路:f[1<<16]状压杀死的人,与A题类似,每个状态下枚举可以杀的敌人,如果可以f[t]+=f[s]s为目前状态,t为加了这个可以杀的人的状态。
#include<bits/stdc++.h> using namespace std; #define ll long long ll f[1<<16]; char a[20][20]; int main() { int t,n; scanf("%d",&t); for(int it=1; it<=t; it++) { scanf("%d",&n); scanf("%s",a[n]); for(int i=0; i<=n-1; i++) scanf("%s",a[i]); memset(f,0,sizeof(f)); f[0]=1; for(int s=0; s<1<<n; s++) { for(int i=0; i<n; i++) { if((s&(1<<i))==0) { int flag=0; if(a[n][i]=='1') flag=1; for(int j=0; j<n; j++) { if(s&(1<<j)&&a[j][i]=='1') flag=1; if(flag) break; } if(flag) f[s|1<<i]+=f[s]; } } } printf("Case %d: %lld\n",it,f[(1<<n)-1]); } }
G - 子串 - 解题报告
这道题需要降维或者滚动数组优化,我选择了滚动数组,这样比较好理解好写hh
思路:设f[i][j],以第i位为最低位的字符串%k余j的个数。这里需要注意只能从高位往低位滚动,这样余数*10就是加上新的数的余数,而如果从低位往高位就搞不定了呀。
#include<bits/stdc++.h> using namespace std; const int maxn=2e7+10; #define ll long long ll f[2][maxn]; char a[maxn]; int main() { int n,k; scanf("%d%d",&n,&k); scanf("%s",a+1); int now; ll ans=0; for(int i=1; i<=n; i++) { now=i%2; for(int j=0;j<=k;j++) f[now][j]=0; int t=(a[i]-'0'); for(int j=0; j<=k; j++) { int tt=(t+j*10)%k; f[now][tt]+=f[!now][j]; } t=t%k; f[now][t]++; ans+=f[now][0]; } printf("%lld",ans); }
H - van游戏 - 解题报告
I - 攻略妹纸 - 解题报告
变形的01背包问题
思路:一开始可以想到,可以枚举自身的上限魅力值,也就是给妹子的最大花费,之后就是一个01背包O(N3),但可以想到如果我们给妹子所需最大魅力值从大到小排序,以每个妹子的所需魅力值作为最大魅力值花费的上限来枚举,同时这样排过序之后,不同魅力值之间的f[][]是不会相互覆盖的,可以优化为O(N2)。
#include<bits/stdc++.h> using namespace std; const int maxn=6005; #define ll long long ll f[maxn]; ll n,m,k,x,y,ans; ll a,b,c,d; struct note { ll val,cost,goal; } g[maxn]; int cmp(note a,note b) { return a.goal>b.goal; } int main() { scanf("%lld%lld%lld%lld%lld",&n,&m,&k,&x,&y); // x氪自己 y氪妹子 for(int i=1; i<=n; i++) { scanf("%lld%lld%lld%lld",&a,&b,&c,&d); g[i].val=a; // 你对她好感a,她对你好感b,c妹子对你好感要求c,你魅力要求d if(k<d) g[i].goal=m-(d-k)*x; else g[i].goal=m; if(c>b) g[i].cost=(c-b)*y; else g[i].cost=0; } sort(g+1,g+1+n,cmp); for(int i=1; i<=n; i++) { for(int j=g[i].goal; j>=g[i].cost; j--) f[j]=max(f[j],f[j-g[i].cost]+g[i].val); ans=max(ans,f[g[i].goal]); } printf("%lld",ans); }
K - 抽卡 - 解题报告
L - zh的奖杯 - 解题报告
M - 崭新龙狙,制霸全区 - 解题报告