AtCoder Grand Contest 043 簡要題解

從這裏開始

Problem A Range Flip Find Route

  考慮對於一條路徑的答案是交錯的次數除以 2 向上取整。 dp 即可。

Code

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

const int N = 105;
const int inf = 1e9 + 7;

int H, W;
char G[N][N];
int f[N][N];

int main() {
	scanf("%d%d", &H, &W);
	for (int i = 1; i <= H; i++) {
		scanf("%s", G[i] + 1);
	}
	f[1][1] = (G[1][1] != '.');
	for (int s = 3; s <= H + W; s++) {
		for (int x = max(s - W, 1); x <= H && x < s; x++) {
			int y = s - x;
			f[x][y] = 1e9 + 7;
			if (x > 1)
				f[x][y] = min(f[x][y], f[x - 1][y] + (G[x - 1][y] != G[x][y]));
			if (y > 1)
				f[x][y] = min(f[x][y], f[x][y - 1] + (G[x][y - 1] != G[x][y]));
		}
	}
	int ans = ((G[H][W] != '.') + f[H][W] + 1) >> 1; 
	printf("%d\n", ans);
	return 0;
}

Problem B 123 Triangle

  你可以根據第一輪差判斷答案是否可能是 2。如果是 2,說明第一輪差分後只有 0 和 2,把 2 當成 1。

  然後問題都相當於判斷模 2 最後一位是否是 1。

Code

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

const int N = 1e6 + 5;

int n;
char s[N];

int main() {
	scanf("%d", &n);
	scanf("%s", s + 1);
	boolean flag2 = true;
	for (int i = 1; i < n; i++) {
		if (s[i + 1] >= s[i]) {
			s[i] = s[i + 1] - s[i];
		} else {
			s[i] = s[i] - s[i + 1];	
		}
		if (s[i] == 1) {
			flag2 = false;
		}
	}
	if (flag2) {
		for (int i = 1; i < n; i++) {
			if (s[i] == 2) {
				s[i] = 1;
			}
		}
	} else {
		for (int i = 1; i < n; i++) {
			if (s[i] == 2) {
				s[i] = 0;
			}
		}
	}
	int ans = 0;
	--n;
	for (int i = 0; i < n; i++) {
		if ((i & (n - 1)) == i) {
			ans += s[i + 1];
		}
	}
	ans = (ans & 1) * (1 + flag2);
	printf("%d\n", ans);
	return 0;
}

Problem C Giant Graph

  注意到這個標記的過程和博弈有點像。考慮這樣一個問題,Alice 和 Bob 輪流移動有向圖上的一個棋子。

  然後相信大家都會了。

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 = 1e5 + 5;

Zi base (1000000000000000000ll);
Zi pw[N];

void init_pw(int n) {
	pw[0] = 1;
	pw[1] = base;
	for (int i = 2; i <= n; i++) {
		pw[i] = pw[i - 1] * base;
	}
}

int n;

typedef class Graph {
	public:
		bitset<512> vis;
		int SG[N];
    Zi buk[512];
    vector<int> G[N];

		Zi* solve() {
      int u, v, m;
      scanf("%d", &m);
			while (m--) {
				scanf("%d%d", &u, &v);
			  if (u > v) {
          swap(u, v);
        }
        G[u].push_back(v);
      }
      for (int i = n; i; i--) {
        for (auto e : G[i]) {
          vis.set(SG[e]);
        }
        while (vis.test(SG[i]))
          SG[i]++;
        for (auto e : G[i]) {
          vis.reset(SG[e]);
        }
      }
      for (int i = 1; i <= n; i++) {
        buk[SG[i]] += pw[i];
      }
      return buk;
		}
} Graph;

Graph g1, g2, g3;

void fwt(Zi* f, int n) {
  int N = 1 << n;
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < N; j++) {
      if ((j >> i) & 1) {
        Zi a = f[j ^ (1 << i)], b = f[j];
        f[j ^ (1 << i)] = a + b;
        f[j] = a - b;
      }
    }
  }
}
void ifwt(Zi* f, int n) {
  fwt(f, n);
  int N = 1 << n;
  Zi invlen = ~Zi(N);
  for (int i = 0; i < N; i++) {
    f[i] *= invlen;
  }
}

int main() {
  scanf("%d", &n);
  init_pw(n);
  Zi* b1 = g1.solve();
  Zi* b2 = g2.solve();
  Zi* b3 = g3.solve();
  fwt(b1, 9);
  fwt(b2, 9);
  fwt(b3, 9);
  for (int i = 0; i < 512; i++) {
    b1[i] = b1[i] * b2[i] * b3[i];
  }
  ifwt(b1, 9);
  printf("%d\n", b1->v);
	return 0;
}

Problem D Merge Triplets

  考慮最小數,顯然它必須塞進前面的數所在序列中,然後把它刪掉。

  因此條件是:

  • 考慮前綴最大值,兩個前綴最大值間的數小於等於 2
  • 長度爲 1 的段的數量大於等於長度爲 2 的段的數量。

  然後 dp 即可。

Code

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

const int N = 6005;

#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);
}

int 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 boolean operator == (const Z& a, const Z& b) {
			return a.v == b.v;
		} 
};

typedef Z Zi;

int n;
Zi Inv[N];
Zi f[N][6015];

int main() {
	scanf("%d%d", &n, &Mod);
	const int off = 3005;
	f[0][off] = 1;
	Inv[0] = 0, Inv[1] = 1;
	int n3 = n * 3;
	for (int i = 2; i <= n3; i++) {
		Inv[i] = -Inv[Mod % i] * (Mod / i);
	}
	for (int i = 0; i < n3; i++) {
		int L = -(i >> 1), R = min(i, 3005);
		for (int j = L; j <= R; j++) {
			Zi v = f[i][j + off];
			if (!v.v) {
				continue;
			}
			v *= Inv[n3 - i];
			f[i + 3][j + off] += v;
			f[i + 2][j - 1 + off] += v;
			f[i + 1][min(j + 1, off) + off] += v;
		}
	}
	Zi ans = 0;
	for (int i = 0; i <= min(off, n * 3); i++) {
		ans += f[n3][i + off];
	}
	for (int i = 1; i <= n3; i++)
		ans *= i;
	printf("%d\n", ans.v);
	return 0;
}

Problem E Topology

  考慮如何判斷一個在某個點集下是否能將線圈移動到 $y$ 軸下方。

  考慮沿着線圈走,如果經過第 $i$ 個點向上的射線那麼記下 $u_i$,如果經過第 $i$ 個點向下的射線,那麼記下 $d_i$。能移動的條件是存在一種方案每次刪除相鄰的相同字符使得字符串變爲空。

  證明?因爲我不會所以這裏沒有。

  注意到一個顯然的必要條件是如果 $A_S = 1$,對於任意 $T \subset S$ 也滿足 $A_T = 1$。可以證明這是充分的。

  考慮怎麼構造 $1111111\cdots110$。設對於 $n = k$,構造的字符串爲 $a_k$。

  顯然當 $n = 1$ 的時候令 $a_1 = u_1d_1$ 就可以了。

  考慮 $n > 1$ 的情況,令 $a_n = u_n a_{n - 1} u_n d_n \overline{a_{n-1}} d_n$

  如果 $n$ 不在點集內,那麼顯然能消空,否則 $a_{n - 1}$ 不能消除當且僅當 $1, 2, \cdots, n - 1$ 中某個點不存在。這恰好是我們想要的。

  然後對於每個 $A_S = 0$ 把這樣一個東西放上去就行了。

Code

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

template <typename T>
vector<T>& operator += (vector<T>& a, vector<T> b) {
	for (auto x : b) {
		a.push_back(x);
	}
	return a;
}
template <typename T>
vector<T> operator ~ (vector<T> a) {
  reverse(a.begin(), a.end());
  return a;
}

typedef class Point {
  public:
    int x, y;

    Point() { }
    Point(int x, int y) : x(x), y(y) {  }

    bool operator == (Point b) {
      return x == b.x && y == b.y;
    }
} Point;

typedef class Data {
  public:
    char dir;
    int num;

    Data() {  }
    Data(char dir, int num) : dir(dir), num(num) {  }
} Data;

int n, N;

vector<Point> _export(vector<Data> vd) {
  vector<Point> ret {Point(n, 1)};
  for (auto t : vd) {
    auto lst = ret.back();
    if (t.dir == 'd' && lst.y == 1) {
      ret.emplace_back(lst.x, 0);
    } else if (t.dir == 'u' && lst.y == 0) {
      ret.emplace_back(lst.x, 1);
    }
    lst = ret.back();
    if (lst.x == t.num) {
      lst.x--;
    } else if (lst.x == t.num - 1) {
      lst.x++;
    } else {
      assert(false);
    }
    ret.push_back(lst);
  }
  if (ret[0] == ret.back()) {
    ret.pop_back();
  }
  return ret;
}
vector<Point> _export(vector<Data> vd, vector<int> pos) {
  for (auto& t : vd) {
    t.num = pos[t.num - 1] + 1;
  }
  vector<Data> nvd;
  for (int i = n; i > vd[0].num; i--) {
      nvd.emplace_back('d', i);
  }
  for (auto t : vd) {
    if (!nvd.empty()) {
      auto& lst = nvd.back();
      for (int i = lst.num + 1; i < t.num; i++) {
        nvd.emplace_back('d', i);
      }
      for (int i = lst.num - 1; i > t.num; i--) {
        nvd.emplace_back('d', i);
      }
    }
    nvd.push_back(t);
  }
  for (int i = nvd.back().num + 1; i <= n; i++) {
    nvd.emplace_back('d', i);
  }
  return _export(nvd);
}

char A[530];
vector<Data> df[10];

#define NO do puts("Impossible"), exit(0); while (0)

int main() {
  scanf("%d", &n);
  N = 1 << n;
  scanf("%s", A);
  if (A[0] != '1') {
    NO;
  }
	for (int s = 0; s < N; s++) {
    if (A[s] == '1') {
      for (int i = 0; i < n; i++) {
        if (((s >> i) & 1) && A[s ^ (1 << i)] == '0') {
          NO;
        }
      }
    }
  }
  df[1].emplace_back('d', 1);
  df[1].emplace_back('u', 1);
  for (int i = 2; i <= n; i++) {
    df[i].emplace_back('u', i);
    df[i] += df[i - 1];
    df[i].emplace_back('u', i);
    df[i].emplace_back('d', i);
    df[i] += ~df[i - 1];
    df[i].emplace_back('d', i);
  }
  vector<Point> ans;
  for (int s = 1; s < N; s++) {
    if (A[s] == '0') {
      vector<int> pos;
      int bit = 0;
      for (int i = 0; i < n; i++) {
        if ((s >> i) & 1) {
          pos.push_back(i);
          bit++;
        }
      }
      ans += _export(df[bit], pos);
    }
  }
  puts("Possible");
  printf("%d\n", (signed) ans.size());
  if (ans.empty()) {
    ans.emplace_back(0, 0);
  } else {
    ans.push_back(ans[0]);
  }
  for (auto p : ans) {
    printf("%d %d\n", p.x, p.y);
  }
  return 0;
}

Problem F Jewelry Box

  通常情況下,某位大神會跑來給我說這個 F 對個偶就沒了,並表示錯過了吊打 tourist 的機會非常地氣。

  先考慮 $Q = 1$ 的情形,不妨設詢問的是 $A$。

  考慮怎麼判斷每個商店選出若干珠寶是否合法。考慮每次貪心地選擇每個商店重量最小的珠寶湊成珠寶盒。

  注意到如果在 $v_i$ 商店購買了 $x$ 個重量小於等於 $k$ 的珠寶,那麼要求在 $u_i$ 商店購買了至少 $A - x$ 個重量大於等於 $k - w_i$ 的物品。

  不難用上面的貪心證明這個條件是充分的。

  將第 $i$ 個商店的商品按重量從小到達排序,並設前 $j$ 個商品中購買了 $x_{i, j}$ 個。

  大概有如下限制:

  • $x_{i, 0} = 0$
  • $x_{i, K_i} = A$
  • $x_{i, j} \geqslant x_{i, j - 1}$
  • $x_{i, j} - x_{i, j - 1} \leqslant C_{i, j}$
  • $x_{v_i, j} \geqslant x_{u_i, k}$

  然後要最小化 $\sum (x_{i, j} - x_{i, j - 1})P_{i, j}$。

  首先把它標準化,再對偶,然後就是個上下界最大費用可行流問題。不難注意到你可以通過一些增廣操作使得只經過 0 邊讓有下界的邊都滿流。現在已經最大流了,只能通過正環改變費用了。爲了方便你需要把 $x_{i, 0}$ 以及 $x_{i, K_i}$ 分別壓成一個點,這樣和 $A$ 相關的邊只有兩條了。注意到 $x_{i, K_i} \leqslant A$ 是沒有意義的,因爲是最小化費用,多選了肯定不優。因此和 $A$ 相關的邊只有一條了。

  注意到正環一定包含費用爲 $A$ 這條邊,如果包含了至少一次說明存在正環,原問題要麼是 infeasible 要麼是 unbounded。所以在有最優解的情況下,每個正環恰好包含一次費用爲 $A$ 的邊。把它刪掉,剩下變成從 $x_{i, K_i}$ 到 $x_{i, 0}$ 的一條路徑。

  然後跑費用流預處理出凸包,然後詢問的時候二分一下就行了。

Code

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

#define ll long long

const ll llf = (signed ll) (~0ull >> 3);

template <typename T>
void pfill(T* pst, const T* ped, T val) {
  for ( ; pst != ped; *(pst++) = val);
}

typedef class Edge {
  public:
    int ed, nx;
    ll r, c;

    Edge() {	}
    Edge(int ed, int nx, ll r, ll c) : ed(ed), nx(nx), r(r), c(c) {	}
} Edge;

typedef class MapManager {
  public:
    int* h;
    vector<Edge> es;

    MapManager() {	}
    MapManager(int n)  {
      h = new int[(n + 1)];
      memset(h, -1, (n + 1) << 2);
    }

    void add_edge(int u, int v, ll cap, ll c) {
      es.emplace_back(v, h[u], cap, c);
      h[u] = (signed) es.size() - 1;
    }
    void add_arc(int u, int v, ll cap, ll c) {
      add_edge(u, v, cap, c);
      add_edge(v, u, 0, -c);
    }
    Edge& operator [] (int p) {
      return es[p];
    }
} MapManager;

typedef class Data {
  public:
    ll f, c, res;

    Data(ll f, ll c, ll res) : f(f), c(c), res(res) { }
} Data;

typedef class Network {
  public:
    int S, T;
    MapManager g;

    Network() {	}
    Network(int S, int T) : S(S), T(T), g(T) {
      f = new ll[(T + 1)];
      mf = new ll[(T + 1)];
      le = new int[(T + 1)];
      vis = new boolean[T + 1];
    }

    ll *f, *mf;
    int *le;
    boolean *vis;

    void clear() {
      delete[] f;
      delete[] le;
      delete[] mf;
      delete[] vis;
    }

    vector<Data> conv;
    boolean spfa() {
      queue<int> Q;
      pfill(f, f + T + 1, llf);
      pfill(vis, vis + T + 1, false);
      Q.push(S);
      f[S] = 0, mf[S] = llf, le[S] = -1;
      while (!Q.empty()) {
        int e = Q.front();
        Q.pop();
        vis[e] = false;
        for (int i = g.h[e], eu; ~i; i = g[i].nx) {
          eu = g[i].ed;
          if (!g[i].r) continue;
          ll w = f[e] + g[i].c;
          if (w < f[eu]) {
            f[eu] = w, mf[eu] = min(mf[e], g[i].r), le[eu] = i;
            if (!vis[eu]) {
              Q.push(eu);
              vis[eu] = true;
            }
          }
        }
      }
      if (f[T] == llf)
        return false;
      ll flow = mf[T];
      for (int p = T, e; p ^ S; p = g[le[p] ^ 1].ed) {
        e = le[p];
        g[e].r -= flow;
        g[e ^ 1].r += flow;
      }
      Data t = conv.back();
      conv.emplace_back(t.f + flow, f[T], t.res + 1ll * f[T] * flow);
      if (t.f + flow >= (1ll << 50)) {
        return false;
      }
      return true;
    }

    void min_cost() {
      conv.emplace_back(0, 0, 0);
      while (spfa());
    }

    void add_edge(int u, int v, ll cap, ll w) {
      //			cerr << u << "->" << v << " " << cap << " " << w << '\n';
      g.add_arc(u, v, cap, w);		
    }
} Network;

typedef class Jewelry {
  public:
    int s, p;
    ll c;

    void read() {
      cin >> s >> p >> c;
    }
    boolean operator < (Jewelry b) const {
      return s < b.s;
    }
} Jewelry;

int n, m, q;
vector<vector<int>> id;
vector<vector<Jewelry>> shop;

int main() {
  ios::sync_with_stdio(false);
  cin.tie(0), cout.tie(0);
  cin >> n;
  id.resize(n);
  shop.resize(n);
  int T = 0;
  for (int i = 0, K; i < n; i++) {
    cin >> K;
    id[i].resize(K - 1);
    shop[i].resize(K);
    for (auto& x : id[i]) {
      x = ++T;
    }
    id[i].push_back(-1);
    id[i].insert(id[i].begin(), 0);
    for (auto& x : shop[i]) {
      x.read();
    }
    sort(shop[i].begin(), shop[i].end());
  }
  Network network (0, ++T);
  for (auto& v : id) {
    v.back() = T;
  }
  for (int i = 0; i < n; i++) {
    int K = shop[i].size();
    for (int j = 0; j < K; j++) {
      network.add_edge(id[i][j], id[i][j + 1], shop[i][j].p, 0);
      network.add_edge(id[i][j + 1], id[i][j], llf, 0);
      network.add_edge(id[i][j], id[i][j + 1], llf, shop[i][j].c);
    }
  }
  cin >> m;
  for (int i = 0, x, y, w; i < m; i++) {
    cin >> x >> y >> w;
    --x, --y;
    int kx = shop[x].size(), ky = shop[y].size();
    for (int l = 0, r = 0; l < ky; l++) {
      while (r < kx && shop[y][l].s - w > shop[x][r].s) {
        r++;
      }
      network.add_edge(id[y][l], id[x][r], llf, 0);
    }
  }
  network.min_cost();
  auto& conv = network.conv;
  cin >> q;
  ll a;
  while (q--) {
    cin >> a;
    auto t = lower_bound(conv.begin(), conv.end(), a, [&] (Data x, ll y) {  return x.c < y; });
    if (t == conv.end()) {
      cout << "-1" << '\n';
    } else {
      --t;
      cout << 1ll * (*t).f * a - (*t).res << '\n'; 
    }
  }
  return 0;
}

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