snoi多校模拟赛 1.18 t3 home

本文介绍了一道图论问题,涉及到寻找在限制条件下的无向图路径计数。 Alice 和 Bob 要找从学校到家的路径,要求经过特定数量的点,并确保路径不重复。通过动态规划和二进制枚举的方法,解决这个问题,得出满足条件的路径总数。
摘要由CSDN通过智能技术生成

C 回家(home.pas/c/cpp)

TL:3S  ML:512MB

【Description】

       Alice和Bob是一对好兄妹。有一天放学后,他们打算回家。但是,他们还想在路上多玩一会。现在他们有一张地图,地图上标注了小镇的所有的N个地点,以及这些地点之间的道路(道路是无向的),共M条。

在这N个地点中,有K个地点他们希望一定经过。同时,他们不希望在外面玩太久,所以他们要求经过的点总数不能超过R个,为了使得回家的路途不要太无聊,他们要求经过的点不能少于L个。他们想知道共有多少条满足要求的回家的路径。两条路径被认为是不同的,当且仅当至少有一条边不同。

如果用图论语言描述,则为:给定无向图G,求以学校为起点,家为终点,边数在L到R之间,且路径上相邻两个点均不同的路径的数量,结果对109+9取模。

 

【Input】

第一行五个整数N,M,K,L, R,保证L<=R。

接下来M行,每行两个整数x, y,表示一条无向边,其两个端点的编号为x与y。保证自环

接下来K行,每行一个整数,表示他们要经过的地点的编号。保证这K个点不是家和学校,也不会有重复的点。

这里,家的节点编号为1,学校的节点编号为2,其余所有节点的编号为3..N中的一个整数。

【Output】

       一行一个整数,表示路径的条数。

【Sample Input】

       57 1 2 3

       15

       23

       54

       53

       13

       32

       12

       3

【Sample Output】

       9

【Hint】

测试点

N的上界

K的上界

其他

1

N/A

0

L=R

2

N/A

0

N/A

3

10

3

L=R

4

10

3

R-L<5

5

N/A

N/A

L=R

6

N/A

N/A

R-L<5

7

20

N/A

N/A

8

20

N/A

N/A

9

N/A

N/A

N/A

10

N/A

N/A

N/A

 

对于100%:N<=30, M<=300, K<=4, 1<=L<=R<=109

首先明确,图的邻接矩阵的x次方即为每个点走x次到另一个点的方案数。所以这题如果没有k直接暴力即可。

有k的话因为k极其小,所以二进制枚举即可,容斥原理,减奇加偶

但是我们注意:只能求出来某个距离时的方案数而得不到前缀,所以我们从终点向超级汇点连一条有向边,超级汇点连一条自环,这样就能前缀累加了,要注意最后答案是ans(r+1)-ans(l)

注意:__builtin_popcount(x)表示x的二进制下1的个数

然后代码理解起来几乎就没有压力,我感觉自己写的比其他julao们写的简洁一些(借鉴hjy julao的思路了)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define ll long long
#define ljm 1000000009
int read()
{
	int res=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
	while(c>='0'&&c<='9'){res=res*10+c-'0';c=getchar();}
	return res*f;
}
int n,m,k,l,r,vis[32],num[32];
struct M
{
	ll a[32][32];
	M(){memset(a,0,sizeof(a));}
}p,t;
M operator * (M a,M b)
{
	M c;
    for(int i=1;i<=n+1;i++)
        for(int j=1;j<=n+1;j++)
            for(int k=1;k<=n+1;k++)
                c.a[i][j]=(c.a[i][j]+(a.a[i][k]*b.a[k][j])%ljm)%ljm;
    return c;
}
M pow(M a,int b)
{
	M q;
	for(int i=1;i<=n+1;i++) q.a[i][i]=1;
    while(b)
    {
        if(b&1) q=q*a;
        a=a*a;
        b>>=1;
    }
    return q;
}
ll cal(int x)
{
	ll res=0;
	for(int st=0;st<(1<<k);st++)
	{
		t=p;
		for(int i=1;i<=n;i++) vis[i]=1;
		for(int i=0;i<k;i++)
		    if(st&(1<<i))
		        vis[num[i+1]]=0;
		for(int i=1;i<=n;i++)
		    for(int j=1;j<=n;j++)
		        if(!vis[i]||!vis[j])
		            t.a[i][j]=0;
		t=pow(t,x);
		if(__builtin_popcount(st)&1) res=(res-t.a[2][n+1]+ljm)%ljm;
		else res=(res+t.a[2][n+1]+ljm)%ljm;
	}
	return res;
}
int main()
{
	freopen("home.in","r",stdin);freopen("home.out","w",stdout);
	n=read();m=read();k=read();l=read();r=read();
	for(int i=1;i<=m;i++)
	{
		int u=read(),v=read();
		p.a[u][v]++;
		p.a[v][u]++;
	}
	p.a[n+1][n+1]++;
	p.a[1][n+1]++;
	for(int i=1;i<=k;i++) num[i]=read();
	printf("%lld\n",(cal(r+1)-cal(l)+ljm)%ljm);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值