AtCoder Grand Contest 058 部分題目不簡要題解

從這裏開始

Problem A Make it Zigzag

  考慮使 $1, 3, 5, 7, \cdots, 2n - 3$ 這些位置後三個中的最大值在中間,最後再處理一下最後兩個位置就行了。

Code

#include <bits/stdc++.h>
using namespace std;

const int N = 2e5 + 5;

int n;
int a[N];
vector<int> ans;

int maxp(int a, int b, int c) {
  if (c > max(a, b)) return 2;
  if (b > max(a, c)) return 1;
  return 0;
}

int main() {
  scanf("%d", &n);
  for (int i = 1; i <= (n << 1); i++) {
    scanf("%d", a + i);
  }
  for (int i = 1; i <= (n << 1) - 2; i += 2) {
    int d = maxp(a[i], a[i + 1], a[i + 2]);
    if (d != 1) {
      ans.push_back(min(i + d, i + 1));
      swap(a[i + d], a[i + 1]);
    }
  }
  int n2 = n << 1;
  if (a[n2 - 1] > a[n2]) {
    ans.push_back(n2 - 1);
  }
  printf("%d\n", (signed) ans.size());
  for (auto x : ans) {
    printf("%d ", x);
  }
  return 0;
}

Problem B Adjacent Chmax

  考慮其實問題相當於一個較大數可以把較小數覆蓋掉。

  依次考慮原序列每個數在最終序列中佔了多長一段。容易發現只需要滿足佔的那一段裏沒有比它大的數就可以。

  然後就是個簡單 dp。

Code

#include <bits/stdc++.h>
using namespace std;

#define ll long long

void exgcd(int a, int b, int& x, int& y) {
	if (!b) {
		x = 1, y = 0;
	} else {
		exgcd(b, a % b, y, x);
		y -= (a / b) * x;
	}
}

int inv(int a, int n) {
	int x, y;
	exgcd(a, n, x, y);
	return (x < 0) ? (x + n) : (x);
}

const int Mod = 998244353;

template <const int Mod = :: Mod>
class Z {
	public:
		int v;

		Z() : v(0) {	}
		Z(int x) : v(x){	}
		Z(ll x) : v(x % Mod) {	}

		friend Z operator + (const Z& a, const Z& b) {
			int x;
			return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x));
		}
		friend Z operator - (const Z& a, const Z& b) {
			int x;
			return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x));
		}
		friend Z operator * (const Z& a, const Z& b) {
			return Z(a.v * 1ll * b.v);
		}
		friend Z operator ~(const Z& a) {
			return inv(a.v, Mod);
		}
		friend Z operator - (const Z& a) {
			return Z(0) - a;
		}
		Z& operator += (Z b) {
			return *this = *this + b;
		}
		Z& operator -= (Z b) {
			return *this = *this - b;
		}
		Z& operator *= (Z b) {
			return *this = *this * b;
		}
		friend bool operator == (const Z& a, const Z& b) {
			return a.v == b.v;
		} 
};

Z<> qpow(Z<> a, int p) {
	Z<> rt = Z<>(1), pa = a;
	for ( ; p; p >>= 1, pa = pa * pa) {
		if (p & 1) {
			rt = rt * pa;
		}
	}
	return rt;
}

typedef Z<> Zi;

const int N = 5005;

template <typename T>
bool vmax(T& a, T b) {
  return a < b ? (a = b, true) : false;
}

int n;
int vis[N];
int a[N], fa[N];
Zi f[N], g[N];

void build(int l, int r, int v) {
  if (l > r) {
    return;
  }
  int mx = -1, mxp = 0;
  for (int i = l; i <= r; i++) {
    if (vmax(mx, a[i])) {
      mxp = i;
    }
  }
  fa[mxp] = v;
  build(l, mxp - 1, mxp);
  build(mxp + 1, r, mxp);
}

int main() {
  scanf("%d", &n);
  for (int i = 1; i <= n; i++) {
    scanf("%d", a + i);
  }
  a[0] = N, a[n + 1] = N;
  f[0] = 1;
  for (int i = 1; i <= n; i++) {
    int l = i, r = i;
    while (a[l - 1] < a[i]) l--;
    while (a[r + 1] < a[i]) r++;
    Zi s = 0;
    for (int j = l; j <= r; j++) {
      g[j] += (s += f[j - 1]);
    }
    for (int j = 0; j <= n; j++) {
      g[j] += f[j];
      f[j] = g[j];
      g[j] = 0;
    }
  }
  printf("%d\n", f[n].v);
  return 0;
}

Problem C Planar Tree

  差點不會這題,感覺好毒瘤啊。

  如果只有 1, 2 這個題瞎做就好了。

  注意到如果奇數和偶數都能連,就是隻有 1, 2 的情況,可是 1, 4 不能連。考慮先把這兩個煩人的東西幹掉。

  考慮 $(1, 2)$,$(3, 4)$ 相鄰的時候直接連起來,然後分別當做只有一個點 2 或者 3。假如原問題有解且不存在這條邊,那麼顯然這條邊可以加上去,然後環上隨便刪一條邊就行了。因此原問題和新問題有解性等價。

  注意到如果有連續兩個數是相同的,那麼可以在其中 1 個連邊的時候,兩個一起連。考慮把這兩個縮一起。和上面同樣的方法證明兩個問題有解性等價,只是可以加邊的原因略有不同。

  現在問題變成 1 不和 2 相鄰,4 不和 3 相鄰,相鄰的數不同,目標把所有的 1, 4 連到別的點上。可以發現,樹上不跨過 4 的 $(1, 2)$ 相連會導致至少 1 個 $3$ 不能繼續和 4 相連。(如果有跨過 4 的話就考慮這個 $(3, 4)$,這樣處理下去可能會得到不跨過 2 的 $(3, 4)$ 相連,但和這個情形是相似的)這樣的情況一定是 $(1, 3, 2)$,考慮把這個縮成 $2$,繼續這個操作。所以每消去一個 1 就會減少一個 3,每消去一個 4 就會減少一個 2。

  如果不存在 $(1, 3, 2)$ 也不存 $(4, 2, 3)$,那麼說明不存在 $(2, 3)$ 相鄰的情況,此時可以得到 $4$ 的數量大於等於 $2$ 的數量, $3$ 的數量大於等於 $1$ 的數量(等於是因爲可能都沒有),此時顯然不可能。

  相反如果滿足 $2$ 的數量加上 $3$ 的數量大於 $1$ 的數量加上 $4$ 的數量,同時 1 的數量大於等於 3 的數量,4 的數量大於等於 2 的數量,因爲每次兩個數量和同時減少,所以不可能出現不存在 $(2, 3)$ 相鄰的情形。因此只用處理一下,然後數數量就可以了。

Code

#include <bits/stdc++.h>
using namespace std;

const int N = 3e5 + 5;

int T, n;
int a[N], stk[N], cnt[5];

int merge(int x, int y) {
  if (x > y) swap(x, y);
  if (x == y) {
    return x;
  }
  if (x + 1 == y && x != 2) {
    return x == 1 ? y : x;
  }
  return 0;
}

void solve() {
  scanf("%d", &n);
  for (int i = 1; i <= n; i++) {
     scanf("%d", a + i);
  }
  int pos = 1, top = 1;
  while (a[pos] == a[1]) pos++;
  rotate(a + 1, a + pos, a + n + 1);
  stk[top] = a[1];
  for (int i = 2, x; i <= n; i++) {
    x = merge(stk[top], a[i]);
    if (x) {
      stk[top] = x;
    } else {
      stk[++top] = a[i];
    }
  }
  int frt = 1, tmp;
  while (frt < top && (tmp = merge(stk[frt], stk[top]))) {
    stk[top] = tmp;
    frt++;
  }
  memset(cnt, 0, sizeof(cnt));
  for (int i = frt; i <= top; i++) {
    cnt[stk[i]]++;
  }
  if (cnt[4] <= cnt[2] && cnt[1] <= cnt[3] && cnt[1] + cnt[4] < cnt[2] + cnt[3]) {
    puts("Yes");
  } else {
    puts("No");
  }
}

int main() {
  scanf("%d", &T);
  while (T--) {
    solve();
  }
  return 0;
}

Problem D Yet Another ABC String

  這種題比那種無聊的猜結論題有意思多了。

  樸素容斥的話相當於硬點若干個位置,然後這個位置起始長度爲 3 的子串不合法。有重疊的話不好處理,主要因爲 3 種字符的數量有限制。

  考慮讓硬點的位置後面一定不和它相連。就是 ABC 後面不是 A。相當於就是對每個這樣連續的 ABCABCAB 末做一個子集容斥。

  然後就是個簡單組合計數。

Code

#include <bits/stdc++.h>
using namespace std;

#define ll long long

void exgcd(int a, int b, int& x, int& y) {
	if (!b) {
		x = 1, y = 0;
	} else {
		exgcd(b, a % b, y, x);
		y -= (a / b) * x;
	}
}

int inv(int a, int n) {
	int x, y;
	exgcd(a, n, x, y);
	return (x < 0) ? (x + n) : (x);
}

const int Mod = 998244353;

template <const int Mod = :: Mod>
class Z {
	public:
		int v;

		Z() : v(0) {	}
		Z(int x) : v(x){	}
		Z(ll x) : v(x % Mod) {	}

		friend Z operator + (const Z& a, const Z& b) {
			int x;
			return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x));
		}
		friend Z operator - (const Z& a, const Z& b) {
			int x;
			return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x));
		}
		friend Z operator * (const Z& a, const Z& b) {
			return Z(a.v * 1ll * b.v);
		}
		friend Z operator ~(const Z& a) {
			return inv(a.v, Mod);
		}
		friend Z operator - (const Z& a) {
			return Z(0) - a;
		}
		Z& operator += (Z b) {
			return *this = *this + b;
		}
		Z& operator -= (Z b) {
			return *this = *this - b;
		}
		Z& operator *= (Z b) {
			return *this = *this * b;
		}
		friend bool operator == (const Z& a, const Z& b) {
			return a.v == b.v;
		} 
};

Z<> qpow(Z<> a, int p) {
	Z<> rt = Z<>(1), pa = a;
	for ( ; p; p >>= 1, pa = pa * pa) {
		if (p & 1) {
			rt = rt * pa;
		}
	}
	return rt;
}

typedef Z<> Zi;

const int N = 3e6 + 5;

int A, B, C, S;

Zi fac[N], _fac[N], pw2[N];

void init(int n) {
  fac[0] = 1;
  for (int i = 1; i <= n; i++) {
    fac[i] = fac[i - 1] * i;
  }
  _fac[n] = ~fac[n];
  for (int i = n; i; i--) {
    _fac[i - 1] = _fac[i] * i;
  }
  pw2[0] = 1;
  for (int i = 1; i <= n; i++) {
    pw2[i] = pw2[i - 1] * 2;
  }
}

void cswap(int& a, int& b) {
  if (a > b) {
    swap(a, b);
  }
}

Zi comb(int n, int m) {
  return n < m ? 0 : fac[n] * _fac[m] * _fac[n - m];
}

int main() {
  scanf("%d%d%d", &A, &B, &C);
  S = A + B + C;
  cswap(B, C), cswap(A, B), cswap(B, C);
  init(S);
  Zi ans = 0, tmp;
  for (int i = 0; i <= A; i++) {
    // the last one isn't included
    int rest = S - 3 * i;
    tmp = comb(rest + i - 1, i) * pw2[i] * fac[rest] * _fac[A - i] * _fac[B - i] * _fac[C - i];
    // the last one is included
    if (i) {
      tmp += comb(rest + i - 1, i - 1) * pw2[i - 1] * 3 * fac[rest] * _fac[A - i] * _fac[B - i] * _fac[C - i];
    }
    if (i & 1) {
      ans -= tmp;
    } else {
      ans += tmp;
    }
  }
  printf("%d\n", ans.v);
  return 0;
}

Problem E Nearer Permutation

  感覺這種題和碼農題一樣無聊,反正順着思路做就完了,每步難度不大,就是思考的過程特別長。雖然也有可能是因爲我菜。

  考慮怎麼找出 $z$。

  首先考慮怎麼算 $d(x, z)$,相當於將 $x$ 第一個位置上的數,在 $z$ 中改爲 $1$,第二位置上的數,在 $z$ 中改爲 $2$,然後求 $z'$ 的逆序對數。

  這個顯然相當於 $i$ 在兩個排列中出現的位置構成的有序二元組的逆序對數。

  那麼現在有 2 列二元組,每列二元組只知道其中 1 個數,從小到大把 $1$ 到 $n$ 填進去,每次儘可能填較小的數對應的二元組。

  現在問題變成判定把剩下的填進來其中 1 列的逆序對數是否不超過另一列的逆序對數。

  可以發現,只有每對 $i$ 在 $x$ 和 $y$ 中出現的位置構成的有序二元組的逆序對會對差有貢獻,並且貢獻爲 $+1$ 或者 $-1$。(其實這個逆序對其實就是 $i$ 在 $x$ 中出現的位置 $p_i$ 的逆序對)如果 $p_i > p_j (i < j)$ 並且 $(p_i, ?)$ 被先填上數,那麼會產生 $+1$ 的差。(最終需要差小於等於 0)我們下面稱違反一個逆序對 $(i, j) (i < j, p_i > p_j)$ 指先在 $(p_i, ?)$ 填數。

  因此我們可以至多違反逆序對總數除以 $2$ 向下取整個逆序對。

  然後很容易得到一個明確的求 $z$ 的做法:假設當前在確定第 $i$ 個位置上的數,找儘量小的 $j$ 滿足上一條限制,然後填上 $(p_j, i)$。

  現在回到原問題。

  現在我們要填 $p_1, \cdots, p_n$,題目指定了上面做法填 $(p_i, ?)$ 的順序。

  當我們填了一個 $(p_i, ?)$  且前面有沒有填的 $(p_j, ?)$ 時相當於限制此時能違反的數量小於填 $(p_j, ?)$ 會違反的數量。考慮每次填的過程,允許違反的數量每次單調不增,後者至多減少 1。所以如果下一次填的位置 $(p_k, ?)$ 滿足 $k < i$,那麼意味着上面這個差是剛好變爲 $0$,所以填完 $(p_k, ?)$ 後不能再違反任何逆序對。這之後只能每次填 $p$ 最小的沒填的位置。

  我們將 $p_i$ 分爲 4 類:

  • 第一類位置:滿足 $i = z_i$
  • 第三類位置:第一個滿足 $z_i > z_{i + 1}$ 的 $z_{i + 1}$
  • 第二類位置:設第三類位置爲 $z_t$,那麼在 $z_1, \cdots, z_{t - 1}$ 中滿足 $z_i > i$ 的爲第二類位置
  • 第四類位置:剩下的

  容易發現對於第一類位置沒有限制。如果一個現在要填的位置 $p_i$,之前還有沒有填的位置 $p_j$,那麼一定滿足 $p_i < p_j$,否則能違反的限制數一定滿足能先填 $p_j$。因此在不考慮第三類位置的情況下,第二類位置的限制在處理出第四類位置的 $p$ 的相對順序後就已知了。

  我們希望使得逆序對數減去 2 倍被違反的逆序對數爲 $0$ 或者 $1$。我們先考慮這個東西的最小值。注意到每產生一個被違反的逆序對的時候對這個值的貢獻爲 $-1$。

  在不考慮第三類位置的時候,最優方案爲第一類位置依次填 $n, n - 1, \cdots$,第一個第二類位置填能填最大的數(能違反的逆序對數到這裏的時候最大值就是這個位置上的限制)剩下按順序填最小的數。

  注意到你可以連續地把這個值加上 1,所以如果第三類位置在第一個第二類位置的後面就做完了。

  考慮第三類位置在所有第二類位置之前的情形。

  如果第三類位置填上後後面有 $x$ 個數比它小,那麼到這個位置的時候允許違反的逆序對數也是 $x$。第三類位置對於倒數第 $i$ 個被填上的第二類位置有一個限制:這後面至多有 $i - 1$ 個數比它小(注意求 $z$ 的過程運行到這裏的時候允許違反的逆序對數是 $x$)。此時顯然最優的話把第一個第二類位置填能填的最大的,剩下的沒填的按順序填能填的最小的。因此我們只用求一下最大可能的 $x$ 即可。(注意這裏所有的第二類位置都對這個第三類位置有限制,因爲經過第一個第二類位置後,允許違反的逆序對數是 $x$ 不是 $0$)。

Code

#include <bits/stdc++.h>
using namespace std;

template <typename T>
bool vmin(T& a, T b) {
  return a > b ? (a = b, true) : false;
}

const int N = 3e5 + 5;

typedef class Fenwick {
  public:
    int n;
    int a[N];

    void init(int n) {
      this->n = n;
      fill(a, a + n + 1, 0);
    }
    
#define lowbit(_) ((_) & -(_)) 
    void add(int idx, int val) {
      for ( ; idx <= n; idx += lowbit(idx)) {
        a[idx] += val;
      }
    }
    int query(int idx) {
      int ret = 0;
      for ( ; idx; idx -= lowbit(idx)) {
        ret += a[idx];
      }
      return ret;
    }
    int query(int l, int r) {
      return query(r) - query(l - 1);
    }
} Fenwick;

int T, n;
Fenwick fen;
int a[N], les[N];

void solve() {
  scanf("%d", &n);
  for (int i = 1; i <= n; i++) {
    scanf("%d", a + i);
  }
  int p1, p2;
  for (p1 = 1; p1 <= n && a[p1] == p1; p1++);
  if (p1 >= n) {
    puts("Yes");
    return;
  }
  for (p2 = p1 + 1; p2 <= n && a[p2] > a[p2 - 1]; p2++);
  
  long long dif = -1ll * (n + n - p1) * (p1 - 1) / 2;   
  fen.init(n);
  for (int i = n; i >= p1; i--) {
    int p = a[i];
    dif += fen.query(p);
    fen.add(p, 1);
  }
  for (int i = 1; i <= n; i++) {
    les[i] = n + 1;
  }
  fen.init(n);
  for (int i = p2; i <= n; i++) {
    int p = a[i];
    if (i != p2) {
      les[p] = fen.query(p, n);
    }
    fen.add(p, 1);
  }
  if (a[p2] > a[p1]) {
    int lim = n + 1;
    for (int i = p1; i <= a[p1]; i++) {
      vmin(lim, les[i]);
    }
    dif -= lim + (p2 - p1 - 1);
  } else {
    int lim = n - a[p2] - (p2 - p1);
    for (int i = 2; i <= n; i++) {
      vmin(les[i], les[i - 1]);
    }
    for (int i = p1; i < p2; i++) {
      vmin(lim, les[a[i]] + p2 - i - 1);
    }
    vmin(lim, les[a[p2]] - 1);
    dif -= lim + p2 - p1 - 1;
  }
  puts((dif < 2) ? "Yes" : "No");
}

int main() {
  scanf("%d", &T);
  while (T--) {
    solve();
  }
  return 0;
}

Problem F Authentic Tree DP

  別催了,在路上了.jpg

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章