[BZOJ3508] 開燈 [狀態壓縮][dp][bfs][異或][差分]
首先要注意到操作的本質是區間異或。哦。
到達目標狀態也可以看作把目標狀態的燈全部熄滅。哦。。
起始狀態→目標狀態,代價最小。。。?最短路?
區間異或顯然可以用字首異或或者異或差分來代替。
同時注意到狀態不成環、所以可以最短路的話說不定也可以
然後呢,這道題字首異或沒什麼用,模擬一下就發現了
所以就要用異或差分了。
異或差分?
這個名詞也許有點陌生,不過又非常熟悉。異或差分,就是用異或來進行差分。
需要對原序列 建立一個“異或差分序列“ ,其中
為什麼可以用在 上面修改來代替在 上面區間修改呢?
比如說,在 上面把 取反
那麼 裡面的元素相對關係不變
在 裡面的表現就是隻有 和 改變了。
所以可以用改變 和 來代替區間修改。可以類比普通差分。
異或差分這種說法好像只在這道題裡面有聽到過 說實在的懵了我一比(((
那麼,本題要求我們做到的就是——把序列裡面所有的
變成
。
實際上由於是對區間操作,用幾次操作應該會消除掉某一段區間的
在差分數組裡面表現為少了兩個
。並且消除一對
的代價於距離直接相關。
所以先求消除距離為
的一對
的代價,這個可以跑最短路,由於邊權為
實際上就是
也可以用完全揹包解。每個長度拆成-x和x兩種代價的物品,價值均為1
(完全揹包貌似不對?可能是在處理操作長度大於距離的時候會出錯?不是很確定)
(總之找到的完全揹包寫法都會被hack掉)
總之這樣就可以愉悅地狀壓
了。
具體過程就是在異或差分序列裡面為
的部分上狀壓。
另外有一個優化,因為最後都是要全部消除的,所以每次雖然是選兩個
,但是並沒有必要都列舉。
列舉一個就好了,另外一個選lowbit。這樣正好能夠得到答案。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<cstdlib>
using namespace std;
int n,k,m;
int T,Lim;
int a[25]={};
int pos[25]={};
int cost[25][25]={};
int F[1111111]={};
int opt[105]={};
int dis[10005]={};
bool ext[10005]={};
queue<int>Q;
void bfs(int S)
{
for(int i=1;i<=n;++i)dis[i]=0x3f3f3f3f;
dis[pos[S]]=0; Q.push(pos[S]);
while(!Q.empty())
{
int x=Q.front(); Q.pop();
for(int t,i=1;i<=m;++i)
{
t=x+opt[i];
if(t<=n&&dis[t]>dis[x]+1)dis[t]=dis[x]+1,Q.push(t);
t=x-opt[i];
if(t>=1&&dis[t]>dis[x]+1)dis[t]=dis[x]+1,Q.push(t);
}
}
for(int i=1;i<=pos[0];++i)if(i!=S)cost[S][i]=dis[pos[i]];
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
memset(ext,0,sizeof(ext));
scanf("%d%d%d",&n,&k,&m); ++n;
for(int i=1;i<=k;++i)scanf("%d",&a[i]),ext[a[i]]=1;
for(int i=1;i<=m;++i)scanf("%d",&opt[i]);
pos[0]=0;
for(int i=1;i<=n;++i)if(ext[i]^ext[i-1])pos[++pos[0]]=i;
if(pos[0]&1){printf("-1\n");continue;}
memset(cost,0x3f,sizeof(cost));
for(int i=1;i<=pos[0];i++)bfs(i);
memset(F,0x3f,sizeof(F));
F[0]=0;
Lim=(1<<pos[0])-1;
for(int v,j,i=0;i<Lim;++i)
{
if(F[i]>=0x3f3f3f3f)continue;
for(j=1;j<=pos[0];++j)if(!(i&(1<<j-1)))break;
for(int k=j;k<=pos[0];++k)
{
if(i&(1<<k-1))continue;
v=i|(1<<k-1)|(1<<j-1);
F[v]=min(F[v],F[i]+cost[j][k]);
}
}
if(F[Lim]>=0x3f3f3f3f)printf("-1\n");
else printf("%d\n",F[Lim]);
}
return 0;
}
資料生成器(沒有進行判重)
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<sstream>
#include<cmath>
#include<cstring>
#include<queue>
using namespace std;
#define ll long long
ll GenRand(const ll Lim1,ll Lim2)
{
ll ret=rand()<<15|rand();
while(ret<Lim1)ret+=Lim1+(20+Lim2>>2);
ret%=Lim2;
if(ret<Lim1)ret=Lim1+rand()%(Lim2-Lim1);
return ret;
}
stringstream ss;
int main(int argc,char**argv)
{
int seed=time(NULL);
if(argc>1)
{
ss.clear();
ss<<argv[1];
ss>>seed;
}
srand(seed);
int m,n,k,l,t;
m=GenRand(1,10); printf("%d\n",m);
while(m--)
{
n=GenRand(1,40000); k=GenRand(1,10); l=GenRand(1,100);
printf("%d %d %d\n",n,k,l);
while(k--)
{
t=GenRand(1,n);
printf("%d ",t);
}
printf("\n");
while(l--)
{
t=GenRand(1,n);
printf("%d ",t);
}
printf("\n");
}
return 0;
}
一組叉掉完全揹包的資料
1
191 1 7
42
119 48 159 27 79 88 161
完全揹包輸出:3
bfs輸出:5
本題也可以跑最短路跑出1互相匹配的權值,然後用帶花樹開幾個fafa跑一般圖最小權匹配。
快樂開花
聯賽模擬賽級別的題都想了這麼久
感覺離
不遠了(