NOI 2019 简要题解

从这里开始

  说好的 agc 046 呢

  去年的题真难写

Day 1

Problem A 回家路线

  暴力即可。 2e8 真的很稳。

  可以按开始时间排序,然后每个点上斜率优化。

Code

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

const int N = 2e5 + 5;

#define ll long long

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

typedef class Point {
  public:
    int x;
    ll y;
    
    Point(int x = 0, ll y = 0) : x(x), y(y) { }
} Point;

Point operator - (Point a, Point b) {
  return Point(a.x - b.x, a.y - b.y);
}

ll cross(Point a, Point b) {
  return a.x * b.y - a.y * b.x;
}

typedef class ConvexHull {
  public:
    int pos;
    vector<Point> stk;
    
    ConvexHull() : pos(0) { }
   
    void insert(int _x, ll _y) {
      Point p (_x, _y);
      while (!stk.empty() && stk.back().x == _x && stk.back().y >= _y)
        stk.pop_back();
      while (stk.size() > 1u && cross(stk.back() - stk[stk.size() - 2], p - stk.back()) <= 0)
        stk.pop_back();
      stk.push_back(p);
    }
    ll query(ll k) {
      if (stk.empty()) {
        return llf;
      }
      ll ret = llf;
      for (int i = 0; i < (signed) stk.size(); i++) {
        ret = min(ret, stk[i].y - stk[i].x * k);
      }
      pos = min(pos, (signed) stk.size() - 1);
      while (pos < (signed) stk.size() - 1 && stk[pos].y - k * stk[pos].x > stk[pos + 1].y - k * stk[pos + 1].x)
        pos++;
      return stk[pos].y - k * stk[pos].x;
    }
} ConvexHull;

typedef class Route {
  public:
    int s, t, p, q;
    
    void read() {
      scanf("%d%d%d%d", &s, &t, &p, &q);
    }
    
    bool operator < (Route b) const {
      return p < b.p;
    }
} Route;

int n, m, A, B, C;
ll f[N];
Route R[N];
int ord[N];
ConvexHull con[N];

int main() {
  freopen("route.in", "r", stdin);
  freopen("route.out", "w", stdout);
  scanf("%d%d%d%d%d", &n, &m, &A, &B, &C);
  for (int i = 1; i <= m; i++) {
    R[i].read();
    assert(R[i].p < R[i].q);
  }
  sort(R + 1, R + m + 1);
  R[0].t = 1, R[0].q = 0;
  for (int i = 0; i <= m; i++) {
    ord[i] = i;
  }
  sort(ord + 1, ord + m + 1, [&] (int x, int y) { return R[x].q < R[y].q; });
  ll ans = llf;
  int* po = ord, *_po = ord + m + 1;
  for (int i = 1; i <= m; i++) {
    while (po != _po && R[*po].q <= R[i].p) {
      if (f[*po] < (llf >> 1)) {
        Route &r = R[*po];
        con[r.t].insert(r.q, f[*po] + 1ll * A * r.q * r.q - 1ll * B * r.q);
      }
      po++;
    }
    Route& r = R[i];
    f[i] = con[r.s].query(2 * A * r.p);
    if (f[i] >= (llf >> 1))
      continue;
    f[i] += 1ll * A * r.p * r.p + 1ll * B * r.p + C;
    if (r.t == n) {
      ans = min(ans, f[i] + r.q);
    } 
  }
  printf("%lld\n", ans);
  return 0;
}

Problem B 机器人

  显然答案是一个关于最大能选的数的不超过 $n$ 次的分段多项式。

  写一个暴力打表发现涉及到的区间的长度和最长也就 17400 左右,因为一个点可能因为 +1 被加上 $O(log n)$ 次,大概总的段数也在 $10^5$ 左右。直接用点值维护分段多项式就行了。据说直接暴力维护出系数表示的多项式也能过。

Code 

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

const int N = 305;
const int Mod = 1e9 + 7;

const int inf = (~0u >> 2);

#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 x, y;
  exgcd(a, Mod, x, y);
  return x < 0 ? x + Mod : x;
}

typedef class Zi {
  public:
    int v;
     
    Zi() { }
    Zi(int v) : v(v) { }
    Zi(ll x) : v(x % Mod) {  }
      
    friend Zi operator + (Zi a, Zi b) {
      int x = a.v + b.v;
      return x >= Mod ? x - Mod : x;
    }
    friend Zi operator - (Zi a, Zi b) {
      int x = a.v -b.v;
      return x < 0 ? x + Mod : x; 
    }
    friend Zi operator * (Zi a, Zi b) {
      return 1ll * a.v * b.v;
    }
    friend Zi operator ~ (Zi a) {
      return inv(a.v);
    }
    Zi& operator += (Zi b) {
      return *this = *this + b;
    }
    Zi& operator -= (Zi b) {
      return *this = *this - b;
    }
    Zi& operator *= (Zi b) {
      return *this = *this * b;
    }
} Zi;

vector<Zi> Inv;
vector<Zi> fac, _fac;

void prepare(int n) {
  Inv.resize(n + 1);
  for (int i = 1; i <= n; i++) {
    Inv[i] = ~Zi(i);
  }
  fac.resize(n + 1);
  _fac.resize(n + 1);
  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;
  }
}

int n, S;

typedef class Segment {
  public:
    int r;
    vector<Zi> f;
    
    Segment() { }
    Segment(int r, vector<Zi> f) : r(r), f(f) { }
    
    void init(Zi x) {
      f.resize(S, x);
    }
    Zi eval(Zi n) {
      static Zi L[N], R[N];
      if (n.v < S) {
        return f[n.v];
      }
      L[0] = 1;
      for (int i = 0; i < S; i++) {
        L[i + 1] = L[i] * (n - i); 
      }
      R[S] = 1;
      for (int i = S; i--; ) {
        R[i] = R[i + 1] * (n - i);
      }
      Zi ret = 0;
      for (int i = 0; i < S; i++) {
        Zi tmp = f[i] * L[i] * R[i + 1] * _fac[i] * _fac[S - 1 - i];
        if ((S - 1 - i) & 1) {
          ret -= tmp; 
        } else {
          ret += tmp;
        }
      }
      return ret;
    }
    Segment operator + (const Segment& b) {
      Segment s;
      s.init(S);
      s.r = min(r, b.r);
      for (int i = 0; i < S; i++) {
        s.f[i] = f[i] + b.f[i];
      } 
      return s;
    }
    Segment operator * (const Segment& b) {
      Segment s;
      s.init(S);
      s.r = min(r, b.r);
      for (int i = 0; i < S; i++) {
        s.f[i] = f[i] * b.f[i];
      }
      return s;
    }
    
    Segment& operator += (Zi x) {
      for (auto& y : f)
        y += x;
      return *this;
    }
    
    void pre_sum() {
      Zi sum = 0;
      for (int i = 0; i < S; i++) {
        sum += f[i];
        f[i] = sum;
      }
    }
    
    void shift() {
      r = min(r + 1, inf);
      f.insert(f.begin(), eval(Mod - 1));
      f.pop_back();
    }
} Segment;

typedef class Function {
  public:
    vector<Segment> seg;
    
    void append(Segment s) {
      seg.push_back(s);
    }
      
    void pre_sum() {
      int ls = 0;
      Zi sum = 0;
      for (auto&s : seg) {
        s.pre_sum();
        s += (sum - s.eval(ls));
        ls = s.r;
        sum = s.eval(s.r);
      }
    }
    
    Function operator * (const Function& b) {
      Function f;
      auto pl = seg.begin(), _pl = seg.end();
      auto pr = b.seg.begin(), _pr = b.seg.end();
      while (pl != _pl && pr != _pr) {
        if ((*pl).r == (*pr).r) {
          f.append(*(pl++) * *(pr++));
        } else if ((*pl).r < (*pr).r) {
          f.append(*(pl++) * *pr);
        } else {
          f.append(*pl * *(pr++));
        }
      }
      assert(pl == _pl && pr == _pr);
      return f;
    }
    Function operator + (const Function& b) {
      Function f;
      auto pl = seg.begin(), _pl = seg.end();
      auto pr = b.seg.begin(), _pr = b.seg.end();
      while (pl != _pl && pr != _pr) {
        if ((*pl).r == (*pr).r) {
          f.append(*(pl++) + *(pr++));
        } else if ((*pl).r < (*pr).r) {
          f.append(*(pl++) + *pr);
        } else {
          f.append(*pl + *(pr++));
        }
      }
      assert(pl == _pl && pr == _pr);
      return f;
    }
    
    Function& reserve(int l, int r) {
      for (int i = seg.size(); i--; ) {
        if (!i || seg[i - 1].r < r) {
          seg[i].r = r;
          seg.erase(seg.begin() + i + 1, seg.end());
          break;
        }
      }
      seg.push_back(Segment(inf, vector<Zi>(S, 0)));
      if (l == 1) return *this;
      for (int i = 0; i < (seg.size()); i++) {
        if (seg[i].r >= l) {
          seg.erase(seg.begin(), seg.begin() + i);
          break;
        }      
      }
      seg.insert(seg.begin(), Segment(l - 1, vector<Zi>(S, 0)));
      return *this;
    }
    
    Function& shift() {
      for (auto& s : seg) {
        s.shift();
      }
      seg.insert(seg.begin(), Segment(1, vector<Zi>(S, 0)));
      return *this;
    }
    
    void shrink() {
      vector<Segment> nseg;
      int ls = 0;
      for (auto& s : seg) {
        if (s.r > ls) {
          nseg.push_back(s);
          ls = s.r; 
        }        
      }
      seg = nseg;
    }
} Function;

int A[N], B[N];
bool vis[N][N];
Function f0, fe, f[N][N];

int Abs(int x) {
  return x < 0 ? -x : x;
} 

Function solve(int l, int r) {
  if (l > r) {
    return fe;
  }
  if (vis[l][r]) {
    return f[l][r];
  }
  vis[l][r] = true;
  Function& F = f[l][r];
  F = f0;
  for (int i = l; i <= r; i++) {
    if (Abs((i - l) - (r - i)) <= 2) {
      F = F + (solve(l, i - 1) * ((i + 1 <= r) ? solve(i + 1, r).shift() : fe)).reserve(A[i], B[i]);
    }
  }
  F.shrink();
  F.pre_sum();
  return F;
}

int main() {
  freopen("robot.in", "r", stdin);
  freopen("robot.out", "w", stdout);
  scanf("%d", &n);
  S = n + 2;
  prepare(S + 3);
  for (int i = 1; i <= n; i++) {
    scanf("%d%d", A + i, B + i); 
  }
  f0.append(Segment(inf, vector<Zi>(S, 0)));
  fe.append(Segment(inf, vector<Zi>(S, 1)));
  Function fans = solve(1, n);
  Zi ans = fans.seg.back().eval(inf);
  printf("%d\n", ans.v);
  return 0;
}

Problem C 序列

  开大数组可以得到比预估更高的分数,学到了。 

  有一个比较好想的建图,然而大概平方版本跑不过 2e3,模拟费用流跑不过 3e5。 d1 t3 还卡常真有毒。

  考虑要求每次选择选择一个 $x$ 也选择一个 $y$,这样能够很好地限制它们数量相等。

  建两列点,$A_i$ 向 $B_i$ 连边,再建一个一对点 $x, x'$,连边 $(x, x', K - L , 0)$,用于限制选择不同的一对的数量。然后 $A_i$ 向 $x$ 连边,$x'$ 向 $B_i$  连边。$S$ 向 $A_i$ 连边,$B_i$ 向 $T$ 连边。

  考虑怎么费用流:

  • 如果 $x \rightarrow$ 未满流了,那么无论如何都相当于左边选择最大,右边再选择一个最大的,然后再判断它的流量会不会增加。
  • 否则有 $4$ 种情况
    • $S \rightarrow A_i \rightarrow B_i \rightarrow T$
    • $S \rightarrow A_i \rightarrow x \rightarrow A_j \rightarrow B_j \rightarrow T$
    • $S \rightarrow A_i \rightarrow B_i \rightarrow x' \rightarrow B_j \rightarrow T$
    • $S \rightarrow A_i \rightarrow B_i \rightarrow x' \rightarrow x \rightarrow  A_j \rightarrow B_j \rightarrow  T$

  然后瞎维护一下就行了

Code

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

typedef class Input {
	protected:
		const static int limit = 65536;
		FILE* file; 

		int ss, st;
		char buf[limit];
	public:
		
		Input() : file(NULL)	{	};
		Input(FILE* file) : file(file) {	}

		void open(FILE *file) {
			this->file = file;
		}

		void open(const char* filename) {
			file = fopen(filename, "r");
		}

		char pick() {
			if (ss == st)
				st = fread(buf, 1, limit, file), ss = 0;//, cerr << "str: " << buf << "ed " << st << endl;
			return buf[ss++];
		}
} Input;

#define digit(_x) ((_x) >= '0' && (_x) <= '9')

Input& operator >> (Input& in, unsigned& u) {
	char x;
	while (~(x = in.pick()) && !digit(x));
	for (u = x - '0'; ~(x = in.pick()) && digit(x); u = u * 10 + x - '0');
	return in;
}

Input& operator >> (Input& in, unsigned long long& u) {
	char x;
	while (~(x = in.pick()) && !digit(x));
	for (u = x - '0'; ~(x = in.pick()) && digit(x); u = u * 10 + x - '0');
	return in;
}

Input& operator >> (Input& in, int& u) {
	char x;
	while (~(x = in.pick()) && !digit(x) && x != '-');
	int aflag = ((x == '-') ? (x = in.pick(), -1) : (1));
	for (u = x - '0'; ~(x = in.pick()) && digit(x); u = u * 10 + x - '0');
	u *= aflag;
	return in;
}

Input& operator >> (Input& in, long long& u) {
	char x;
	while (~(x = in.pick()) && !digit(x) && x != '-');
	int aflag = ((x == '-') ? (x = in.pick(), -1) : (1));
	for (u = x - '0'; ~(x = in.pick()) && digit(x); u = u * 10 + x - '0');
	u *= aflag;
	return in;
}

Input& operator >> (Input& in, double& u) {
	char x;
	while (~(x = in.pick()) && !digit(x) && x != '-');
	int aflag = ((x == '-') ? (x = in.pick(), -1) : (1));
	for (u = x - '0'; ~(x = in.pick()) && digit(x); u = u * 10 + x - '0');
	if (x == '.') {
		double dec = 1;
		for ( ; ~(x = in.pick()) && digit(x); u = u + (dec *= 0.1) * (x - '0'));
	}
	u *= aflag;
	return in;
}

Input& operator >> (Input& in, char* str) {
	char x;
	while (~(x = in.pick()) && x != '\n' && x != ' ')
		*(str++) = x;
	*str = 0;
	return in;
}

Input in (fopen("sequence.in", "r"));

typedef class Output {
	protected:
		const static int Limit = 65536;
		char *tp, *ed;
		char buf[Limit];
		FILE* file;
		int precision;

		void flush() {
			fwrite(buf, 1, tp - buf, file);
			fflush(file);
			tp = buf;
		}

	public:

		Output() {	}
		Output(FILE* file) : tp(buf), ed(buf + Limit), file(file), precision(6) {	}
		Output(const char *str) : tp(buf), ed(buf + Limit), precision(6) {
			file = fopen(str, "w");
		}
		~Output() {
			flush();
		}

		void put(char x) {
			if (tp == ed)
				flush();
			*(tp++) = x;
		}

		int get_precision() {
			return precision;
		}
		void set_percision(int x) {
			precision = x;
		}
} Output;

Output& operator << (Output& out, int x) {
	static char buf[35];
	static char * const lim = buf + 34;
	if (!x)
		out.put('0');
	else {
		if (x < 0)
			out.put('-'), x = -x;
		char *tp = lim;
		for ( ; x; *(--tp) = x % 10, x /= 10);
		for ( ; tp != lim; out.put(*(tp++) + '0'));
	}
	return out;
}

Output& operator << (Output& out, long long x) {
	static char buf[36];
	static char * const lim = buf + 34;
	if (!x)
		out.put('0');
	else {
		if (x < 0)
			out.put('-'), x = -x;
		char *tp = lim;
		for ( ; x; *(--tp) = x % 10, x /= 10);
		for ( ; tp != lim; out.put(*(tp++) + '0'));
	}
	return out;
}

Output& operator << (Output& out, unsigned x) {
	static char buf[35];
	static char * const lim = buf + 34;
	if (!x)
		out.put('0');
	else {
		char *tp = lim;
		for ( ; x; *(--tp) = x % 10, x /= 10);
		for ( ; tp != lim; out.put(*(tp++) + '0'));
	}
	return out;
}

Output& operator << (Output& out, char x)  {
	out.put(x);
	return out;
}

Output& operator << (Output& out, const char* str) {
	for ( ; *str; out.put(*(str++)));
	return out;
}

Output& operator << (Output& out, double x) {
	int y = x;
	x -= y;
	out << y << '.';
	for (int i = out.get_precision(); i; i--, y = x * 10, x = x * 10 - y, out.put(y + '0'));
	return out;
}

Output out ("sequence.out");

#define pii pair<int, int>
#define ll long long

const int inf = (signed) (~0u >> 2);

typedef class ZKW {
  public:
    int M;
    pii mx[524288];
    
    void init(int n, pii* w) {
      for (M = 1; M < n; M <<= 1);
      for (int i = 0; i < n; i++) {
        mx[i + M] = w[i];
      }
      for (int i = n; i < M; i++) {
        mx[i + M] = pii(-inf, 0);
      }
      for (int i = M; --i; push_up(i));
    }
    
    void push_up(int p) {
      mx[p] = max(mx[p << 1], mx[p << 1 | 1]);
    }
    void upd(int p, int vi) {
      p += M;
      mx[p] = pii(vi, p - M);
      for ( ; p >>= 1; push_up(p));
    }
    int qry() {
      return mx[1].first;
    }
    int qryi() {
      return mx[1].second;
    }
} ZKW;

const int N = 2e5 + 5;

int T, n, K, L;
int cnt;
int a[N], b[N];
ZKW zs, za, zb, _za, _zb;
bool ina[N], inb[N];

void seclecta(int p) {
  ina[p] = true;
  cnt -= ina[p] && inb[p];
  zs.upd(p, -inf);
  za.upd(p, -inf);
  _za.upd(p, -inf);
  if (!inb[p])
    _zb.upd(p, b[p]);
}
void seclectb(int p) {
  inb[p] = true;
  cnt -= ina[p] && inb[p];
  zs.upd(p, -inf);
  zb.upd(p, -inf);
  _zb.upd(p, -inf);
  if (!ina[p])
    _za.upd(p, a[p]);
}

void solve() {
  static pii tmp[N];
  cnt = 0;
  in >> n >> K >> L;
  for (int i = 0; i < n; i++) {
    in >> a[i];
  }
  for (int i = 0; i < n; i++) {
    in >> b[i];
  }
  for (int i = 0; i < n; i++)
    tmp[i] = pii(a[i] + b[i], i);
  zs.init(n, tmp);
  for (int i = 0; i < n; i++)
    tmp[i] = pii(a[i], i);
  za.init(n, tmp);
  for (int i = 0; i < n; i++)
    tmp[i] = pii(b[i], i);
  zb.init(n, tmp);
  for (int i = 0; i < n; i++)
    tmp[i] = pii(-inf, i);
  _za.init(n, tmp);
  _zb.init(n, tmp);
  fill(ina, ina + n, false);
  fill(inb, inb + n, false);
  for (int i = 1; i <= K; i++) {
    if (cnt == K - L) {
      int swpa = _za.qry() + zb.qry();
      int swpb = za.qry() + _zb.qry();
      int mx = zs.qry(), id = 0, q;
      if ((q = _za.qry() + _zb.qry()) > mx)
        id = 1, mx = q;
      if ((q = swpa) > mx)
        id = 2, mx = q;
      if ((q = swpb) > mx)
        id = 3, mx = q;
      if (!id) {
        q = zs.qryi();
        seclecta(q);
        seclectb(q);      
      } else if (id == 1) {
        seclecta(_za.qryi());
        seclectb(_zb.qryi());
      } else if (id == 2) {
        seclecta(_za.qryi());
        seclectb(zb.qryi());
      } else {
        seclecta(za.qryi());
        seclectb(_zb.qryi());
      }
    } else {
      seclecta(za.qryi());
      seclectb(zb.qryi());
    }
    ++cnt;
  }
  ll ans = 0;
  for (int i = 0; i < n; i++) {
    ans += ina[i] * a[i];
    ans += inb[i] * b[i];
  }
  out << ans << '\n';
}

int main() {
  in >> T;
  while (T--) {
    solve();
  }
  return 0;
}

Day 2

Problem A 弹跳 

  考虑 dijkstra 的过程,每次我们加入一条边。一条边更新一个点后,一个点就不可能再被更新了。因此我们只用查询一个矩形内的所有点然后把它们删除。

  可以用线段树套 set 或者 KDtree 维护。

  时间复杂度 $O(n\log^2 n + m\log n)$。

  假设对 $x$ 建线段树,考虑将所有查询矩形和点先按照 $y$ 从小到大排序,依次加入(矩形只用在定位到的节点上加入它的下边界),每个节点上做一次归并,这样可以预处理出每次矩形删点的 lower_bound 的结果。删点的过程显然可以用并查集优化。

  理论上可以做到 $O(n\log n + m \log n)$

Code

/**
 * loj
 * Problem#3159
 * Accepted
 * Time: 6467ms
 * Memory: 51568k
 */
#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

const int N = 7e4 + 5;
const int M = 150005;

typedef class Node {
	public:
		int eid, dis;

		Node() {	}
		Node(int eid, int dis) : eid(eid), dis(dis) {	}

		boolean operator < (Node b) const {
			return dis > b.dis;
		}
} Node;

#define pii pair<int, int>

int n, m, w, h;
int f[N];
boolean vis[N];
vector<int> G[N];
set<pii> S[N << 2];
priority_queue<Node> Q;

int xl[M], xr[M], yl[M], yr[M], W[M];

void insert(int p, int l, int r, int x, int y, int id) {
	S[p].insert(pii(y, id));
	if (l == r) return;
	int mid = (l + r) >> 1;
	if (x <= mid) {
		insert(p << 1, l, mid, x, y, id);
	} else {
		insert(p << 1 | 1, mid + 1, r, x, y, id);
	}
}

void del(int p, int l, int r, int x1, int x2, int y1, int y2, int w) {
	if (l == x1 && r == x2) {
		set<pii>::iterator it = S[p].lower_bound(pii(y1, 0));
		while (it != S[p].end() && (*it).first <= y2) {
			int id = (*it).second;
			if (!vis[id]) {
				vis[id] = true;
				f[id] = w;
				for (auto eid : G[id]) {
					Q.push(Node(eid, f[id] + W[eid]));
				}
			}
			it = S[p].erase(it);
		}
		return;
	}
	int mid = (l + r) >> 1;
	if (x2 <= mid) {
		del(p << 1, l, mid, x1, x2, y1, y2, w);
	} else if (x1 > mid) {
		del(p << 1 | 1, mid + 1, r, x1, x2, y1, y2, w);
	} else {
		del(p << 1, l, mid, x1, mid, y1, y2, w);
		del(p << 1 | 1, mid + 1, r, mid + 1, x2, y1, y2, w);
	}
}

int main() {
	freopen("jump.in", "r", stdin);
	freopen("jump.out", "w", stdout);
	scanf("%d%d%d%d", &n, &m, &w, &h);
	for (int i = 1, x, y; i <= n; i++) {
		scanf("%d%d", &x, &y);
		if (i ^ 1) {
			insert(1, 1, w, x, y, i);
		}
	}
	for (int i = 1, p; i <= m; i++) {
		scanf("%d%d%d%d%d%d", &p, W + i, xl + i, xr + i, yl + i, yr + i);
		G[p].push_back(i);
	}
	for (auto e : G[1]) {
		Q.push(Node(e, W[e]));
	}
	while (!Q.empty()) {
		int e = Q.top().eid;
		int d = Q.top().dis;
		Q.pop();
		del(1, 1, w, xl[e], xr[e], yl[e], yr[e], d);
	}
	for (int i = 2; i <= n; i++) {
		printf("%d\n", f[i]);
	}
	return 0;
}

Problem B 斗主地

  开始听说看过具体数学就会,后来听说打个表也没了,当时我就不一样,既没读过书,也不会打表

  考虑直接计算 $E_i$ 贡献到 $E'_j$ 的系数。不妨先考虑 $i \leqslant A$ 的情形

$$
\begin{align}
\binom{n - j}{A - i} \frac{A^{\underline{A-i+1}}(n-A)^{\underline{n - j - (A- i)}}}{n^{\underline{n - j + 1}}} &= \binom{n}{A}^{-1}\binom{n-j}{A- i}\binom{j-1}{i-1}
\end{align}
$$

  然后 $E'_j$ 会对 $i \leqslant A$ 的 $E_i$ 乘上这个系数进行求和。

  先考虑 $E_i = i$ 的时候,提出 $\binom{n}{A}^{-1}$, 考虑这个式子的组合意义:从 $n$ 个球选出 $A$ 个球,第 $j$ 个球一定被选择,如果是第 $i$ 个被选择的球,贡献为 $i$。

  考虑枚举被选择的球,不难得到它等于 $\binom{n - 1}{A - 1} + (j - 1)\binom{n - 2}{A - 2}$。

  对于 $i > A$ 的时候是类似的做法。

  容易发现 $E'_j$ 是关于 $j$ 的一次多项式。

  当 $type = 2$ 的时候是关于 $j$ 的二次多项式。

  可以暴力维护,也可以插值。

  插值竞赛(确信

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

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;

int n, m, type, q;

vector<Zi> fac, _fac;

void prepare(int n) {
  fac.resize(n + 1);
  _fac.resize(n + 1);
  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;
  }
}
Zi comb(int n, int m) {
  assert(n >= 0 && m >= 0);
  return n < m ? 0 : fac[n] * _fac[m] * _fac[n - m];
}
Zi _comb(int n, int m) {
  return _fac[n] * fac[m] * fac[n - m];
}

typedef vector<Zi> vec;

vec operator * (vec a, Zi k) {
  for (auto& x : a)
    x *= k;
  return a;
}
vec operator + (vec a, vec b) {
  if (a.size() < b.size())
    a.resize(b.size());
  for (int i = 0; i < (signed) b.size(); i++)
    a[i] += b[i];
  return a;
}

Zi eval(int A, int c) {
  return A - 1 < c ? 0 : comb(n - 1 - c, A - 1 - c);
}

vec get1(int A) {
  vec v (2);
  v[0] = eval(n - A, 0) * A;
  v[1] = eval(A, 1) + eval(n - A, 1);
  return v * _comb(n, A);
}
vec get2(int A) {
  vec v (3);
  v[0] = eval(n - A, 0) * A * (A - 1) * ((Mod + 1) >> 1);
  v[1] = eval(n - A, 1) * A;
  v[2] = eval(A, 2) + eval(n - A, 2);
  return v * _comb(n, A);
}

int main() {
  freopen("landlords.in", "r", stdin);
  freopen("landlords.out", "w", stdout);
  scanf("%d%d%d", &n, &m, &type);
  prepare(n + 1);
  vec f, g;
  if (type == 1) {
    f = {1, 1};
  } else {
    f = {1, 3, 2};
  }
  for (int i = 1, a; i <= m; i++) {
    scanf("%d", &a);
    g = vec {f[0]};
    g = g + get1(a) * f[1];
    if (type == 2)
      g = g + get2(a) * f[2];
    f = g;
  }
  scanf("%d", &q);
  Zi x;
  while (q--) {
    scanf("%d", &x.v);
    Zi ans = f[0] + f[1] * (x - 1);
    if (type == 2)
      ans += f[2] * (x - 1) * (x - 2) * ((Mod + 1) >> 1);
    printf("%d\n", ans.v);
  }
  return 0;
}

Problem C I 君的探险

  A:不难得到每个点周围一圈的异或和,做完了。

  B:直接整体二分。

  对于树的部分分考虑可以假设一个点是叶子,然后用 2 次修改 1 次询问检查它是不是,因此我们可以判断一个点是不是叶子。然后每次剥叶子就可以了。

  对于一般图的情形,考虑随机一个排列,然后套用 B 的整体二分做法,如果一个点向前连了奇数条边,那么一定能找到一条边,否则可能能找到。

  不难证明最坏情况下,期望能减少 $\frac{t}{3}$ 条边,其中 $t$ 是度数非 0 的点的数量。因此当有孤立点的时候复杂度有点假,需要删除孤立点。

  否则的话,可以花费 $O(n\log n)$ 的代价,期望减少 $\frac{n}{3}$ 的边。因此期望的操作次数和复杂度均是 $O(m\log n)$。

  至于初始孤立点我好像只会一些常数比较劣的 $O(n\log n)$ 随机化做法,加了估计操作次数会超,不过数据好像没有卡不处理初始孤立点的情况。

Code

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

int N, M;

namespace brute {
  
  vector<int> old;
  
  void solve() {
    old.resize(N, 0);
    for (int i = 0; i < N - 1; i++) {
      modify(i);
      for (int j = i + 1; j < N; j++) {
        int x = query(j);
        if (x ^ old[j]) {
          report(i, j);
          old[j] = x;
        }
      }
    }
  }
  
}

namespace subtaskA {

vector<int> v;
vector<int> old;

void solve() {
  v.assign(N, 0);
  old.assign(N, 0);
  for (int b = 1; b < N; b <<= 1) {
    for (int i = 0; i < N; i++) {
      if (i & b) {
        modify(i);
      }
    }
    for (int i = 0; i < N; i++) {
      int x = query(i);
      if (old[i] ^ x) {
        v[i] |= b;
        old[i] = x;
      }
    }
  }
  for (int i = 0; i < N; i++) {
    if ((v[i] ^ i) < i) {
      report(v[i] ^ i, i);
    }
  }
}

}

namespace subtaskB {

void dividing(int l, int r, vector<int> p)  {
  if (l == r) {
    for (auto x : p) {
      report(l, x);
    }
    return;
  }
  if (p.empty()) {
    return;
  }
  int mid = (l + r) >> 1;
  for (int i = l; i <= mid; i++) {
    modify(i);
  }
  vector<int> vl, vr;
  for (auto x : p) {
    if (x <= mid || query(x)) {
      vl.push_back(x); 
    } else {
      vr.push_back(x);
    }
  }
  for (int i = l; i <= mid; i++) {
    modify(i);
  }
  dividing(l, mid, vl);
  dividing(mid + 1, r, vr);
}

void solve() {
  vector<int> p (N - 1);
  for (int i = 1; i < N; i++)
    p[i - 1] = i;
  dividing(0, N - 1, p);
}

}

namespace general {

vector<int> P;
vector<int> on;
vector<int> tg;
vector<bool> exist;
vector<vector<int>> G;

void answer(int x, int y) {
  report(x, y);
  G[x].push_back(y);
  G[y].push_back(x);
  M--;
  if (check(x)) {
    exist[x] = false;
  }
  if (check(y)) {
    exist[y] = false;
  }
}
void upd(int p) {
  modify(p);
  on[p] ^= 1; 
}
bool qry(int p) {
  bool ret = query(p);
  for (auto x : G[p]) {
    ret ^= on[x];
  }
  return ret ^ tg[p];
}

void light_on(int l, int r) {
  static int ql = 0, qr = -1;
  if (l < 0) {
    for (int i = ql; i <= qr; i++) {
      upd(P[i]);
    }
    ql = 0, qr = -1;
    return;
  }
  for (int i = ql; i <= qr; i++) {
    if (i < l || i > r) {
      upd(P[i]);
    }
  }
  for (int i = l; i <= r; i++) {
    if (!on[P[i]]) {
      upd(P[i]);  
    }
  }
  ql = l, qr = r;
}

void dividing(int l, int r, vector<int> S) {
  if (S.empty()) {
    return;
  }
  if (l == r) {
    int p = P[l];
    light_on(l, l);
    for (auto x : S) {
      if (P[x] != p && qry(P[x])) {
        answer(p, P[x]);
      }
    }
    return;
  }
  int mid = (l + r) >> 1;
  light_on(l, mid);
  vector<int> sl, sr;
  for (auto x : S) {
    if (x <= mid || qry(P[x])) {
      sl.push_back(x);
    } else {
      sr.push_back(x);
    }
  }
  dividing(l, mid, sl);
  dividing(mid + 1, r, sr);
}

void solve() {
  P.resize(N);
  tg.assign(N, 0);
  on.assign(N, false);
  exist.assign(N, true);
  G.assign(N, vector<int>());
  for (int i = 0; i < N; i++)
    P[i] = i;
  vector<int> P0 = P;
  while (M) {
    light_on(-1, 0);
    vector<int> tmp;
    for (auto x : P) {
      if (exist[x]) {
        tmp.push_back(x);
      }
    }
    P = tmp;
    P0.resize(P.size());
    random_shuffle(P.begin(), P.end());
//    cerr << P.size() << " " << " " << M << '\n';
    dividing(0, (signed) P.size() - 1, P0);
  }
}

}

void explore(int N, int M) {
  srand(time(NULL));
//  srand(233u);
  ::N = N;
  ::M = M;
  if (N <= 500) {
    brute::solve();
  } else if (N % 10 == 8) {
    subtaskA::solve();
  } else if (N % 10 == 7) {
    subtaskB::solve();
  } else {
    general::solve(); 
  }
}

posted @ 2020-07-01 23:12  阿波罗2003  阅读(514)  评论(0编辑  收藏  举报