洛谷P1333 瑞瑞的木棍 字符串 最短路
说在前面
用M↓写的第一篇题解,欢迎提出意见.
题目描述
瑞瑞有一堆的玩具木棍,每根木棍的两端分别被染上了某种颜色,现在他突然有了一个想法,想要把这些木棍连在一起拼成一条线,并且使得木棍与木棍相接触的两端颜色都是相同的,给出每根木棍两端的颜色,请问是否存在满足要求的排列方式。
例如,如果只有 \(2\) 根木棍,第一根两端的颜色分别为 red, blue,第二根两端的颜色分别为 red, yellow,那么 blue---red | red----yellow 便是一种满足要求的排列方式。
输入格式
输入有若干行,每行包括两个单词,表示一根木棍两端的颜色,单词由小写字母组成,且单词长度不会超过 \(10\) 个字母,最多有 \(250000\) 根木棍。
输出格式
如果木棒能够按要求排列,输出 Possible,否则输出 Impossible
输入输出样例
输入 #1
blue red
red violet
cyan blue
blue magenta
magenta cyan
输出 #1
Possible
分析
一根木棒就相当于一条边,木棒两边的颜色就是这条边所连的点,所以此题就是问一张图是否存在欧拉路。
欧拉路的判定很简单——所有点都连通(并查集),最多两个点的度为奇数(一个数组就可以搞定)。
那么这道题之所以是蓝题,是因为它对时间要求很严格——最多2.5e6个木棒,以及读入的字符串的处理。
字符串的处理
这里最最最快的方式是用\(trie\)。
\(trie\)中每个节点都有两个元素,\(num\)标记和\(26\)个指针,这里我们用数组模拟指针:
- 设\(0\)是根节点,实际上不代表任何的字母
- 对于要加入的每一个字符串,逐位考虑
- 如果当前节点没有str[i]这个子节点的话就新建一个节点为子节点
- 跳转到这个子节点,判断下一位
- 重复3、4步骤
- 如果最终的节点没有被分配一个编号的话,那么这个就新建一个编号给这个终节点
这样每一个字符串都有了唯一的编号,且编号是连续的,方便开数组等后续操作。
这就完成了trie的部分——字符串到正整数的映射。
struct Trie
{
int son[26],num;
}trie[2000010];
int n,trie_cnt;
int insert(string str)
{
int now=0;
for(int i=0;i<str.size();i++)
{
if(!trie[now].son[str[i]-'a']) trie[now].son[str[i]-'a']=++trie_cnt;
now=trie[now].son[str[i]-'a'];
}
if(!trie[now].num) trie[now].num=++n;
return trie[now].num;
}
可能有人会问为什么不用\(map\)?
因为\(map\)每次插入是\(logn\)的,而\(trie\)每次插入与字符串的长度有关(看作\(O(1)\)就好),题目也说了每个单词不超过\(10\)个字母。
当然也可以用\(unordered_map\)(需要C++11!),不会\(STL\)的同学可以用\(hash\)(这个还是有点慢)。
所以这里最优的就是\(trie\)啦!
判断欧拉路
欧拉路有个经典的故事——七桥问题,相信你应该耳熟能详了。
即:经过每条边正好一次,能不能把所有边都走一遍?
可以证明,只要整个图是连通的,且有奇数度的点不超过两个,那么就存在欧拉路!
-
度的统计只需要在读入木棒的时候标记一下即可。
-
判断连通图用并查集。
读入
while(cin>>str)
{
x=insert(str);
cin>>str;
y=insert(str);
merge(x,y);
degree[x]++;
degree[y]++;
}
判断
判断这里我用了点小技巧,同时判断度和并查集。
- 如果有相邻的两项的祖先是不同的,那么这肯定不是连通图,给flag加上一个大数字。
- 如果这点的度是奇数,那么flag+1就好。
- 最后判断flag,大于2说明奇度点多了或者图不连通,即不可能有欧拉路,反正则存在。
int flag=(degree[1]&1);
for(int i=2;i<=n;i++)
{
if(getf(i)!=getf(i-1)) flag+=10;
if(degree[i]&1) flag++;
}
if(flag>2) cout<<"Impossible"<<endl;
else cout<<"Possible"<<endl;
Code
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<queue>
#include<vector>
#define IL inline
#define re register
#define LL long long
#define ULL unsigned long long
#define re register
#define debug printf("Now is %d\n",__LINE__);
using namespace std;
template<class T>inline void read(T&x)
{
char ch=getchar();
while(!isdigit(ch))ch=getchar();
x=ch-'0';ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
}
inline int read()
{
int x=0;
char ch=getchar();
while(!isdigit(ch))ch=getchar();
x=ch-'0';ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x;
}
int G[55];
template<class T>inline void write(T x)
{
int g=0;
if(x<0) x=-x,putchar('-');
do{G[++g]=x%10;x/=10;}while(x);
for(re int i=g;i>=1;--i)putchar('0'+G[i]);putchar('\n');
}
struct Trie
{
int son[26],num;
}trie[2000010];
int n,trie_cnt;
int insert(string str)
{
int now=0;
for(int i=0;i<str.size();i++)
{
if(!trie[now].son[str[i]-'a']) trie[now].son[str[i]-'a']=++trie_cnt;
now=trie[now].son[str[i]-'a'];
}
if(!trie[now].num) trie[now].num=++n;
return trie[now].num;
}
int f[500010];
int getf(int x)
{
while(x!=f[x]) x=f[x];
return x;
}
void merge(int x,int y)
{
x=getf(x);
y=getf(y);
f[x]=y;
}
int degree[500010];
int main()
{
string str;
int x,y;
for(int i=1;i<=500000;i++) f[i]=i;
while(cin>>str)
{
x=insert(str);
cin>>str;
y=insert(str);
merge(x,y);
degree[x]++;
degree[y]++;
}
int flag=(degree[1]&1);
for(int i=2;i<=n;i++)
{
if(getf(i)!=getf(i-1)) flag+=10;
if(degree[i]&1) flag++;
}
if(flag>2) cout<<"Impossible"<<endl;
else cout<<"Possible"<<endl;
return 0;
}
注意
在洛谷上提交的话,如果你\(#2\)无法通过,请注意数组大小问题:
最多有\(2.5e6\)根木棒,意味着最多有\(5e6\)种单词,所以数组要开到\(5e6\),而\(trie\)数组越大越好。
经过我的非精确计算,类似于线段树,每一层新建分支\(26\),最多\(10\)层,(最多\(141,167,095,653,376\)个叶子节点),而题目中有单词数量不超过\(2.5e6\),\(26^4=456976\),应该开到最多\(26+676+17576+2500000≈2,518,278\)即可。(这个是乱算的)
疑惑
如果出现,有两根相同的木棒怎么办?题目中并没有否认这种情况,但是不这样判断的话也过了啊……奇怪啊。
如果以后这道题修改或者加强了,请来踢我一脚(

浙公网安备 33010602011771号