https://acm.uestc.edu.cn/contest/15/summary/?tdsourcetag=s_pctim_aiomsg
dp专题要刷完!!
A - oy环游世界 - 解题报告
状态压缩dp入门题
注意要开long long
![](https://i-blog.csdnimg.cn/blog_migrate/0931e18071dc3c7066e95f25f14da087.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/78f3b678cd30cd24d9a2c5722ec0002b.gif)
#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问题
日哦,没说矿道不能拐弯?只能直西直北?浪费时间!
![](https://i-blog.csdnimg.cn/blog_migrate/c2fdcb4e3dad4d3b6a77d9332c582efa.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/b84f984cfc2b9303a81420efd6a05091.gif)
#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得到的混乱度加上该状态下未出现的高度,求最小值。
![](https://i-blog.csdnimg.cn/blog_migrate/9f9022db663bded39f9f9d88515c01d0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/54c0a06dc91025405bc2e8780f60cd26.gif)
#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为负无穷,注意数据范围
![](https://i-blog.csdnimg.cn/blog_migrate/2100ee0472cc6811990bbf31f68672e0.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/fb7bf8f2f587dc73da65aa4531a4a3d2.gif)
#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。
![](https://i-blog.csdnimg.cn/blog_migrate/236b9ef231d321b121aa6a44e9d00f25.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/cd984cce1a6217a39bf35280470cdb56.gif)
#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为加了这个可以杀的人的状态。
![](https://i-blog.csdnimg.cn/blog_migrate/7364f47018a81c46f485bcbb148e128d.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/c21d42310b081eb3cc6a2d4decd45f3e.gif)
#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就是加上新的数的余数,而如果从低位往高位就搞不定了呀。
![](https://i-blog.csdnimg.cn/blog_migrate/23198d1f4d500a1d832aeb421c2f7d59.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/f56e605f6d0d3e4d542b79423e0dbb26.gif)
#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)。
![](https://i-blog.csdnimg.cn/blog_migrate/17c3a5df33b00d49115c29c3692a9751.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/c8d8d3c010fa6d6ec64d1bed1572d3f4.gif)
#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);
}