星空[好题][题意转化]

题目大意:给你一个0/1串,让你对区间亦或(有特定几个长度限制操作),最后让你求出把这个0/1串变成全0串的最小操作数;

前置问题:

首先发现对原序列区间整体亦或很不好控制,因为会不断出现新的1;

那我们怎么办?

想想差分;

与普通线性数组差分一样,若原序列有初始值,则需要把原序列转化为差分序列;

具体求法即为:b[i]=a[i]-a[i-1];(b数组为差分数组,a为原数组)

同理亦或差分:b[i]=a[i]^a[i-1];

于是我们可以把原序列转化成为差分序列;

那我们为什么要把元序列转化为差分序列呢?

{

  1.考虑区间修改,元序列有0与1相互转化,并会蹦出新的1;

  2.而差分数组只对两端进行修改;只有0/1的单次转化;

  3.最重要的性质,差分数组与原数组的最终状态一直,都是全0串;(若 原序列为000000000 ,亦或差分序列不也得是00000000吗)

}

一-------于是成功转化题意;

在原序列的差分序列上进行如下操作:

1.把可把1在序列上向左移动或向右移动几种固定长度,当1与1想碰时变为0;

2.求最终把这个序列变为全0串的最小操作数;

eg.差分序列:000110001100   区间修改就是选区间的左右端点(不严谨),然后对两个端点亦或,这不就是把左端点1亦或成0,而右端点0亦或成1吗;其实就是把1移动......

那这样就很好做了;

既然有固定的长度;那就把长度看成边;

把差分序列上的每个点都和他能一次操作到达的点连边;而边权就是1;一部操作吗.......

二-------于是就形成了一个图;

而我们要知道的就是1之间互相到达的代价;在图中转化为距离;

那就跑dijistrla呗;只不过得跑2k次;

但没关系啊;NlogN*2k完全可以;

一波dijistrla后,我们求出了dis[i][j] 代表的是差分序列上 第i个1 到 第j个1的 代价;

三-------于是问题又转化了:

你有2k个物品,两两能以一定代价互相匹配,求把这2k个物品分为k对的最小代价;

而k<=8;2k<=16;

状压呗;O((k^2)*(2^2k))可以卡过;

最终状态就是(1<<2k)-1;

附上自带常数的代码

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define rd(x) scanf("%lld",&x)
 4 #define int long long
 5 int a[400050],b[400050],edge[1000],pos[100],id[400050];
 6 int N,K,M;
 7 int dis[100][100];
 8 int f[1<<20|1],g[100][100];
 9 vector<int>to[400500];
10 
11 struct node{
12     int to,dis;
13     node(int a,int b){to=a;dis=b;}
14     friend bool operator<(node a,node b){
15         return a.dis>b.dis;
16     }
17 };
18 
19 bool v[40050];
20 int d[40050];
21 void diji(int st){
22     priority_queue<node>q;
23     memset(d,0x3f,sizeof(d));memset(v,0,sizeof(v));
24     d[st]=0;q.push(node(st,d[st]));
25     register int u,w;
26     while(q.size()){
27         u=q.top().to;w=q.top().dis;q.pop();
28         if(v[u])continue;
29         v[u]=1;
30         for(register int i=0,y;i<to[u].size();++i){
31             y=to[u][i];
32             if(v[y])continue;
33             if(d[u]+1<d[y]){
34                 d[y]=d[u]+1;
35                 q.push(node(y,d[y]));
36             }
37         }
38     }
39     for(int i=1;i<=pos[0];++i)
40     if(id[i]!=st){
41         dis[id[st]][i]=dis[i][id[st]]=d[pos[i]];
42     }
43 }
44 
45 signed main(){
46     rd(N);rd(K);rd(M);
47     for(register int i=1,p;i<=K;++i){
48         rd(p);a[p]=1;
49     }
50     for(register int i=1;i<=M;++i){
51         rd(edge[i]);
52     }
53     for(register int i=1;i<=N;++i){
54         for(register int j=1;j<=M;++j){
55             if(i+edge[j]>N+1)break;
56             to[i].push_back(i+edge[j]);
57             to[i+edge[j]].push_back(i);
58         }
59     }
60     for(register int i=1;i<=N+1;++i){
61         b[i]=a[i]^a[i-1];
62         if(b[i])pos[++pos[0]]=i,id[i]=pos[0];
63     }
64     for(register int i=1;i<=pos[0]-1;++i)diji(pos[i]);
65     /*for(int i=1;i<=pos[0];++i){
66         for(int j=1;j<=pos[0];++j)cout<<dis[i][j]<<" ";
67         cout<<endl;
68     }*/
69     for(register int i=1;i<=pos[0];++i)
70         for(register int j=1;j<=pos[0];++j)
71             if(i!=j)g[i][j]=(1<<i-1)|(1<<j-1);
72     memset(f,0x3f,sizeof(f));register int Z=(1<<pos[0])-1;
73     f[0]=0;
74     for(register int i=0;i<=Z;++i)
75     {
76         if(i==Z)break;
77         for(register int j=1;j<=pos[0];++j)
78         if((i&(1<<j-1))==0){
79             for(register int k=j+1;k<=pos[0];++k)
80             if((i&(1<<k-1))==0){
81                 f[i|g[j][k]]=min(f[i|g[j][k]],f[i]+dis[j][k]);
82             }
83         }
84 
85     }
86     printf("%lld\n",f[Z]);
87 }
自带常数爆炸

 

posted @ 2019-08-11 16:37  Hzoi_whs  阅读(155)  评论(0编辑  收藏  举报