圖論之假面舞會
阿新 • • 發佈:2020-07-15
題目
(懶)
[NOI2008]假面舞會
思路
對於給出的圖,存雙向邊,正向長度為1,反向長度為-1,我們可以將它處理為環和鏈
- 對於單個的環來說,其中k最大可能為環中的節點個數,其因數都為該環k的個數的可能情況—>可以推得多個環的最大可能為多個環的最大公因數(因為所有環都要滿足),最小可能為最大公因數的最小因數(所有因數都有可能,這裡取最小的);
- 對於無環的鏈來說,最大可能為圖中所有鏈的長度總和,最小可能為k的最低限制3;
所以綜上
在k>=3時
最大值:有環情況下,為所有環的節點個數的最大公因數,無環為所有鏈長之和
最小值:有換情況下,為所有環的節點個數的最大公因數的最小的因數(>=3),無環情況下為為k最小取值3
在k<3時
最大值:-1
最小值:-1
程式碼如下
#include<bits/stdc++.h> using namespace std; const int maxn=1e5+10,maxm=2e6+10; int head[maxn],ver[maxm],edge[maxm],Next[maxm],tot=1; bool vis[maxn];//記錄點的訪問情況 bool flag[maxm];//記錄邊的訪問情況 int mx,mn,ans,m,n; int d[maxn];//記錄dfs初始點到該節點的距離 int gcd(int a,int b){//求最大公因數 return b==0?a:gcd(b,a%b); } void add(int x,int y,int z){ ver[++tot]=y,edge[tot]=z,Next[tot]=head[x],head[x]=tot; } void DFS(int now){ vis[now]=1; for(int i=head[now];i;i=Next[i]){ int to=ver[i]; if(!vis[to]){ d[to]=d[now]+edge[i]; DFS(to); } else ans=gcd(ans,abs(d[now]+edge[i]-d[to])); } } void dfs(int now){ vis[now]=1; mx=max(mx,d[now]);//更新最大距離 mn=min(mn,d[now]);//更新最小距離 for(int i=head[now];i;i=Next[i]){ if(!flag[i]){ flag[i]=flag[i^1]=1;//標記反向邊,使其只能向一邊走 int to=ver[i]; d[to]=d[now]+edge[i]; dfs(to); } } } int main(){ scanf("%d%d",&n,&m); for(int i=1,x,y;i<=m;i++){//正向加正邊,反向加反邊 scanf("%d%d",&x,&y); add(x,y,1); add(y,x,-1); } for(int i=1;i<=n;i++){//判環 if(!vis[i])DFS(i); } if(ans){ if(ans<3){//不符合k>=3 printf("-1 -1\n"); return 0; } else{ int x; for(x=3;x<=ans;x++)if(ans%x==0)break;//求最小因數為最小可能 printf("%d %d\n",ans,x); return 0; } } //尋找環失敗,開始尋找鏈 memset(vis,0,sizeof(vis)); for(int i=1;i<=n;i++){ if(!vis[i]){ mx=mn=d[i]=0; //初始化最大值最小值,因為正向為1,反向為-1,並且初始點不一定是兩端 dfs(i); ans+=mx-mn+1;//最大距離(正)減去最小距離(負)為鏈長 } } if(ans>=3)printf("%d 3\n",ans); else printf("-1 -1\n");//不符合k>=3 return 0; }