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已经按照初始状态排好序了,而寻找它的编号其实就是它的初状态。我把这个和它的初状态割裂了,导致我觉得初状态是初状态,编号是编号。
所以我会觉得单源最短路不可做。这一点忽略了,当然很麻烦。

posted @ 2024-04-03 16:54  HL_ZZP  阅读(2)  评论(0编辑  收藏  举报