HNU 12814 SIRO Challenge(最短路+状态压缩+dp)

题意:

给你N个点,M条双向边,L个饭店,S代表起点,T代表时间限制

给你M个a,b,c 表示走过边(a,b)的花费为c

给你L个j,e 表示点j'旁边有个饭店,在该饭店吃饭需要时间e

  • 2 ≤ n ≤ 300

  • 1 ≤ m ≤ 5,000

  • 1 ≤ l ≤ 16

  • 1 ≤ s ≤ n

  • 1 ≤ t ≤ 100,000

  • 1 ≤ ai, bi ≤ n

  • 1 ≤ ci ≤ 1,000

  • 1 ≤ ji ≤ n

  • 1 ≤ ei ≤ 15

想法:  训练赛的时候我就想到先用弗洛伊德预处理,然后就把图变成了一个只有16个点的图,后面我想用状态压缩,也想过用dp,当时以为这不能用dp,因为dp有后效性

后面自己用dfs写TLE了

比赛之后HNU的学长在群里说记忆化搜索就是dp,我肯定是因为没有记忆化。。。 

等他将完这题的时候我才知道。。 原来状态还可以酱紫转移的。。  涨姿势了。。

PS: 写代码写了好久才写出来。。。 真是无语。。  知道怎么做居然写不出来。。  LCB 教我用递归写了一次。。 昨晚还是TLE了。。  今早上用for循环写AC了。。

代码:

#include <stdio.h>
#include <string.h>
#define maxn 305
#define maxt 100005
#define REP(i,n) for(int i= 1; i<= (n); i++)

int map[maxn][maxn];
int dis[maxn][maxn];
int n, m, l, s, t;	
int pos[20], c[20];
int dp[1<<17][20];
int cnt[1<<17][20];
int ans;

int min(int x, int y)
{
	return x< y? x: y;
} 

void Floyd()
{
	REP(i, n) REP(j, n)
		dis[i][j]= map[i][j];	
	REP(u, n) REP(v, n) REP(w, n)
		if(dis[u][v] + dis[u][w] < dis[v][w])
			dis[v][w]= dis[u][v] + dis[u][w];
}
/*
void get(int ss, int k)
{
	//printf("%d %d %d %d %d %d\n",ss,k,pos[k],dp[ss][k], dis[s][pos[k]],dp[ss][k]+ dis[ss][pos[k]]);
	if(dp[ss][k]+ dis[s][pos[k]]> t)
		return;
//	printf("xx\n");	
	if(cnt[ss][k]> ans)
		ans= cnt[ss][k];	
	for(int i= 1; i<= l; i++)
		if(ss&(1<<i))continue;
		else
		{
			int xx= dp[ss][k] + dis[pos[k]][pos[i]] + c[i];
		//	printf("%d %d %d %d %d %d\n",xx,dp[s][k], dis[pos[k]][pos[i]],c[i],pos[k],pos[i]);
			int hh= ss|(1<<i);
			if(dp[hh][i]== -1)
				dp[hh][i]= xx;
			else	
				dp[hh][i]= min(dp[hh][i], xx);
			cnt[hh][i]= cnt[ss][k] + 1;		
			get(hh, i);	 
		}
}   
*/ //之前的递归代码 
int main()
{
	while(scanf("%d %d %d %d %d",&n,&m,&l,&s,&t)!=EOF)
	{
		if(n+m+l+s+t==0)
			break;
		REP(i, n) REP(j, n)
			map[i][j]= maxt;
		for(int i= 1; i<= m; i++)
		{
			int a, b, c;
			scanf("%d %d %d",&a,&b,&c);
			map[a][b]= map[b][a]= c;
		}
		for(int i= 1; i<= l; i++)
			scanf("%d %d",&pos[i], &c[i]);
		pos[0]= s;	
		Floyd();
		memset(dp, -1, sizeof(dp));
		memset(cnt, 0, sizeof(cnt));
		dp[1][0]= 0;
		dis[s][s]= 0;
		ans= 0;
		for(int num= 1; num< (1<<(l+1)); num+=2) //用一个L+1位二进制数表示,其中最低位表示起点S 
			for(int i= 1; i<= l; i++)
				if(num&(1<<i))continue;
				else
				{
					for(int k= 0; k<= l; k++)
						if(num&(1<<k) && dp[num][k]!=-1) // 这里要判断dp[num][k]是否非法 因为S点只能在第一步往其他点延伸 
						{	
							int xx= dp[num][k] + dis[pos[k]][pos[i]] + c[i];
							int hh= num|(1<<i);
							if(dp[hh][i]== -1)
								dp[hh][i]= xx;
							else	
								dp[hh][i]= min(dp[hh][i], xx);
							cnt[hh][i]= cnt[num][k] + 1;			 
						}
				}
		for(int i= 1; i< (1<< (l+1)); i++)
			for(int j= 1; j<= l; j++)
			{
				if(dp[i][j]!=-1 && dp[i][j] + dis[s][pos[j]]<= t && cnt[i][j]> ans)	
					ans= cnt[i][j];
			}			
		printf("%d\n",ans);		
	}
	return 0;
} 


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值