题目描述
加里敦星球的人们特别喜欢喝可乐。因而,他们的敌对星球研发出了一个可乐机器人,并且放在了加里敦星球的 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的方案数