首先要注意到操作的本质是区间异或。哦。
到达目标状态也可以看作把目标状态的灯全部熄灭。哦。。
哦哦。。。。。。咋做啊??
起始状态→目标状态,代价最小。。。?最短路?
区间异或显然可以用前缀异或或者异或差分来代替。
同时注意到状态不成环、所以可以最短路的话说不定也可以
然后呢,这道题前缀异或没什么用,模拟一下就发现了
所以就要用异或差分了。
异或差分?
这个名词也许有点陌生,不过又非常熟悉。异或差分,就是用异或来进行差分。
需要对原序列 建立一个“异或差分序列“ ,其中
为什么可以用在 上面修改来代替在 上面区间修改呢?
比如说,在 上面把 取反
那么 里面的元素相对关系不变
在 里面的表现就是只有 和 改变了。
所以可以用改变 和 来代替区间修改。可以类比普通差分。
异或差分这种说法好像只在这道题里面有听到过 说实在的懵了我一比(((
那么,本题要求我们做到的就是——把序列里面所有的
变成
。
实际上由于是对区间操作,用几次操作应该会消除掉某一段区间的
在差分数组里面表现为少了两个
。并且消除一对
的代价于距离直接相关。
所以先求消除距离为
的一对
的代价,这个可以跑最短路,由于边权为
实际上就是
也可以用完全背包解。每个长度拆成-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跑一般图最小权匹配。
快乐开花
联赛模拟赛级别的题都想了这么久
感觉离
不远了(