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=0,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 - 子串 - 解题报告
H - van游戏 - 解题报告
I - 攻略妹纸 - 解题报告
K - 抽卡 - 解题报告
L - zh的奖杯 - 解题报告
M - 崭新龙狙,制霸全区 - 解题报告