[BZOJ1138][POI2009]Baj 最短回文路

试题描述

N个点用M条有向边连接,每条边标有一个小写字母。 对于一个长度为D的顶点序列,回答每对相邻顶点Si到Si+1的最短回文路径。 如果没有,输出-1。 如果有,输出最短长度以及这个字符串。

输入

第一行正整数N和M ( 2 ≤ N ≤ 400 , 1 ≤ M ≤ 60,000 ) 接下来M行描述边的起点,终点,字母。接下来D表示询问序列长度 ( 2 ≤ D ≤ 100 ) 再接下来D个1到N的整数

输出

对于D-1对相邻点,按要求输出一行。如果没合法方案,输出-1。 如果有合法,输出最短长度

输入示例

6 7
1 2 a
1 3 x
1 4 b
2 6 l
3 5 y
4 5 z
6 5 a
3
1 5 3

输出示例

3
-1

数据规模及约定

见“输入

题解

朴素的 dp 是这样的:设 f[i][j] 表示节点 i 到节点 j 的最短回文长度,转移的时候枚举字母 x,设节点 j 沿着带有字母 x 的边走一格所能到达的点集为集合 A,设 i 逆着带有字母 x 的边走一格所能达到的点集为集合 B。那么对于 ?a ∈ A, ?b ∈ B,就可以更新 f[a][b] 了,即 f[a][b] = min{ f[a][b], f[i][j] + 2 }。

但是我们发现这个算法被爆菊了。。。就是如果有很多条相同字母的边指向 i,相同字母的边从 j 出发,转移就会被卡成 O(n2) 的了。。。

解决方式就是添加一个中间状态 g[i][j][c],表示节点 i 到节点 j 的路径最后一个字母是 c,并且除掉最后一个字母后它就是一个回文路径,在这样的情况下的最短长度。那么对于状态 f[i][j],可以用节点 j 沿着字母 c(枚举)走一格到达的所有节点 b 来更新 g[i][b][c];对于状态 g[i][b][c],可以用节点 i 逆着字母 c(这个字母是固定的,即状态中的字母)走一格到达的所有节点 a 来更新 f[a][b],这样状态数变成了原来的 27 倍,转移复杂度变成了 O(n)。

转移顺序比较乱,可以用 BFS 帮助转移。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <queue>
using namespace std;

int read() {
	int x = 0, f = 1; char c = getchar();
	while(!isdigit(c)){ if(c == ‘-‘) f = -1; c = getchar(); }
	while(isdigit(c)){ x = x * 10 + c - ‘0‘; c = getchar(); }
	return x * f;
}

#define maxn 410
#define maxa 26
#define maxm 10660

bool G[maxn][maxn][maxa];
int f[maxn][maxn], g[maxn][maxn][maxa];
vector <int> from[maxn][maxa], to[maxn][maxa];
void AddEdge(int a, int b, int c) {
	to[a][c].push_back(b);
	from[b][c].push_back(a);
	return ;
}

struct Pair {
	int a, b, c;
	Pair() {}
	Pair(int _1, int _2, int _3): a(_1), b(_2), c(_3) {}
};
queue <Pair> Q;

int main() {
	int n = read(), m = read();
	for(int i = 1; i <= m; i++) {
		int a = read(), b = read(); char ch[2]; scanf("%s", ch);
		G[a][b][ch[0]-‘a‘] = 1;
	}

	memset(f, -1, sizeof(f)); memset(g, -1, sizeof(g));
	for(int i = 1; i <= n; i++) Q.push(Pair(i, i, -1)), f[i][i] = 0;
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= n; j++) if(i != j) {
			bool fl = 0;
			for(int c = 0; c < maxa; c++) if(G[i][j][c]) {
				AddEdge(i, j, c);
				fl = 1; break;
			}
			if(fl) f[i][j] = 1, Q.push(Pair(i, j, -1));
		}
	while(!Q.empty()) {
		Pair u = Q.front(); Q.pop();
		int a = u.a, b = u.b, c = u.c, tmp = c < 0 ? f[a][b] : g[a][b][c];
//		printf("(%d, %d, %d)\n", a, b, c);
		if(c < 0)
			for(c = 0; c < maxa; c++)
				for(int i = 0; i < to[b][c].size(); i++) {
					int B = to[b][c][i];
					if(g[a][B][c] < 0) g[a][B][c] = tmp + 1, Q.push(Pair(a, B, c));
				}
		else
			for(int i = 0; i < from[a][c].size(); i++) {
				int A = from[a][c][i];
				if(f[A][b] < 0) f[A][b] = tmp + 1, Q.push(Pair(A, b, -1));
			}
	}

	int q = read(), st = read();
	for(int i = 2; i <= q; i++) {
		int u = read();
		printf("%d\n", f[st][u]);
		st = u;
	}

	return 0;
}
时间: 2024-10-29 19:06:46

[BZOJ1138][POI2009]Baj 最短回文路的相关文章

bzoj 1138: [POI2009]Baj 最短回文路 dp优化

1138: [POI2009]Baj 最短回文路 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 161  Solved: 48[Submit][Status] Description N个点用M条有向边连接,每条边标有一个小写字母. 对于一个长度为D的顶点序列,回答每对相邻顶点Si到Si+1的最短回文路径. 如果没有,输出-1. 如果有,输出最短长度以及这个字符串. Input 第一行正整数N和M ( 2 ≤ N ≤ 400 , 1 ≤ M ≤

BZOJ 1138 POI2009 Baj 最短回文路 BFS

+题目大意:给定一张有向图,每个点有一个字符,多次求两点的最短回文路 据说这道题第一次做的人都会T? 一开始的思路是这样的:令fx,y表示从点x走到点y的最短回文路径,转移fx,y=min{fz,w+2|x?c?>z,w?c?>y} 然后广搜,果断T了= = 冗余的转移太多了-- 正解是这样的: 令gx,y,c表示从点x走到点y,除了最后一条边之外是回文路径且最后一条边的字符为c的最短路 然后转移是这样的: gx,y,c=min{fx,z+1|z?c?>y} fx,y=min{gz,y,

bzoj 1138: [POI2009]Baj 最短回文路

额,,貌似网上的题解都说超时之类的. 然而我这个辣鸡在做的时候不知道在想什么,连超时的都不会. 超时的大概是这样的,f[x][y]表示x到y的最短回文路,然后更新的话就是 f[x][y]更新到 f[a][b] 当x->a,y->b且边的颜色是一样的. 然后yy了一下为什么会超时呢.... 然后想到了一个sb的情况..两个菊花图连起来...这样的复杂度就呵呵呵了..每次用f[x][y]更新的话都要枚举一个颜色,然后把另一边的颜色找出来,这样显然是要爆炸的. 为了避免这种sb情况,就引入了一个叫题

BZOJ 1138 [POI2009]Baj 最短回文路 DP

题意:链接略 方法: DP 解析: 显然我们可以找回文中点然后宽搜向两边拓展. 不过这样的复杂度的话- 枚举中点再拓展,岂不有点爆炸? 所以我们换个方式来优化. 我们设F[i][j]表示由i到j的最短回文路径长度. 设G[i][j][k]表示从i到j走一条回文路径再走一个小写字母k的最短回文路径长度. 有了这个辅助式子,问题就变得简单多了. 所有的状态最多也就n^2*k个. 直接上宽搜即可. F[i][j]=min{G[z][j][k]+1&&edge[i][z]==k} G[i][j][

[经典] 回文问题(三)

Longest Palindromic Substring 最长回文子串 Given a string S, find the longest palindromic substring in S. You may assume that the maximum length of S is 1000, and there exists one unique longest palindromic substring. 普通做法,动态规划O(N^2)是能直接出结果的,长度从小到大排列,可以边动规

刷题之路(九)--判断数字是否回文

Palindrome Number 问题简介:判断输入数字是否是回文,不是返回0,负数返回0 举例: 1: 输入: 121 输出: true 2: 输入: -121 输出: false 解释: 回文为121-,所以负数都不符合 3: 输入: 10 输出: false 解释: 倒序为01,不符合要求 解法一:这道题比较简单,先判断一下,负数返回0,正数可以通过转换为字符串,通过方法反转,再比较两字符串 解法二:Revert half of the number 将一个数字分为前后两部分,通过判断数

添加回文串

题目描述 对于一个字符串,我们想通过添加字符的方式使得新的字符串整体变成回文串,但是只能在原串的结尾添加字符,请返回在结尾添加的最短字符串. 给定原字符串A及它的长度n,请返回添加的字符串.保证原串不是回文串. 测试样例: "ab",2 返回:"a" 问题分解 1.找到最长的回文子串 2.剩余部分就是需要添加的子串 使用Naive查找,寻找最大公共串 这里用到了:翻转子串==原子串 =>回文子串 从原串的开头开始找,比较是否与翻转串的末尾相同 [这里用到了本题

回文诗

回文诗 在中国文学中,回文诗词是一种奇特的文体.这种诗词,有的可叠读或者叠字环读,有的可倒读.古代的白话小说<苏小妹三难新郎>中,佛印禅师写的一首长歌,就是属于叠读诗一类.而秦少游寄给苏小妹的一首诗和苏东坡与苏小妹记采莲胜会的两首诗,则是属于叠字环读的诗.也有既能顺读又能倒读的诗,但这种诗不多见. 先请看佛印禅师的原诗.他的诗都是奇特的叠字排列: 野野 鸟鸟 啼啼 时时 有有 思思 春春 气气 桃桃 花花 发发 满满 枝枝 莺莺 雀雀 相相 呼呼 唤唤 岩岩 畔畔 花花 红红 似似 锦锦 屏屏

[林大帅作品连载]第二回 环岛路绝景配绝音 俏学妹书店忆书院

第二回 环岛路绝景配绝音 俏学妹书店忆书院    诗云:      漫言旧情心未冷,夜寂乱稿伴孤灯.            本意沈吟新人至,人生何处不相逢?       话接上回,林二抬头一看,只见那女童生微笑吟吟到:“叨劳了,我几个路过宝地,只因醉心于这路旁小花,竟忘了投宿之事,请问八日客栈位于何处?”,话未叙完,两颊便已微红.林二一见,便来了精神,方才丢手机,钱袋之事,早已抛到九霄云外.却故意顿了顿道:“几位姑娘莫急,这前面不远处便是‘八日’客栈,小可无事可领诸位前往”.       说罢那