状压DP例题
P2831愤怒的小鸟
首先记录抛物线的方案。根据题意可知,两个点可能会确定一条符合题设的抛物线。所以
接下来是状压DP。设
bool function(double x,double y){
double fx=a*x*x+b*x-y;
fx=fx>0?fx:-fx;
return fx<=exp;
}
void sol(){
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;++j){
if(p[i].x!=p[j].x){
a=(p[i].x/p[j].x*p[j].y-p[i].y)/((p[j].x-p[i].x)*p[i].x);
b=(p[i].y-p[i].x*p[i].x*a)/p[i].x;
if(a>=0)
continue;
s[++tot]|=(1<<(i-1));s[tot]|=(1<<(j-1));
for(int k=1;k<=n;++k)
if(k!=i&&k!=j&&function(p[k].x,p[k].y))
s[tot]|=(1<<(k-1));
}
}
for(int i=1;i<=n;++i)
s[++tot]|=(1<<(i-1));
}
int main(){
scanf("%d",&t);
while(t--){
for(int i=1;i<=tot;++i)
s[i]=0;
tot=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%lf%lf",&p[i].x,&p[i].y);
sol();
for(int i=0;i<(1<<n);++i)
f[i]=1e9;
f[0]=0;
for(int i=0;i<(1<<n);++i){
for(int j=1;j<=tot;++j){
f[i|s[j]]=min(f[i|s[j]],f[i]+1);
}
}
//for(int i=1;i<=(1<<n);++i)cout<<f[i]<<" ";
printf("%d\n",f[(1<<n)-1]);
}
return 0;
}
P2157学校食堂
设
如果第
如果第
需要注意的是
初始化:
答案:
void solve(){
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d%d",&t[i],&b[i]);
for(int i=0;i<=n+1;++i)
for(int j=0;j<=(1<<8);++j)
for(int k=-8;k<=7;++k)
f[i][j][k+8]=1e9;
f[1][0][7]=0;
for(int i=1;i<=n;++i)
for(int j=0;j<(1<<8);++j)
for(int k=-8;k<=7;++k)
if(f[i][j][k+8]!=1e9){
if(j&1)
f[i+1][j>>1][k+7]=min(f[i+1][j>>1][k+7],f[i][j][k+8]);
else{
int tmp=1e9;
for(int h=0;h<=7;++h)
if(!((j>>h)&1)){
if(i+h>tmp)
break;
tmp=min(tmp,i+h+b[i+h]);
f[i][j|(1<<h)][h+8]=min(f[i][j|(1<<h)][h+8],f[i][j][k+8]+(i+k?(t[i+k]^t[i+h]):0));
}
}
}
ans=1e9;
for(int k=0;k<=8;++k)
ans=min(ans,f[n+1][0][k]);
printf("%d\n",ans);
return;
}
P7519 [省选联考 2021 A/B 卷] 滚榜
首先想到,答案是最终排名的方案数,与
若
设
代码实现时注意分数相等时,位置小的靠前。
int main(){
scanf("%lld%lld",&n,&m);
a[0]=-1;
for(int i=1;i<=n;++i){
scanf("%lld",&a[i]);
if(a[maxn]<a[i])
maxn=i;
}
for(int i=1;i<=n;++i){//一开始的节点要成为第一,所以要比最大值大
long long sum=n*(a[maxn]-a[i]);
if(maxn<i)
sum+=n;
if(sum<=m)
f[1<<(i-1)][i][sum]=1;
}
for(int i=0;i<(1<<n);++i){
int tmp=i,cnt=0,num=1;
while(tmp){
if(tmp&1) b[++cnt]=num;
++num;tmp>>=1;
}
for(int j=1;j<=cnt;++j){
int x=b[j];
if(i&(1<<(x-1))){
for(int k=1;k<=cnt;++k){
int y=b[k];
if(j!=k&&(i&(1<<(y-1)))){
int sum=(a[y]-a[x])*(n-cnt+1);
if(y<x) sum+=n-cnt+1;
if(a[y]<a[x]) sum=0;
for(int l=m;l>=sum;--l)
f[i][x][l]+=f[i-(1<<(x-1))][y][l-sum];//cout<<f[i][x][l]<<" "<<i-(1<<(x-1))<<" "<<y<<" "<<l-sum<<endl;
}
}
}
}
}
for(int i=1;i<=n;++i)
for(int j=0;j<=m;++j)
ans+=f[(1<<n)-1][i][j];
printf("%lld\n",ans);
return 0;
}
P2150 [NOI2015] 寿司晚宴
因为两个人选的数字全部互质,所以甲选的数字的质因数集合和乙选的数字的质因数集合没有交集
设
状态转移方程:
然而,
我们发现,一个小于
设
注意这里要减去
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
long long n,m,tot,p[1005],dp[1005][1005],f2[1005][1005],f1[1005][1005],ans;
struct node{
int w,big;
} a[1005];
bool ok[1005];
bool cmp(node a,node b){
return a.big<b.big;
}
int main(){
scanf("%lld%lld",&n,&m);
const long long mod=m;
for(int i=2;i<=n;++i)
if(!ok[i]){
p[++tot]=i;
for(int j=i*2;j<=n;j+=i)
ok[j]=1;
}
for(int i=1;i<n;++i){
for(int j=1;j<=tot;++j){
if(p[j]<=22&&(i+1)%p[j]==0)
a[i].w|=1<<(j-1);
if(p[j]>22&&(i+1)%p[j]==0)
a[i].big=p[j];
}
}
sort(a+1,a+n,cmp);
dp[0][0]=1;
for(int i=1;i<n;++i){
if(a[i].big==0||i==n-1||a[i].big!=a[i-1].big){
memcpy(f1,dp,sizeof(f1));
memcpy(f2,dp,sizeof(f2));
}
for(int j=255;j>=0;--j)
for(int k=255;k>=0;--k)
if((j&k)==0){
if((a[i].w&j)==0)
f2[j][a[i].w|k]=(f2[j][a[i].w|k]+f2[j][k])%mod;
if((a[i].w&k)==0)
f1[j|a[i].w][k]=(f1[j|a[i].w][k]+f1[j][k])%mod;
}
if(i==n-1||a[i].big!=a[i+1].big||a[i].big==0){
for(int j=0;j<=255;++j)
for(int k=0;k<=255;++k)
if((j&k)==0)
dp[j][k]=((f1[j][k]+f2[j][k])-dp[j][k]+mod)%mod;
}
}
for(int i=0;i<=255;++i)
for(int j=0;j<=255;++j)
ans=(ans+dp[i][j])%mod;
printf("%lld\n",ans);
return 0;
}
P3451 [POI2007] ATR-Tourist Attractions
如果没有64MB的限制,那么这就成了一个状压水题。设
那么加上空间限制怎么办呢?可以考虑滚动数组。设
void pre_work(){
for(int i=1;i<=k+2;++i)
dij(i);//求i到所有点的最短路(特别地,k+2表示n)
for(int i=0;i<(1<<k);++i){
int tmp=i,len=0;
while(tmp){
if(tmp&1) ++len;
tmp>>=1;
}
sta[++cnt[len]][len]=i;
sta2[i]=cnt[len];
}
}
int main(){
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;++i){
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);add(v,u,w);
}
scanf("%d",&g);
for(int i=1;i<=g;++i){
scanf("%d%d",&u,&v);
++in[v];add2(u,v);
}
pre_work();
for(int i=0;i<k;++i){
clear(i+1);//清空数组
for(int j=1;j<=cnt[i];++j){
int tmp=sta[j][i],len=0,num=1;
for(int t=2;t<=k+1;++t)
in2[t]=in[t],vis[t]=0;
while(tmp){
if(tmp&1)
b[++len]=num+1,vis[num+1]=1;
tmp>>=1;++num;
}
if(!sta[j][i])
b[len=1]=1;
for(int t=1;t<=len;++t)
for(int p=head2[b[t]];p;p=nxt2[p])
--in2[ver2[p]];
for(int t=2;t<=k+1;++t)
if(in2[t]==0&&!vis[t]){
int y=sta2[sta[j][i]+(1<<(t-2))];
for(int p=1;p<=len;++p)
f[(i+1)&1][y][t]=min(f[(i+1)&1][y][t],f[i&1][j][b[p]]+dis[b[p]][t]);
}
}
}
ans=inf;
for(int i=1;i<=k+1;++i)
ans=min(ans,f[k&1][1][i]+dis[k+2][i]);//别忘了最后走到n
printf("%d\n",ans);
return 0;
}
P1777 帮助
因为序列的值域很小,所以考虑状压DP。
设
最后答案是
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!