1. 程式人生 > >【省內訓練2018-09-13】Hamilton Path

【省內訓練2018-09-13】Hamilton Path

【思路要點】

  • 有一種樸素的 O(NM) 的做法,首先列舉路徑開始的位置,那麼從這個點開始的每一個點必須只存在一個沒有被訪問的後繼,因此我們可以在 O(M) 的時間內確定一個起始點出發是否有解,若有解,我們會找到一組唯一的解。

  • 對於有哈密爾頓迴路的資料,若我們先找到了一條哈密爾頓迴路,那麼對於一條非環邊,它會使一個區間內的點無法作為起始點,如圖中的紅點,因此最終可行的起始點一定是環上的一個區間,我們顯然能線上性的時間內找到可行的起始點區間並計算答案。

    image

  • 注意到一個起始點出發若不能夠找到解,那麼從它經過的點出發同樣不能夠找到解,如果我們將點集

    random_shuffle 一下,每次選取起始點都會期望刪除一半的錯誤起始點,因此期望 O(LogN) 次選取起始點後,我們能夠找到哈密爾頓迴路或者沒有起始點可以選擇,期望時間複雜度 O(MLogN)

  • 在比賽時筆者發現加上這個優化後程序獲得了 AC (下文程式碼就是這個做法)。

  • 事實上,當不存在哈密爾頓迴路,但問題有解的情況下,問題的解的數量不超過 2 (下文將給予說明)。這個演算法同樣能夠在 O(MLogN) 的期望時間複雜度內給出結果。但這個演算法在不存在哈密爾頓迴路,且問題無解的情況下的時間複雜度可能退化,考慮如下資料,該演算法將退化至

    O(N2)

    image

  • 其實也是存在解決方法的,由於有解的時候存在 O(MLogN) 的期望時間複雜度,所以若一個數據跑了很久沒有出解,那這個資料的答案就是無解。總時間複雜度 O(MLogN)

  • 以上是筆者在考場上的亂搞做法,下面我們來講這個題的正解。

  • 首先,若存在點 i ,滿足 indi2,outdi=1 ,其中 indi 表示 i 的入度, outdi 表示 i 的出度,那麼 i 的後繼結點必然是 i 唯一的出點,我們把 ii 的出點縮在一起考慮。

  • 注意到此時若圖中存在哈密爾頓迴路,那麼縮點後的圖去掉自環後將是一個環,或者出現雙向邊(此時問題無解),接下來我們認為圖中不存在哈密爾頓迴路。

  • 此時,剩餘的點中,若存在 indi=0 的點,那麼它一定是起始點,若有多個這樣的點問題顯然無解。

  • 我們將剩餘的點分為三類: ind=outd=1 的點稱為 A 類點, outd2 的點稱為 B 類點, outd=0 的點稱為 C 類點, C 類點顯然只能有一個。

  • 只有 A 類點可能成為起始點,其餘點由於出度不對,無法成為起始點。

  • 一個能夠作為起始點的 A 類點應當滿足沿著它的出邊走到的第一個點非 A 類點為 B 類點,且該 B 類點存在一條指向該 A 類點的出邊,如下圖。

    image

  • 注意到該圖中 A 點沒有其他的入邊,也就是說,若 A 點不是起始點,這樣的結構一旦進入,就無法走出去了,因此這樣的結構在圖中至多存在兩個,否則問題無解。

  • 那麼,我們只需要暴力檢驗這兩個起始點是否能夠得到一組解即可。

  • 時間複雜度 O(N+M)

【程式碼】


#include<bits/stdc++.h>

using namespace std;
const int MAXN = 5e5 + 5;
const int MAXM = 1e6 + 5;
const int P = 1e9 + 7;
template <typename T> void read(T &x) {
  x = 0; int f = 1;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -f;
  for (; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
  x *= f;
}
template <typename T> void write(T x) {
  if (x < 0) x = -x, putchar('-');
  if (x > 9) write(x / 10);
  putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
  write(x);
  puts("");
}
bool solved, vis[MAXN], ban[MAXN];
int n, m, bit[MAXN];
int x[MAXM], y[MAXM];
int p[MAXN], home[MAXN];
int cnt, ans[MAXN];
vector <int> a[MAXN];
void check(int pos, int sum) {
  bool loop = false;
  for (unsigned i = 0; i < a[pos].size(); i++)
      if (a[pos][i] == p[1]) loop = true;
  if (!loop) {
      ans[++cnt] = sum;
      return;
  }
  solved = true;
  static int d[MAXN * 2];
  for (int i = 1; i <= 2 * n; i++)
      d[i] = 0;
  cnt = 0;
  for (int i = 1; i <= n; i++) {
      ans[i] = -1;
      home[p[i]] = i;
  }
  for (int i = 1; i <= m; i++) {
      int tx = home[x[i]], ty = home[y[i]];
      if (tx + 1 == ty || (tx == n && ty == 1)) continue;
      if (tx < ty) tx += n;
      d[ty + 1]++, d[tx + 1]--;
  }
  for (int i = 1; i <= 2 * n; i++)
      d[i] += d[i - 1];
  for (int i = 1; i <= n; i++) {
      if (d[i] == 0 && d[i + n] == 0) {
          cnt++;
          ans[p[i]] = sum;
      }
      sum = (sum - 1ll * bit[n - 1] * p[i] % P + P) % P;
      sum = (sum * 10ll + p[i]) % P;
  }
  writeln(cnt);
  for (int i = 1; i <= n; i++)
      if (ans[i] != -1) {
          write(ans[i]);
          putchar(' ');
      }
  printf("\n");
  return;
}
void work(int pos, int sum) {
  for (int i = 1; i <= n; i++) {
      sum = (sum + 1ll * bit[n - i] * pos) % P;
      p[i] = pos;
      if (i == n) {
          check(pos, sum);
          for (int j = 1; j <= n; j++)
              vis[j] = false;
          return;
      }
      vis[pos] = true;
      int dest = 0;
      for (unsigned j = 0; j < a[pos].size(); j++)
          if (!vis[a[pos][j]]) {
              if (dest == 0) dest = a[pos][j];
              else {
                  for (int k = 1; k <= i; k++) {
                      vis[p[k]] = false;
                      ban[p[k]] = true;
                  }
                  return;
              }
          }
      if (!dest) {
          for (int k = 1; k <= i; k++) {
              vis[p[k]] = false;
              ban[p[k]] = true;
          }
          return;
      }
      pos = dest;
  }
}
int main() {
  int T; read(T);
  while (T--) {
      read(n), read(m);
      bit[0] = 1;
      for (int i = 1; i <= n; i++) {
          a[i].clear(), ban[i] = false;
          bit[i] = bit[i - 1] * 10ll % P;
      }
      for (int i = 1; i <= m; i++) {
          read(x[i]), read(y[i]);
          a[x[i]].push_back(y[i]);
      }
      for (int i = 1; i <= n; i++) {
          sort(a[i].begin(), a[i].end());
          a[i].resize(unique(a[i].begin(), a[i].end()) - a[i].begin());
      }
      cnt = 0, solved = false;
      for (int i = 1; i <= n; i++) {
          if (!ban[i]) work(i, 0);
          if (solved) break;
      }
      if (!solved) {
          printf("%d\n", cnt);
          if (cnt >= 1 && cnt <= n) {
              for (int i = 1; i <= cnt; i++)
                  printf("%d ", ans[i]);
              printf("\n");
          }
      }
  }
  return 0;
}

【標程】


#include <bits/stdc++.h>

using namespace std;

#define rep(i,a,n) for (int i=a;i<n;i++)


#define per(i,a,n) for (int i=n-1;i>=a;i--)


#define pb push_back


#define mp make_pair


#define all(x) (x).begin(),(x).end()


#define fi first


#define se second


#define SZ(x) ((int)(x).size())

typedef vector<int> VI;
typedef long long ll;
typedef pair<int,int> PII;
const ll mod=1000000007;
ll powmod(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
ll gcd(ll a,ll b) { return b?gcd(b,a%b):a;}
// head

const int N=501000;
VI e[N],r[N];
int he[N];
int n,m,u,v,vis[N],mt,ret[N],_;
int f[N],pre[N],nxt[N],t[N];
int ind[N],oud[N],cnt[N];

            
           

相關推薦

省內訓練2018-09-13Hamilton Path

【思路要點】 有一種樸素的 O(N∗M)O(N∗M) 的做法,首先列舉路徑開始的位置,那麼從這個點開始的每一個點必須只存在一個沒有被訪問的後繼,因此我們可以在 O(M)O(M) 的時間內確定一個起始點出發是否有解,若有解,我們會找到一組唯一的解。

省內訓練2018-10-28網友串

【思路要點】 首先,奇數和偶數可以分開處理,最後再將答案卷積得到最終答案。 預處理每一對網友數是否能夠構成混沌串。 我們用三元組 (

省內訓練2018-10-28排序二叉樹

【思路要點】 若將一個序列按照元素大小排序,那麼其對應的排序二叉樹即為插入時間對應的笛卡爾樹,並且,被刪除的元素可以視作插入時間為正無窮的元素。 一個點在排序二叉樹上所有的祖先即其左側/右側對應的所有插入時間為後/字首最小值的點。 離線操作,對權值離

省內訓練2018-10-26矩陣

【思路要點】 用十字連結串列維護整個矩陣,操作時將被操作的子矩形提取出來,改變周圍一圈點的連邊,再拼接回去即可,單次操作修改的邊數是 O

省內訓練2018-10-26網友數

【思路要點】 首先,當 k ≥ 9

省內訓練2018-10-26遊走

【思路要點】 考慮一個指數暴力,首先列舉每一個位置選擇 “見好就收” 還是 “得寸進尺” 。 記 E

省內訓練2018-11-25Factorization

【思路要點】 用類似 M i

省內訓練2018-11-25Decomposition

【思路要點】 考慮計算每一個數的貢獻,即列舉一個數 i i

省內訓練2018-11-23Bishop

【思路要點】 先考慮一個子問題,在 N ∗

省內訓練2018-11-23Palindrome

【思路要點】 考慮從兩端向中間 d p

省內訓練2018-11-23Graph

【思路要點】 離線詢問,為每一條邊找到一個刪除時間。 將過程倒過來,按照刪除時間倒序加入每一條邊。 我們將加入的邊分為兩類,加入後連線兩個不同的聯通塊的稱為樹邊,剩餘的邊稱為非樹邊。 顯然,樹邊的加入不會產生新的雙連通分量,因此,我們可以預先

校內訓練2018-10-19Gift

【思路要點】 首先,若不存在 0 0

校內訓練2018 10 19貓哭 二分 / 貪心

題 給定一個大寫英文字母串,問最多能將原串分為多少個形如 CATCATCAT 或 TATTATTAT 的子序列? 如 CATATCATATCATAT,僅能分出一個。而 CATTATCATTATCATTA

2018/09/13《塗抹MySQL》MySQL復制特性學習筆記(六)

ref nor affect 來看 like 從數據 b2c img 密碼 推薦一首歌   - 《可不可以》張紫豪 好吧,隨便從排行榜上找了一首 讀   第十一章《MySQL的復制特性》 總結 1:復制(Replication) 應用場景?   - 提高性能 (通過

躍遷之路585天程式設計師高效學習方法論探索系列(實驗階段342-2018.09.13

@(躍遷之路)專欄 【躍遷之路】獎勵金計劃正式開始 從2018.7.1起,【躍遷之路】獎勵金計劃正式起航,從今以後,, 每月1日,我會將自己個人上月收入的1%計入【躍遷之路】獎勵金池,積累到足夠金額後,將適時用於獎勵那些雖然身處困境,卻依然不放棄努力,通過堅持,不斷

2018.03.13Linux基本指令+Vim編輯器+重定向+正則表達式

list lis 3.1 end 邊界 多字節 並不會 模式 oca 一、Linux基本指令 find -name:按照文件名進行查找 文件搜索 不設置參數時,find默認在當前目錄下查找其子目錄及文件,並顯示查找的全部子目錄及文件 -size:按照文件大

JS 2018.09.13訓練題筆記

1、JavaScript程式中,alert(undefined == null)的輸出結果是     ture;     解析:undefined值是派生自null值的,因此ECMA-262規定對它們的相等性測試要返回true。 

度小滿2018-09-26線上筆試ONU

題目描述 ONU是一種新型桌遊,一副牌有若干種花色,總共N張,且每種花色的牌的張數一樣。現在每次給定N,M,表示這幅總共N張的牌至少有M種花色,請問這副牌可能的花色有多少種? 輸入 共一行,兩個整數N,M。(1<=N<=1012,0<=M<=1012)

華為2018-09-15線上筆試查詢最後一個只出現一次的字元

題目描述 在一個給定的字串中找到最後一個只出現一次的字元。 輸入描述: 一個定長的字串,其中字元沒有順序,字元可以重複/ 輸出描述: 一個字元。最後一個只出現一次的字元;如果字元的出現次數都是

華為2018-09-15線上筆試十進位制20位資料乘法

題目描述 十進位制20位資料乘法 輸入描述: 兩個不超過20位都不為0的十進位制字串 輸出描述: 字串相乘結果 示例1 輸入 20000000000000000000 30000000