题目背景
露米娅:我来先考你一道小学数学题吧!
琪露诺:好!小学的题我肯定都会!
题目描述
露米娅:有 n n n 只妖精要跨过雾之湖,由于湖边大雾弥漫,妖精们看不清湖到底有多大,不想从边上绕过去。
湖上有一条船个传送器,且这个传送器每次只能载 r rr 只妖精跨过湖面(注意传送器可以同时把两侧的妖精分别运到对岸,但每次运送的总妖精数不能超过 r rr )。
这些妖精还很喜欢搞事,所以在任何时刻,都需要满足一些条件,其中第一种条件有 m1 m_1m1 个,第二种条件有 m2 m_2 m2 个。
第一种条件形如 妖精 a a a 和妖精 b b b 必须要在湖的同一侧;
第二种条件形如 当妖精 a a a 在湖的一侧时,妖精 b b b 和妖精 c c c 不能同时在湖的另一侧。
现在给出这些条件,求:
-
至少需要传送器几次才能让所有妖精到湖的对岸
- 在保证次数最少的前提下,求过河方案数
输入输出格式
输入格式:第一行四个整数 n,m1,m2,r n , m_1 , m_2 , rn,m1,m2,r
接下来 m1 m_1 m1 行每行2个整数 a,b a , b a,b,代表第一种条件
接下来 m2 m_2 m2 行每行3个整数 a,b,c a , b , c a,b,c, 代表第二种条件
输出格式:两个整数,分别为最少使用传送器次数和方案数,用空格分隔
若无法全部过河,输出"-1 0"(不含引号)
输入输出样例
1 0 0 1
1 1
5 0 0 2
3 90
3 1 0 1 1 2
-1 0
说明
对于 30% 30 \% 30% 的数据, n≤10 n \leq 10 n≤10
对于另外 10% 10 \% 10% 的数据, m1=m2=0 m_1 = m_2 = 0 m1=m2=0
对于 100% 100 \% 100% 的数据, a,b,c≤n≤15 a,b,c \leq n \leq 15a,b,c≤n≤15,m1,m2≤50 m_1 , m_2 \leq 50 m1,m2≤50,r≤109 r \leq 10^9 r≤109
请不要相信洛谷评测机的速度,如果得了80分以上,可以等人少的时候再交一次。但如果得了60分以下,说明可能写的不是正解,就不要再虐萌萌哒评测机啦
解法一:先预处理出所有合法的状态和操作数量<=r的所有操作,然后bfs即可。
#include<iostream>
#include<cstring>
#define f(i,l,r) for(i=(l);i<=(r);i++)
using namespace std;
const int MAXN=55;
int n,m1,m2,r,ma[MAXN],mb[MAXN],Ma[MAXN],Mb[MAXN],Mc[MAXN];
bool g[1<<16];
int ch[1<<15],sum,ukn[1<<15];
int q[1<<16],Head,Tail,vis[1<<16],num[1<<16],ans[1<<16],flag;
inline bool pd(int sta)
{
int i,j;
f(i,1,m1){
if((!(sta&ma[i]))^(!(sta&mb[i]))){
return 0;
}
}
f(i,1,m2){
if(((!(sta&Ma[i]))^(!(sta&Mb[i])))&&((!(sta&Ma[i]))^(!(sta&Mc[i])))){
return 0;
}
}
return 1;
}
inline void dfs(int cur,int res,int sta)
{
if(cur==n+1){
if(sta){
ch[++sum]=sta;
}
ukn[sta]=1;
return;
}
if(res<=r) dfs(cur+1,res+1,sta|(1<<(cur-1)));
dfs(cur+1,res,sta);
}
inline void BFS()
{
int i,st=(1<<n)-1,tmp;
Head=Tail=1;
q[Tail++]=st;
vis[st]=1;
num[st]=1;
ans[st]=0;
while(Head<Tail){
int sta=q[Head++];
if(ans[sta]==ans[0]) return;
if(ans[sta]==ans[0]-1){
if(ukn[sta]){
num[0]+=num[sta];
}
continue;
}
f(i,1,sum){
tmp=sta^ch[i];
if(g[tmp]){
if(!vis[tmp]){
ans[tmp]=ans[sta]+1;
num[tmp]=num[sta];
q[Tail++]=tmp;
vis[tmp]=1;
if(tmp==0) flag=1;
}
else if(ans[sta]+1==ans[tmp]){
num[tmp]+=num[sta];
}
}
}
}
}
int main()
{
ios::sync_with_stdio(false);
memset(ans,63,sizeof(ans));
int i,j,a,b,c;
cin>>n>>m1>>m2>>r;
f(i,1,m1){
cin>>a>>b;
ma[i]=1<<(a-1);
mb[i]=1<<(b-1);
}
f(i,1,m2){
cin>>a>>b>>c;
Ma[i]=1<<(a-1);
Mb[i]=1<<(b-1);
Mc[i]=1<<(c-1);
}
f(i,0,(1<<n)-1){
g[i]=pd(i);
}
dfs(1,1,0);
BFS();
if(flag) cout<<ans[0]<<" "<<num[0]<<endl;
else cout<<"-1 0"<<endl;
return 0;
}
解法二:
优化建图
新建节点(i,j,k)表示与状态i 后j位完全相同 且 剩余位置上差异恰有k位
从(i,j,k)向(i,j-1,k)和(i^(1<<(j-1)),j-1,k+1)连一条权值为0的边
再从(i,0,k)向(i,n,0)连一条权值为1的边(0<=k<=r)
最后从((1<<n)-1,0,k)向T连一条权值为0的边(0<=k<=r)
再在新图上跑BFS+DP
时间复杂度O((n^2)*(2^n))
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
const int MAXN=10000010;
inline int Hash(const int &a,const int &b,const int &c)
{
return a*289+b*17+c;
}
struct edge
{
int v;
bool w;
edge *next;
}*h[MAXN],pool[MAXN*2];
int top;
inline void addedge(int u,int v,bool w)
{
//cout<<u<<' '<<v<<endl;
edge *tmp=&pool[++top];
tmp->v=v;
tmp->w=w;
tmp->next=h[u];
h[u]=tmp;
}
queue<int> q;
int dis[MAXN];
int cnt[MAXN];
int n,m1,m2,r,S,T;
//m1:形如(a和b必须在一边)的限制
//m2:形如(a不在时b和c不能在一边)的限制
int rule1[51],rule2a[51],rule2b[51];
inline bool isvalid(int state)
{
for(int i=1; i<=m1; ++i)
{
if((state&rule1[i])!=rule1[i]&&(state&rule1[i])!=0)
return false;
}
for(int i=1; i<=m2; ++i)
{
if(state&rule2a[i])
{
if(!(state&rule2b[i]))
return false;
}
else
{
if((state&rule2b[i])==rule2b[i])
return false;
}
}
return true;
}
int main()
{
memset(dis,-1,sizeof(dis));
scanf("%d%d%d%d",&n,&m1,&m2,&r);
if(r>n)r=n;
S=Hash(0,0,0),T=10000000;
int a,b,c;
for(int i=1; i<=m1; ++i)
{
scanf("%d%d",&a,&b);
rule1[i]=(1<<(a-1))|(1<<(b-1));
}
for(int i=1; i<=m2; ++i)
{
scanf("%d%d%d",&a,&b,&c);
rule2a[i]=(1<<(a-1));
rule2b[i]=(1<<(b-1))|(1<<(c-1));
}
//for(int i=0;i<1<<n;i++)cout<<i<<' '<<isvalid(i)<<endl;
if(!isvalid((1<<n)-1))
{
printf("-1 0\n");
return 0;
}
for(int i=0; i<1<<n; ++i)
{
for(int j=1; j<=n; ++j)
{
for(int k=0; k<=r; ++k)
{
addedge(Hash(i,j,k),Hash(i,j-1,k),0);
cout<<i<<' '<<j<<' '<<k<<"连"<<i<<' '<<j-1<<' '<<k<<"权值0"<<endl;
if(k!=r){
addedge(Hash(i,j,k),Hash(i^(1<<(j-1)),j-1,k+1),0);
// cout<<i<<' '<<j<<' '<<k<<"连"<<(i^(1<<(j-1)))<<' '<<j-1<<' '<<k+1<<"权值0"<<endl;
}
}
}
if(!isvalid(i))continue;
for(int k=0; k<=r; ++k){
addedge(Hash(i,0,k),Hash(i,n,0),1);
// cout<<i<<' '<<0<<' '<<k<<"连"<<i<<' '<<n<<' '<<0<<"权值1"<<endl;
}
}
for(int k=0; k<=r; ++k)
{
addedge(Hash((1<<n)-1,0,k),T,0);
// cout<<(1<<n)-1<<' '<<0<<' '<<k<<"连"<<"汇点"<<"权值0"<<endl;
}
q.push(S);
dis[S]=0;
cnt[S]=1;
while(!q.empty())
{
int u=q.front();q.pop();
for(edge *tmp=h[u]; tmp; tmp=tmp->next)
{
if(dis[tmp->v]==-1)
{
dis[tmp->v]=dis[u]+tmp->w;
q.push(tmp->v);
}
if(dis[tmp->v]==dis[u]+tmp->w)cnt[tmp->v]=cnt[tmp->v]+cnt[u];
}
}
printf("%d %d\n",dis[T],cnt[T]);
return 0;
}