Codeforces Round 901 (Div. 2) E
有些部分和常规的题目有很大的区别,所以我理解的过程产生的很大很大的障碍。我看了4天吧,这题和题解。
好烦。
我的第一个思路就是暴力。因为很明显,其实对于每一个二进制位,a,b,m的情况数量是很有限的,就只有8种,而相应的,c,d的对应位是由这4种位运算得到的。我先尝试对每一种情况看看几次位运算能够得到所有的c,d的可能的情况。
经过我的dfs,发现好像最多也就4种。那也就是,假如我对abm进行这四种操作尝试得到目标的cd,可能只需要4次。
事实上这个就是一个猜的结论,会wa再第三个点。
然后你加大dfs的深度就会T在第三个点。
正解其实就是上面说的思路的衍生。既然abm的情况数量有限,甚至总共就只有8种,而每次操作都是对整个数字的,也就是我们可以合并所有初始相同的abm的位置,因为不管怎么操作,他们得到的答案都是一样的。
其实我们是要把ab的对应位置改变成cd的样子,那就是假设cd初始为0,然后一步一步变,看看是否能够变成我们需要的样子。
abm共计8种情况,目标cd共计5种,有一种是无要求的情况,那我们的状态其实就可以状压保存了,abm8个位置,各保存对应位置的cd,5种情况,共计就是\(5^8\)个不同的状态,变成点来看就这么多,\(4e5\)的级别。
然后我们就从初始的点,也就是cd和ab相等的情况,尝试向外扩展,看看能够从ab的初始得到cd的状态。
这个过程直接预处理,然后答案就可以直接输出了,\(O(5^8+TlogV)\)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read() {
char c=getchar();int a=0,b=1;
for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
for(;c>='0'&&c<='9';c=getchar())a=a*10+c-48;return a*b;
}
struct edge
{
int next,to;
}e[1200001];
int head[1200001],tot,cnt;
inline void add(int i,int j)
{
e[++tot].next=head[i];
e[tot].to=j;
head[i]=tot;
}
int pw[9],f[1200001];
inline int Worka(int a,int b,int m)
{
return (a<<2)+(b<<1)+m;
}
inline int Workb(int c,int d)
{
return (c<<1)+d;
}
inline int initial(int now,int i)
{
return (now/pw[i])%5;
}
int turn(int now,int opt)
{
int ans=0;
for(int i=0;i<2;i++)
{
for(int j=0;j<2;j++)
{
for(int k=0;k<2;k++)
{
int tmp=initial(now,Worka(i,j,k));
int c=tmp>>1,d=tmp&1;
if(opt==1)c&=d;
if(opt==2)c|=d;
if(opt==3)d=c^d;
if(opt==4)d=d^k;
ans+=pw[Worka(i,j,k)]*Workb(c,d);
}
}
}
return ans;
}
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
pw[0]=1;
for(int i=1;i<=8;i++)
pw[i]=pw[i-1]*5;
queue<int> q;
int S=0;
for(int i=0;i<=1;i++)
{
for(int j=0;j<=1;j++)
{
for(int k=0;k<=1;k++)
{
S+=pw[Worka(i,j,k)]*Workb(i,j);
}
}
}
memset(f,0x3f,sizeof(f));
q.push(S);f[S]=0;
while(!q.empty())
{
int x=q.front();q.pop();
for(int i=1;i<=4;i++)
{
int now=turn(x,i);
if(f[now]==1061109567)f[now]=f[x]+1,q.push(now);
}
}
//and there can be some case that has no requirment for this position
for(int now=0;now<pw[8];now++)
for(int i=0;i<8;i++)
{
if(initial(now,i)==4)
{
for(int j=0;j<4;j++)
{
f[now]=min(f[now],f[now-pw[i]*4+pw[i]*j]);
}
}
}
int T=read();
while(T--)
{
int a=read(),b=read(),c=read(),d=read(),m=read();
int Mark=pw[8]-1;bool flag=0;
for(int i=0;i<30;i++)
{
int A=(a>>i)&1,B=(b>>i)&1,C=(c>>i)&1,D=(d>>i)&1,M=(m>>i)&1;
if(initial(Mark,Worka(A,B,M))==4)Mark=Mark-4*pw[Worka(A,B,M)]+Workb(C,D)*pw[Worka(A,B,M)];
else
if(initial(Mark,Worka(A,B,M))!=Workb(C,D))
{
cout<<-1<<endl;
flag=1;
break;
}
}
// for(int i=0;i<8;i++)
// {
// cout<<initial(Mark,i)<<' ';
// }
// cout<<endl;
if(flag==0)
{
if(f[Mark]!=1061109567)cout<<f[Mark]<<endl;
else cout<<-1<<endl;
}
}
return 0;
}
这个位运算是真的很绕很绕。
我看了很久,主要是被绕在了那个从ab的初始装态出发得到cd的部分。我一直想不清楚怎么可能只用单源最短路。
然后发现其实不管怎么样都是从初始状态出发的,我相当于是把ab已经按照初始状态排好序了,而寻找它的编号其实就是它的初状态。我把这个和它的初状态割裂了,导致我觉得初状态是初状态,编号是编号。
所以我会觉得单源最短路不可做。这一点忽略了,当然很麻烦。