小学数学题 洛谷p2921

题目背景

露米娅:我来先考你一道小学数学题吧!

琪露诺:好!小学的题我肯定都会!

题目描述

露米娅:有 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 不能同时在湖的另一侧。

现在给出这些条件,求:

  1. 至少需要传送器几次才能让所有妖精到湖的对岸

  2. 在保证次数最少的前提下,求过河方案数

输入输出格式

输入格式:

第一行四个整数 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:
1 0 0 1
输出样例#1:
1 1
输入样例#2:
5 0 0 2
输出样例#2:
3 90
输入样例#3:
3 1 0 1
1 2
输出样例#3:
-1 0

说明

对于 30% 30 \% 30% 的数据, n≤10 n \leq 10 n10

对于另外 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,cn15m1,m2≤50 m_1 , m_2 \leq 50 m1,m250r≤109 r \leq 10^9 r109

请不要相信洛谷评测机的速度,如果得了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;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值