[BZOJ3508] 开灯 [状态压缩][dp][bfs][异或][差分]

[ L i n k \frak{Link} Link]
[重题(稍微有点不一样)]

首先要注意到操作的本质是区间异或。哦。
到达目标状态也可以看作把目标状态的灯全部熄灭。哦。。
哦哦。。。。。。咋做啊??
起始状态→目标状态,代价最小。。。?最短路?

区间异或显然可以用前缀异或或者异或差分来代替。
同时注意到状态不成环、所以可以最短路的话说不定也可以 d p \frak{dp} dp

虽然提到了异或,但是这道题前缀异或没什么用。
“前缀和”没有用,是不是考虑一下差分啊?

异或差分
这个名词也许有点陌生,不过又非常熟悉。异或差分,就是用异或来进行差分。
需要对原序列 A \frak{A} A建立一个“异或差分序列“ D \frak{D} D,其中 D i = A i ∧ A i − 1 \frak{D_i=A_i\land A_{i-1}} Di=AiAi1
为什么可以用在 D \frak{D} D上面修改来代替在 A \frak{A} A上面区间修改呢?
比如说,在 A \frak{A} A上面把 [ a , b ] \frak{[a,b]} [a,b]取反
那么 [ a , b ] \frak{[a,b]} [a,b]里面的元素相对关系不变
D \frak{D} D里面的表现就是只有 D a \frak{D_{a}} Da D b + 1 \frak{D_{b+1}} Db+1改变了。
所以可以用改变 D a \frak{D_a} Da D b + 1 \frak{D_{b+1}} Db+1来代替区间修改。可以类比普通差分。
p s . \frak{ps.} ps.异或差分这种说法好像只在这道题里面有听到过 说实在的懵了我一比(((

那么,本题要求我们做到的就是——把序列里面所有的 1 \frak{1} 1变成 0 \frak{0} 0
实际上由于是对区间操作,用几次操作应该会消除掉某一段区间的 1 \frak{1} 1
在差分数组里面表现为少了两个 1 \frak{1} 1。并且消除一对 1 \frak{1} 1的代价与距离直接相关。
所以先求消除距离为 x \frak{x} x的一对 1 \frak{1} 1的代价,这个可以跑最短路,由于边权为 1 \frak{1} 1实际上就是 b f s \frak{bfs} bfs
也可以用完全背包解。每个长度拆成-x和x两种代价的物品,价值均为1
(完全背包貌似不对?可能是在处理操作长度大于距离的时候会出错?不是很确定)
(总之找到的完全背包写法都会被hack掉)

这样就可以愉悦地状压 d p \frak{dp} dp了。在异或差分序列里面 1 \frak{1} 1的部分上状压。
另外有一个优化:因为最后都是要全部消除的,所以每次虽然是选两个 1 \frak{1} 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} noip联赛模拟赛级别的题都想了这么久 i q − 1 \frak{iq-1} iq1
感觉离 a f o \frak{afo} afo不远了(

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值