洛谷P3943 星空
题面:
题解:
很好的思维题,考察数学抽象和问题转化能力。
考虑到区间最长为$40000$,若暴力翻转,一次复杂度为$O(n)$,显然不可承受,考虑将区间操作转化为单点操作,所以我们可想到差分,因为一次翻转为取$xor$,所以我们定义差分为$b[i]=a[i]\ xor\ a[i+1]$。
差分后数组变为一个$01$串,包含不超过$2k$个零。
问题转化(转化一)为:给定一个$01$串,有不超过$2k$个零,每次操作时,从$m$种距离中选一个,把序列上相距为该距离的两个数取反,问使整个串为$0$的最少操作数。
观察该问题,我们可能会有两种操作,
1.一个$1$,一个$0$,相当于移动。
2.两个$0$,相当于消去。
这样,我们可以继续把问题转化(转化二)为:一张图,有$2k$个物品在不同起点,每次操作时,从$m$种距离中选一个,把该物品移动,当两物品相遇时,它们消去,求最少操作数。
我们发现,图上一共有$n$个节点,每个点有$m$条边,于是我们可以用$spfa$预处理出两两间的最短路,复杂度为$O(nmk)$。
考虑继续转化(转化三)为:2k个物品,选择其中两个可以消去,分别有不同的代价。
显然可用状压处理,但注意要写复杂度为$O(k*2^{2k})$的,防止被卡。附上大佬博客。
code:
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<algorithm> using namespace std; #define R register #define ll long long inline ll read(){ ll aa=0;R int bb=1;char cc=getchar(); while(cc<'0'||cc>'9') {if(cc=='-')bb=-1;cc=getchar();} while(cc>='0'&&cc<='9') {aa=(aa<<1)+(aa<<3)+(cc^48);cc=getchar();} return aa*bb; } const int N=40003; const int M=18; int n,k,m,a[N],b[N],tt,pos[M],dp[1<<M]; int ds[N],dis[M][M]; queue<int>qi; inline void spfa(int st) { memset(ds,0,sizeof(ds)); int x=pos[st];ds[x]=1;qi.push(x); while(!qi.empty()){ int u=qi.front();qi.pop(); for(R int i=1;i<=m;++i){ if(u+b[i]<=n&&!ds[u+b[i]]){ ds[u+b[i]]=ds[u]+1; qi.push(u+b[i]); } if(u-b[i]>=0&&!ds[u-b[i]]){ ds[u-b[i]]=ds[u]+1; qi.push(u-b[i]); } } } for(R int i=1;i<=tt;++i)dis[st][i]=ds[pos[i]]-1; } int main() { n=read();k=read();m=read(); for(R int i=1,x;i<=k;++i){x=read();a[x]^=1;} for(R int i=0;i<=n;++i){ a[i]^=a[i+1]; if(a[i])pos[++tt]=i; } for(R int i=1;i<=m;++i)b[i]=read(); for(R int i=1;i<=tt;++i)spfa(i); const int lim=(1<<tt)-1; memset(dp,0x3f,sizeof(dp)); dp[0]=0; for(R int i=0;i<lim;++i) for(R int j=1;j<=tt;++j){ if((1<<(j-1))&i)continue; for(R int q=j+1,x;q<=tt;++q){ if(((1<<(q-1))&i)||dis[j][q]==-1)continue; x=(i|(1<<(q-1))|(1<<(j-1))); dp[x]=min(dp[x],dp[i]+dis[j][q]); } break;//保证复杂度,只需处理第一个位置,防止重复。 } printf("%d\n",dp[lim]); return 0; }