P1278 单词游戏
带权有向图上点不重复的最长路径 状压dp
来自仓鼠老师的话:
这题实际上就是带权有向图上点不重复的最长路径。从理论上来说,应该是个NP问题。
就是把每一句话设为一个点,然后可以\(O(n^2)\)的连边,怎么连边就不说了吧。。
设dp[i][j]
为当前走到\(i\)点,已走的状态为\(j\)。然后就可以愉快地进行状压dp。
有一个经验性总结:枚举状态的那一维一定要放在前面!不然答案是错的!我已经亲身经历过3次了!
今年再来出状压dp哩
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
const int maxn = 17;
struct Edges
{
int next, to;
} e[maxn * maxn];
int head[maxn], tot;
int start[maxn], end[maxn];
int weight[maxn];
int dp[maxn][65537];
int n;
int id(char x)
{
if(x == 'A') return 1;
else if(x == 'E') return 2;
else if(x == 'I') return 3;
else if(x == 'O') return 4;
else if(x == 'U') return 5;
}
void link(int u, int v)
{
e[++tot] = (Edges){head[u], v};
head[u] = tot;
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
char ch[105]; scanf("%s", ch);
start[i] = id(ch[0]);
int len = strlen(ch);
end[i] = id(ch[len - 1]);
weight[i] = len;
}
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= n; j++)
{
if(end[i] == start[j]) link(i, j);
}
}
int S = (1 << n) - 1;
for(int i = 1; i <= n; i++) dp[i][1 << (i - 1)] = weight[i];
for(int j = 1; j <= S; j++)
{
for(int i = 1; i <= n; i++)
{
if((1 << (i - 1)) & j)
{
for(int k = head[i]; k; k = e[k].next)
{
int v = e[k].to;
if((1 << (v - 1)) & j) continue;
dp[v][j | (1 << (v - 1))] = std::max(dp[v][j | (1 << (v - 1))], dp[i][j] + weight[v]);
}
}
}
}
int ans = 0;
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= S; j++)
{
ans = std::max(ans, dp[i][j]);
}
}
printf("%d\n", ans);
return 0;
}