题意:给n个字符串,这些字符串如果某个字符串的尾部字母和另一个字符串的首部字母相等,那么这两个字母就可以连起来,有的字符串可以反转,问是否存在一种情况使得所有字符串都连成一条链。
转载请注明出处:http://blog.csdn.net/inf_force/article/details/39176931
这题真是好题。
建图方法一:
(无法做出。)
如果按照题意给定的方式建图,那么问题就转换为:
判断这个图中是否存在一条通路,通路访问图中所有点一次仅且一次。
一开始我的想法是找最长路,发现思路不可行。spfa碰到环就跪惨。
(tips:有人会最长路吗?为毛感觉这似乎是个不可解的问题= =)
后来查找资料过后发现是这是一个著名的图论模型:哈密顿图(也称Hamilton图)。
那么问题就变成了:
判断这个图有无哈密顿通路
哈密顿通路:经过图中所有节点一次仅且一次的通路。
哈密顿回路:经过图中所有节点一次仅且一次的回路。
哈密顿图: 存在哈密顿回路的图。
网上找了些资料,原来这是个NP问题。
多项式时间复杂度O(n!*n),可以通过状态压缩&动态规划把复杂度降为O(2^n*n^3)。
对于这题来说。明显不能用。所以这种建图方式XX
建图方法二:
如果换一种建图方式。
字符串的首字母-->字符串的尾字母建一条有向边,如果字符串是可反转的,那么就建无向边,
那么问题就转换为:
是否存在------将所有无向边定向,使得这个图中存在这么一条路(回路):经过图中所有边一次仅一次。
其实这也是个著名的图论模型:欧拉图(也称Euler图)
欧拉通路(也称Euler迹): 经过图中所有边一次仅一次的通路。
欧拉回路(也称Euler闭迹):经过图中所有边一次仅一次的回路。
欧拉图: 存在欧拉回路的图。
定理一:
一个非空连通图是Euler图当且仅当它没有奇度顶点。
定理二:
一个连通图有Euler迹当且仅当它有且仅有0个或2个奇度顶点
通过这两定理还不能说明啥:
总结一下就是:
判定方法:
无向图:
欧拉通路(迹) :图连通&&图中只有0个或2个度为奇数的节点。
欧拉回路(闭迹):图连通&&图中所有节点度均为偶数。
有向图:
欧拉通路(迹) :图连通&&除两个端点外其余节点 入度=出度(s(起点):入度=出度-1 t(终点):出度=入度-1)
或者所有节点 入度=出度
欧拉回路(闭迹):图连通&&所有节点 入度=出度
混合图:
欧拉回路(闭迹):把所有无向边任意定向得到有向边,若有向图存在欧拉回路则原图存在欧拉回路。
(注:回路包含通路,如果是回路,那么一定是通路)
这题建图模型定下之后其实就是求判断是否为 混合图欧拉回路,如果是欧拉通路的话给起点终点加条边转换为求欧拉回路,不影响结果的正确性。
给 终点->起点 加条边,边权为1。
首先任意定向无向边为有向边。建图的时候,若为无向边则加边,方向任意定,边权为1。否则不加边,因为有向边已经不能改变,然后记录每个节点的出度和入度的差,为什么记录差?
如果差为k,k如果为偶数的话,那么如果我改变k/2条与这个节点相关的边就可以使得这个节点的 出度=入度 满足题意。
如果差为k,k如果为奇数的话,假设有两个节点的k为奇数那么其中一个是起点另一个是终点。从终点到起点建一条边,边权为1
如果差为k>0,那么说明 出度>入度,需要减少出度增加入度来使得 出度=入度,超级源点->该点,边权为k/2
如果差为k<0,那么说明 出度<入度,需要增加出度减少入度来使得 出度=入度,该点->超级汇点,边权为-k/2
如果差为k=0,那么说明 出度=入度,不鸟它。
如此建图之后判满流,如果满流则说明存在欧拉回路,是欧拉图。
ps:如果要求具体路径,那么假设判断原本定向的边边权是0还是1,比如:原本a--b是无向边,我定向为a->b并且边权为1,跑完最大流后我再判断通过这条边的流量是多少。如果通过这条边的流量为1则说明这条边需要改变方向为b->a
这题令我震惊的是只是转换建图方式就可以将哈密顿图转换为欧拉图求解。感觉很惊奇。因为至今还没有求解哈密顿图的有效方式。
ps:这题数据略水= =因为只有差为1和-1的才能建边。我看过很多代码,都有些小漏洞。。
我把here那里注释了过后时间是31MS,不注释那里时间是0MS。。好神奇。。
代码如下:
//author: CHC //First Edit Time: 2014-09-09 23:46 //Last Edit Time: 2014-09-10 00:32 #include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <set> #include <vector> #include <map> #include <queue> #include <set> #include <algorithm> #include <limits.h> using namespace std; typedef long long LL; const int MAXN=1e+4; const int MAXM=1e+5; const int INF = INT_MAX; struct Edge { int from,to,ci,next; Edge(){} Edge(int _from,int _to,int _ci,int _next):from(_from),to(_to),ci(_ci),next(_next){} }e[MAXM]; int head[MAXN],tot; int dis[MAXN]; int top,sta[MAXN],cur[MAXN]; inline void init(){ memset(head,-1,sizeof(head)); tot=0; } inline void AddEdge(int u,int v,int ci0,int ci1=0){ e[tot]=Edge(u,v,ci0,head[u]); head[u]=tot++; e[tot]=Edge(v,u,ci1,head[v]); head[v]=tot++; } inline bool bfs(int st,int et){ memset(dis,0,sizeof(dis)); dis[st]=1; queue <int> q;q.push(st); while(!q.empty()){ int now=q.front(); q.pop(); for(int i=head[now];i!=-1;i=e[i].next){ int next=e[i].to; if(e[i].ci&&!dis[next]){ dis[next]=dis[now]+1; if(next==et)return true; q.push(next); } } } return false; } LL Dinic(int st,int et){ LL ans=0; while(bfs(st,et)){ top=0; memcpy(cur,head,sizeof(head)); int u=st,i; while(1){ if(u==et){ int pos,minn=INF; for(i=0;i<top;i++){ if(minn>e[sta[i]].ci){ minn=e[sta[i]].ci; pos=i; } } for(i=0;i<top;i++){ e[sta[i]].ci-=minn; e[sta[i]^1].ci+=minn; } top=pos; u=e[sta[top]].from; ans+=minn; } for(i=cur[u];i!=-1;cur[u]=i=e[i].next) if(e[i].ci&&dis[u]+1==dis[e[i].to])break; if(cur[u]!=-1){ sta[top++]=cur[u]; u=e[cur[u]].to; } else { if(top==0)break; dis[u]=0; u=e[sta[--top]].from; } } } return ans; } int du[30]; int path[30],use[30]; int Find(int x){ return x==path[x]?x:path[x]=Find(path[x]); } int Union(int x,int y){ x=Find(x),y=Find(y); if(x==y)return false; path[x]=y; return true; } int main() { int t,n,cas=0; char str[30]; scanf("%d",&t); while(t--){ scanf("%d",&n); init(); for(int i=0;i<26;i++){ path[i]=i; use[i]=0; du[i]=0; } for(int i=0,x;i<n;i++){ scanf(" %s%d",str,&x); int st=str[0]-'a'; int et=str[strlen(str)-1]-'a'; du[st]++;du[et]--; use[st]=1;use[et]=1; if(x)AddEdge(st,et,1); Union(st,et); } int num1=0,num2=0,st=-1,et=-1; for(int i=0;i<26;i++) if(use[i]){ if(path[i]==i)++num1; if(du[i]%2){ ++num2; //here //if(du[i]<0)st=i; //if(du[i]>0)et=i; if(du[i]==-1)st=i; if(du[i]==1)et=i; } } printf("Case %d: ",++cas); if(num1!=1||num2==1||num2>2||(num2==2&&(st==-1||et==-1))){ puts("Poor boy!"); continue; } if(num2==2)AddEdge(et,st,1); st=26,et=27; LL sum=0; for(int i=0;i<26;i++) if(du[i]>0)AddEdge(st,i,du[i]/2),sum+=du[i]/2; else if(du[i]<0)AddEdge(i,et,-du[i]/2); if(Dinic(st,et)!=sum)puts("Poor boy!"); else puts("Well done!"); } scanf(" "); return 0; }