Soratosorato

一道状压

Sorato·2023-09-09 01:02·14 次阅读

一道状压

题目大意#

给定两个长度为 n(n16) 的数组 ab,可以进行若干次如下操作:

  • 选择两个数 i,j(1i,jn),将(aibi)与(ajbj)交换。

问最少进行多少次上述操作,可使得对于每个 i[1,n],都有 |aibi|c

问题转化#

首先我们用状态压缩的思想,用状态的第 i 位表示 是(1)否(0)有 |aibi|c

我们可以把一个个状态都看作点,如果两个状态之间可以通过调换牙刷互相得到,那么这两个点间就有一条边。

和 在边权相同的图上可以直接用 BFS 求单源最短路 同样的道理,这样在建出的图上跑一遍 BFS 即可,起点是初始状态。

但是到达每个点时的a 数组和 b 数组会互不相同,所以我们a1[i][j]b1[i][j] 记录到达点 i(状态为 i)时,第 j 把牙刷的两个属性值,这样的话就可以根据 a1b1 判断两个点之间是否能互相到达。

我们并不需要真正地去建图,真正去建图的话很麻烦。可以在 BFS 的过程中,对于当前(队头)的状态,枚举交换哪两个,然后让交换后的状态入队即可。

DP 求解#

至于 BFS 中的 DP 部分(求最短路,但还是更像状压 DP 一点),状态转移方程如下:

mask 为当前状态,flagi 表示 是(1)否(0)有 |aibi|cdpi 表示到达状态 i 至少要交换几次。

现在对 (i,j) 进行交换操作

  • 若交换后 flagi 发生改变,而 flagj 未发生改变,则 dp[mask^(1<<i)]=dp[mask]+1

  • 若交换后 flagj 发生改变,而 flagi 未发生改变,则 dp[mask^(1<<j)]=dp[mask]+1

  • 若交换后 flagjflagj 均发生改变,则 dp[mask^(1<<i)^(1<<j)]=dp[mask]+1

那么如何判断交换后 flagiflagj 是否发生变化呢?

比如交换 aiaj,若(abs(a1[mask][i]-b1[mask][i])<=c)^(abs(a1[mask][j]-b1[mask][i])<=c)==1,则 flagi 发生变化。同样的,若(abs(a1[mask][j]-b1[mask][j])<=c)^(abs(a1[mask][i]-b1[mask][j])<=c)==1,则 flagj 发生变化。

那么如果交换 aibj,若(abs(a1[mask][i]-b1[mask][i])<=c)^(abs(b1[mask][j]-b1[mask][i])<=c)==1,则 flagi 发生变化。同样的,若(abs(a1[mask][j]-b1[mask][j])<=c)^(abs(a1[mask][j]-a1[mask][i])<=c)==1,则 flagj 发生变化。

不难发现,讨论以上两种情况其实就足够了,没必要再用 aj 去交换。

举个例子,ai=1,bi=2,aj=3,bj=4。那么上面的第一种实质上是让 1,4 一组,2,3 一组。第二种是让 1,3 一组,2,4 一组。此时就已经包含了所有的情况了,不需要让 2 去和 34 交换。

代码如下:

Copy
#include<bits/stdc++.h> #pragma GCC optimize(1) #pragma GCC optimize(2) #pragma GCC optimize(3,"Ofast","inline") #define reg register using namespace std; inline int read() { int x=0; short f=1; char c=getchar(); while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();} while(c>='0'&&c<='9') {x=(x<<1)+(x<<3)+(c^48);c=getchar();} return x*f; } int n,c,dp[1<<20],a[20],b[20],a1[1<<20][20],b1[1<<20][20],mask; bool vis[1<<20]; inline void memcopy(int x) {for(reg int i=0;i<n;i=-~i) a1[x][i]=a1[mask][i],b1[x][i]=b1[mask][i];} inline void DP() { queue<int>q; q.push(mask); while(!q.empty()&&!dp[(1<<n)-1])//小优化,如果dp[(1<<n)-1]已被求出就直接结束 { mask=q.front(); q.pop(); for(reg int i=0;i<n;i=-~i/*位运算的i++*/) for(reg int j=0;j<i;j=-~j)//i和j 枚举交换哪两个 { int nxt1=mask^(1<<j),nxt2=mask^(1<<i),nxt3=mask^(1<<i)^(1<<j); if((abs(a1[mask][i]-b1[mask][i])<=c)^(abs(a1[mask][j]-b1[mask][i])<=c))//flag[i]发生变化 { if((abs(a1[mask][j]-b1[mask][j])<=c)^(abs(a1[mask][i]-b1[mask][j])<=c))//flag[j]发生变化 { if(!vis[nxt3]) { dp[nxt3]=dp[mask]+1;vis[nxt3]=1;q.push(nxt3); memcopy(nxt3);swap(a1[nxt3][i],a1[nxt3][j]); //flag[i]和flag[j]均发生变化 } } else if(!vis[nxt2]) { dp[nxt2]=dp[mask]+1;vis[nxt2]=1;q.push(nxt2); memcopy(nxt2);swap(a1[nxt2][i],a1[nxt2][j]); //仅flag[i]发生变化 } } else if((abs(a1[mask][j]-b1[mask][j])<=c)^(abs(a1[mask][i]-b1[mask][j])<=c)&&!vis[nxt1])//flag[j]发生变化 { dp[nxt1]=dp[mask]+1;vis[nxt1]=1;q.push(nxt1); memcopy(nxt1);swap(a1[nxt1][i],a1[nxt1][j]); //仅flag[j]发生变化 } //1.exchange a[i] and a[j] if((abs(a1[mask][i]-b1[mask][i])<=c)^(abs(b1[mask][j]-b1[mask][i])<=c)) { if((abs(a1[mask][j]-b1[mask][j])<=c)^(abs(a1[mask][j]-a1[mask][i])<=c)) { if(!vis[nxt3]) { dp[nxt3]=dp[mask]+1;vis[nxt3]=1;q.push(nxt3); memcopy(nxt3);swap(a1[nxt3][i],b1[nxt3][j]); } } else if(!vis[nxt2]) { dp[nxt2]=dp[mask]+1;vis[nxt2]=1;q.push(nxt2); memcopy(nxt2);swap(a1[nxt2][i],b1[nxt2][j]); } } else if((abs(a1[mask][j]-b1[mask][j])<=c)^(abs(a1[mask][j]-a1[mask][i])<=c)&&!vis[nxt1]) { dp[nxt1]=dp[mask]+1;vis[nxt1]=1;q.push(nxt1); memcopy(nxt1);swap(a1[nxt1][i],b1[nxt1][j]); } //2.exchange a[i] and b[j] } } } signed main() { n=read();c=read(); for(reg int i=0;i<n;i=-~i) { a[i]=read();b[i]=read(); if(abs(a[i]-b[i])<=c) mask|=(1<<i); } dp[mask]=0;vis[mask]=1; for(reg int i=0;i<n;i=-~i) a1[mask][i]=a[i],b1[mask][i]=b[i]; DP(); return printf("%d",dp[(1<<n)-1]),0; }

时间复杂度#

BFS:2n

枚举交换哪两个(i=0n1,j=0i1:1+2+...+(n1)=n(n1)/2

将原来的所对应的属性值数组 赋值给 交换后的所对应的属性值数组 :n

总复杂度 :O(2n(n(n1)/2+n)),化简得 O(2n1n(n+1)),约 O(2n1n2),完全能过。

但实际复杂度是达不到的,因为几乎不会出现 每个状态都能到达 的情况,而且我们还有小优化。

posted @   Sorato  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示
目录