P3758 [TJOI2017]可乐

题目描述
加里敦星球的人们特别喜欢喝可乐。因而,他们的敌对星球研发出了一个可乐机器人,并且放在了加里敦星球的 111 号城市上。这个可乐机器人有三种行为: 停在原地,去下一个相邻的城市,自爆。它每一秒都会随机触发一种行为。现在给加里敦星球城市图,在第 000 秒时可乐机器人在 111 号城市,问经过了 ttt 秒,可乐机器人的行为方案数是多少?
输入格式

第一行输入两个正整数 NNN,MMM。NNN 表示城市个数,MMM 表示道路个数。

接下来 MMM 行每行两个整数 uuu,vvv,表示 uuu,vvv 之间有一条道路。保证两座城市之间只有一条路相连,且没有任何一条道路连接两个相同的城市。

最后一行是一个整数 ttt,表示经过的时间。
输出格式

输出可乐机器人的行为方案数,答案可能很大,请输出对 201720172017 取模后的结果。
输入输出样例
输入#1

3 2
1 2
2 3
2

输出 #1

8

思路
首先想到暴力解法, 直接直接记忆化搜索用dp[i][j]记忆点j在i时刻的方案数
但内存过大只过了两个测试点
在这里插入图片描述

#include<bits/stdc++.h>

using namespace std;

const int N = 31;
int dp[31][1000002];
int n, t, m;
int mapp[N][N];

int dfs(int u, int ti)
{
    if(dp[u][ti] != -1)
        return dp[u][ti];
    int sum = 0;
    for(int i = 0; i <= n; i++)
    {
        if(mapp[u][i] == 1)
        {
           // cout << u << "->" << i << endl;
            sum += dfs(i, ti - 1) % 2017;
        }
    }
    dp[u][ti] = sum % 2017;
    return sum%2017;
}
int main()
{
    cin >> n >> m;
    for(int i = 1; i <= m; i++)
    {
        int x, y;
        cin >> x >> y;
        mapp[x][y] = mapp[y][x] = 1;
    }
    fill(dp[0], dp[0] + 1000001 * 31, -1);
    for(int i = 0; i <= n; i++) dp[i][0] = 1;
    for(int i = 1; i <= n; i++) mapp[i][0] = 1;
    for(int i = 0; i <= n; i++) mapp[i][i] = 1;
    cin >> t;
    cout << dfs(1, t);
    return 0;
}

第二种将记忆化写成递推式子,据说是分层图dp
递推公式 f[t][i] = (f[t][i] + f[t - 1][k])%2017, t为时间, i为任一点, k为与i相连接的点
对于自爆我们设置一个n + 1这个点, 任何点都可以到达n + 1但n + 1只能到达n + 1点
对于原地不动我们设置i可以到ix形成自环就好了
每个点都由t - 1时每个点的状态推出
内存占用很大
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
const int N=39,M=109;
struct edge{int to,nxt;}e[(M+N)<<1]; int hd[N],tot;
void add(int u,int v){
	e[++tot]=(edge){v,hd[u]}; hd[u]=tot;
}
int n,m,t,ans,f[1000002][32];
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1,u,v;i<=m;i++) scanf("%d%d",&u,&v),add(u,v),add(v,u);
	scanf("%d",&t);
	for(int i=1;i<=n;i++) add(i,n+1),add(i,i);
	for(int i = 1; i <= n + 1; i++)
        f[0][i]=1;//预处理
    add(n+1,n+1);
	for(int j=1;j <= t;j++){
		for(int u=1;u<=n+1;u++){
			for(int i=hd[u]; i; i=e[i].nxt){//与u相连的点
				{
				    f[j][u]=(f[j][u]+f[j - 1][e[i].to])%2017;
				  //  cout << u << "->" << e[i].to << " " << f[j - 1][e[i].to] << endl;
				}
			}
		//	cout << j << "  " << u << "****" << f[j][u] << endl;
		}
	}
	printf("%d", f[t][1]);
	return 0;
}


吸氧过后就AC了但内存依然很大,因为每个点的t时刻的方案数之和t-1时刻的每个点的方案数有关,因此我们可以利用滚动数组进行优化
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
const int N=39,M=109;
struct edge{int to,nxt;}e[(M+N)<<1]; int hd[N],tot;
void add(int u,int v){
	e[++tot]=(edge){v,hd[u]}; hd[u]=tot;
}
int n,m,t,ans,f[3][32];
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1,u,v;i<=m;i++) scanf("%d%d",&u,&v),add(u,v),add(v,u);
	scanf("%d",&t);
	for(int i=1;i<=n;i++) add(i,n+1),add(i,i);
	for(int i = 1; i <= n + 1; i++) f[1][i]= 1;
    add(n+1,n+1);
	for(int j=1;j <= t;j++){
        int x = 0, y = 1;
        if(j % 2 == 0) x = 1, y = 0;
        for(int i = 0; i <= n + 1; i++) f[x][i] = 0;
		for(int u=1;u <= n + 1;u++){
			for(int i=hd[u]; i; i=e[i].nxt){//与u相连的点
                f[x][u] = (f[x][u]+f[y][e[i].to])%2017;
                //cout << f[y][e[i].to] << " " << u << "->" << e[i].to << endl;
			}
		}
	}
	printf("%d", f[1][1]);
	return 0;
}

部分来自转载
上面的优化使空间大大减少,但时间任然很大,可是我们要有职业精神,取得更好的成绩,而不是依赖于我们的好朋友氧气和评测机
我们再看一眼转移方程:

f[j+1][e[i].to]=(f[j+1][e[i].to]+f[j][u])%2017;

来自转载
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
效率极大的提升了

#include<bits/stdc++.h>
using namespace std;
int n,m,t,ans;
struct mat{ //朴素矩阵 
	int a[33][33];
	mat(){memset(a,0,sizeof(a));}
	mat operator *(const mat b)const{
		mat c;
		for(int i=0;i<=n;i++)
			for(int j=0;j<=n;j++)
				for(int k=0;k<=n;k++)
					c.a[i][j]=(c.a[i][j]+a[i][k]*b.a[k][j])%2017;
		return c;
	}
}I,E,ansm;
mat pow(mat a,int p){ //朴素快速幂 
	if(p==0) return I;
	if(p==1) return a;
	mat tmp=pow(a*a,p/2);
	if(p%2) return tmp*a;
	else return tmp;
}
int main(){
	scanf("%d%d",&n,&m,&t);
	for(int i=1,u,v;i<=m;i++) scanf("%d%d",&u,&v),E.a[u][v]=E.a[v][u]=1; //朴素邻接矩阵 
	for(int i=0;i<=n;i++) I.a[i][i]=E.a[i][i]=1,E.a[i][0]=1; //新边和单位矩阵 
	scanf("%d",&t); ansm=pow(E,t); //矩阵快速幂 
	for(int i=0;i<=n;i++) ans+=ansm.a[1][i]; //统计答案 
	printf("%d",ans%2017);
	return 0;
}

注意此时矩阵中的值a[i][j]为i到的方案数, (a^k)[i][j]即为走k步i到j的方案数

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值