HDU 5807 BestCoder Round #86 Keep In Touch (分段式DP)

41 篇文章 1 订阅
23 篇文章 0 订阅

Keep In Touch  


题目链接:点我打开链接


题意:有n个城市,编号依次为1$到n,同时有m条单向道路连接着这些城市,其中第i条道路的起点为Ui,终点为Vi 。(1<=Ui < Vi <= n)。
特工团队一共有3名成员:007,008,以及009,他们将要执行q次秘密任务。

在每次任务中,三人可能会处于三个不同的城市,他们互相之间通过对讲机保持联络。编号为i的城市的无线电频为Wi,如果两个城市的无线电频差值的绝对值不超过K,那么无线电就可以接通。三个特工每个时刻必须要选择一条道路,走到下一个城市,每条道路都只需要花费1单位时间。
他们可以选择在任意城市终止任务,甚至可以在起点就终止任务,但不允许在道路上终止任务。现在他们想知道,对于每次任务,给定三个人的起始位置,有多少种可能的合法行动方案,使得行动过程中任意在城市的时刻,他们都可以两两联络?
两个方案被视作不同当且仅当至少存在一个人在某一时刻所在的城市不同。

注意:3个特工必须同时结束任务。

简单地说,有n( 0<=n<=50 )个城市m ( 0<=m<=n*(n-1)/2 ) 条单向边(x,y),保证x<y 。
对于三个点(x,y,z)如果abs(w[x]-w[y])<=K && abs(w[x]-w[y])<=K && abs(w[x]-w[y])<=K则就是一个合法状态。 否则就不合法。
问你,如果我们从(x,y,z)出发,可以在合法状态中任意行走任意终止,有多少种不同的行走路径数。

官方题解:考虑dp,设f[ i ][ j ][ k ]表示三个人分别在 i , j , k时的方案数,直接转移是O(n^6)的。 于是考虑加维,设f[i][j][k][now]表示三个人分别在i,j,k时,目前准备走now这个人的方案数,那么转移复杂度就降低到了O(n^4)。
我用的是三维。但本质不变。

AC代码:(三维)
//#include<bits/stdc++.h>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
#include<map>
#include<queue>
#include<set>
#include<stack>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N = 50+10;
const int mod=998244353;
int MAX[3];
int MAXN[3];
int read()
{
	int v = 0, f = 1;
	char c =getchar();
	while( c < 48 || 57 < c ){
		if(c=='-') f = -1;
		c = getchar();
	}
	while(48 <= c && c <= 57) 
		v = v*10+c-48, c = getchar();
	return v*f;
}
ll dp[N][N][N];
int a[N][N];
int w[N];
int g[N][N][N];
int h[N][N][N];
int main()
{
  // freopen("in.txt","r",stdin);
	int t,n,m,k,q;
	int x,y,z;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d%d%d",&n,&m,&k,&q);
		for(int i=1;i<=n;i++) scanf("%d",&w[i]);
		memset(a,0,sizeof(a));
		
		for(int i=1;i<=m;i++)
		{
			scanf("%d%d",&x,&y);
			a[y][x]=1;
		}
		for(int i=n;i>=1;--i)
			for(int j=n;j>=1;--j)
				for(int d=n;d>=1;--d){
				dp[i][j][d] = 1;g[i][j][d] = 0;h[i][j][d] = 0;
					for(int t=1;t<=n;t++){
						if(a[t][i]){
							( dp[i][j][d] += h[t][j][d]) %=mod;
						}
					}
					for(int t=1;t<=n;t++){
						if(a[t][j]){
							( h[i][j][d] += g[i][t][d] )%=mod;
						}
					}
					for(int t=1;t<=n;t++){
						if(a[t][d]){
							( g[i][j][d] += dp[i][j][t])%=mod;
						}
					}
					//不合法 
					if(abs(w[i]-w[j]) > k) dp[i][j][d] = 0;
					if(abs(w[i]-w[d]) > k) dp[i][j][d] = 0;
					if(abs(w[j]-w[d]) > k) dp[i][j][d] = 0;
				}
		for(int i=1;i<=q;i++)
		{
			scanf("%d%d%d",&x,&y,&z);
			printf("%lld\n",dp[x][y][z]);
		}
	}
	return 0;
}

也可以写成官方题解的四维吧。
代码:
//#include<bits/stdc++.h>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
#include<map>
#include<queue>
#include<set>
#include<stack>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N = 50+10;
const int mod=998244353;
int t;
int n, m, K, q;
int w[N];
vector<int>a[N];
int f[N][N][N][3];
bool e[N][N];
int main()
{
	// freopen("in.txt","r",stdin);
	scanf("%d", &t);
	for (int i = 1; i <= t; i++)
	{
		memset(e,0,sizeof(e));
		scanf("%d%d%d%d", &n, &m, &K, &q);
		for (int i = 1; i <= n; ++i)scanf("%d", &w[i]), a[i].clear();
		for (int i = 1; i <= m; ++i)
		{
			int x, y; scanf("%d%d", &x, &y);
			a[y].push_back(x);
			e[x][y] = 1;
		}
		memset(f,0,sizeof(f));
		for (int i = n; i >= 1; --i)
		{
			for (int j = n; j >= 1; --j)
			{
				for (int k = n; k >= 1; --k)
				{
					if (abs(w[i] - w[j]) > K || abs(w[i] - w[k]) > K || abs(w[j] - w[k]) > K)
						f[i][j][k][0] = 0;
					else (f[i][j][k][0]+=1)%=mod;
					
					if (f[i][j][k][0])
						for (int u = 1; u < i; ++u) 
							if (e[u][i])
								(f[u][j][k][2]+=f[i][j][k][0])%=mod;
							
					if(f[i][j][k][1])
						for (int u = 1; u < k; ++u)
							if (e[u][k])
								(f[i][j][u][0]+=f[i][j][k][1])%=mod;
							
					if(f[i][j][k][2])
						for (int u = 1; u < j; ++u)
							if (e[u][j])
								(f[i][u][k][1]+=f[i][j][k][2])%=mod;										
				}
			}
		}
		while (q--)
		{
			int x, y, z;
			scanf("%d%d%d", &x, &y, &z);
			printf("%d\n", f[x][y][z][0]);
		}
	}
	return 0;
}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值