snoi模拟赛day4t3 回家

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

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

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

    

       输出给的大样例的答案能得10分哦,所以如果三个题我今天都输出大样例的答案,那么就和我今天得的分一样了!

       今天刚出第一题还过了大样例就比较兴奋的去乱搞第二题了,就没有打这道题了(然后t1惨得20分)...

       因为[l,r]区间较大,我们可以先拆分成[1,r]的答案减去[1,l-1]的答案。

       又因为一个图的邻接矩阵存储的k次方,就是从任意一个点出发走k步的方案数,所以我们可以构造一个如下的矩阵套矩阵来解决要求[1,r]的问题

       一般矩阵[ sum[i],a[i] ] 

       特殊矩阵 [ E , 0  ]

                      [  A,  A ]

       sum[i]表示走了1到i步的前缀和,a[i] 表示走了i步的前缀和,这样二者相乘就可以得到[sum[i+1],a[i+1]]了。

       因为我们sum求出来的是从0到k都有可能走过的路径条数。接着为了满足k个点必须走,通过容斥原理,很容易就能想到k个点必须走的个数=总共走了[0,k]个点-总共走了[0,k-1]个点的和+[0,k-2]个点的和-....。所以我们可以通过2的i次方枚举每个点选或不选,然后统计有多少个点没选来执行上文的式子。

       下附AC代码。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define maxn 70
using namespace std;
typedef long long ll;
const ll mod=1e9+9;
int n,m,k,l,r,pos[maxn];
struct nod 
{
	ll n,m;
	ll a[maxn][maxn];
	nod()	{ memset(a,0,sizeof(a)); }
};
nod mul(nod a,nod b)
{
	nod ans;
	ans.n=a.n;ans.m=a.m;
	for(int i=1;i<=ans.n;i++)
	{
		for(int j=1;j<=ans.m;j++)
		{
			for(int k=1;k<=b.n;k++)
			{
				ans.a[i][j]+=a.a[i][k]*b.a[k][j];
				ans.a[i][j]%=mod;
			}
		}
	}
	return ans;
}
nod pow(nod a,ll p)
{
	nod ans;
	for(int i=1;i<=a.n;i++)
	ans.a[i][i]=1;
	ans.n=a.n;ans.m=a.m;
	while(p)
	{
		if(p&1)
		{
			ans=mul(ans,a);
		}
		a=mul(a,a);
		p>>=1;
	}
	return ans;
} 
nod a,b;
int lowbit(int now)
{
	return (now&(-now));
}
int countbit(int now)
{
	int tot=0;
	while(now)
	now-=lowbit(now),tot++;
	return tot;
}
int check[maxn];
ll cal(int now)
{
	ll res=0;
	for(int state=0;state<(1<<k);state++)
	{
		nod temp1=a,temp2=b;
		for(int i=1;i<=n;i++)
		{
			check[i]=1;
			for(int j=0;j<k;j++)
			{
				if((1<<j)&state)
				{
					if(i==pos[j])
					check[i]=0;
				}
			}
		}
		
		for (int i=1;i<=n;i++)
		for (int j=1;j<=n;j++) 
		if ((!check[i])||(!check[j]))
		{
			temp1.a[i][j+n]=0;temp1.a[j][i+n]=0;
			temp2.a[i+n][j]=0;temp2.a[j+n][i]=0;
			temp2.a[i+n][j+n]=0;temp2.a[j+n][i+n]=0;
		}
		
		nod temp=mul(temp1,pow(temp2,now));
		if(countbit(state)&1)
		{
			res-=temp.a[1][2];
			res=((res%mod)+mod)%mod;
		}
		else
		{
			res+=temp.a[1][2];
			res%=mod;
		}
	}
	return res;
}
int main()
{
	scanf("%d%d%d%d%d",&n,&m,&k,&l,&r);
	b.n=b.m=2*n;a.n=n;a.m=2*n;
	
	for(int i=1;i<=m;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		b.a[n+x][n+y]++;	b.a[n+y][n+x]++;
		b.a[n+x][y]++;	    b.a[n+y][x]++;
	}
	
	for(int i=1;i<=n;i++)
	{
		b.a[i][i]=1;
		a.a[i][i]=1;
		a.a[i][n+i]++;
	}
	
	for(int i=0;i<k;i++)
		scanf("%d",&pos[i]);
		
	ll ans=0;
	ans+=cal(r);
	if(l>=1) ans-=cal(l-1);
	ans=((ans%mod)+mod)%mod;
	printf("%lld\n",ans);
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值