1. 程式人生 > >[BZOJ3508] 開燈 [狀態壓縮][dp][bfs][異或][差分]

[BZOJ3508] 開燈 [狀態壓縮][dp][bfs][異或][差分]

[ L i n k \frak{Link} ]
[重題(稍微有點不一樣)]

首先要注意到操作的本質是區間異或。哦。
到達目標狀態也可以看作把目標狀態的燈全部熄滅。哦。。


哦哦。。。。。。咋做啊??
起始狀態→目標狀態,代價最小。。。?最短路?

區間異或顯然可以用字首異或或者異或差分來代替。
同時注意到狀態不成環、所以可以最短路的話說不定也可以 d p \frak{dp}

然後呢,這道題字首異或沒什麼用,模擬一下就發現了
所以就要用異或差分了。

異或差分
這個名詞也許有點陌生,不過又非常熟悉。異或差分,就是用異或來進行差分。
需要對原序列 A

\frak{A} 建立一個“異或差分序列“ D \frak{D} ,其中 D i
= A i A i 1 \frak{D_i=A_i\land A_{i-1}}

為什麼可以用在 D \frak{D} 上面修改來代替在 A \frak{A} 上面區間修改呢?
比如說,在 A \frak{A} 上面把 [ a , b ] \frak{[a,b]} 取反
那麼 [ a , b ] \frak{[a,b]} 裡面的元素相對關係不變
D \frak{D} 裡面的表現就是隻有 D a \frak{D_{a}} D b + 1 \frak{D_{b+1}} 改變了。
所以可以用改變 D a \frak{D_a} D b + 1 \frak{D_{b+1}} 來代替區間修改。可以類比普通差分。
p s . \frak{ps.} 異或差分這種說法好像只在這道題裡面有聽到過 說實在的懵了我一比(((

那麼,本題要求我們做到的就是——把序列裡面所有的 1 \frak{1} 變成 0 \frak{0}
實際上由於是對區間操作,用幾次操作應該會消除掉某一段區間的 1 \frak{1}
在差分數組裡面表現為少了兩個 1 \frak{1} 。並且消除一對 1 \frak{1} 的代價於距離直接相關。
所以先求消除距離為 x \frak{x} 的一對 1 \frak{1} 的代價,這個可以跑最短路,由於邊權為 1 \frak{1} 實際上就是 b f s \frak{bfs}
也可以用完全揹包解。每個長度拆成-x和x兩種代價的物品,價值均為1
(完全揹包貌似不對?可能是在處理操作長度大於距離的時候會出錯?不是很確定)
(總之找到的完全揹包寫法都會被hack掉)
總之這樣就可以愉悅地狀壓 d p \frak{dp} 了。
具體過程就是在異或差分序列裡面 1 \frak{1} 的部分上狀壓。
另外有一個優化,因為最後都是要全部消除的,所以每次雖然是選兩個 1 \frak{1} ,但是並沒有必要都列舉。
列舉一個就好了,另外一個選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跑一般圖最小權匹配。
快樂開花

n o i p \frak{noip} 聯賽模擬賽級別的題都想了這麼久 i q 1 \frak{iq-1}
感覺離 a f o \frak{afo} 不遠了(