[BZOJ 1061][NOI2008]志愿者招募

膜了一发BYVoid的题解……竟然搞懂了

这首先是个非常简单的线性规划,就是有若干限制(每天要求的志愿者),形如:

\[P_i = X_a + X_b +\ldots + X_c\geq A_i\]

(这里用\(X_i\)表示第\(i\)类志愿者雇佣了多少个,\(P_i\)表示第\(i\)天的志愿者总数,\(A_i\)同原题面)

且最小化总费用。

既然我们我知道\(P_i\geq A_i\),那么说明\(P_i\)一定可以表示为\(A_i + Y_i\)(\(Y_i\geq 0\))。然后这样限制就变成了:

\[P_i = X_a + X_b +\ldots + X_c + Y_i = A_i\]

这个长得很像可以流量平衡的样子,但是流量的变动是表示不了的……

然后假设\(P_0 = 0\)且\(P_{n + 1} = 0\),这样就可以对限制差分一下,我们就有了\(n + 1\)个限制,然后这个式子就可以流量平衡做了(因为不考虑常数项的话,同一变量只可能在两个限制中出现,并且一正一负,这样就可以通过一个限制送给另一个限制流量来表示了。至于常数项,通过源或者汇连边即可表达)。

然后由于志愿者无限,所以这个东西也一定有解……

我局的我这么渣各位看官看懂的可能性基本上是零……还是推荐BYVoid神犇的题解,比我透彻多了。

代码:

/**************************************************************
    Problem: 1061
    User: danihao123
    Language: C++
    Result: Accepted
    Time:3164 ms
    Memory:6824 kb
****************************************************************/
 
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <utility>
#include <algorithm>
#include <queue>
#include <cassert>
typedef long long ll;
int m, n;
const int maxno = 100005;
const int maxe = 100005;
int first[maxno];
int next[maxe], from[maxe], to[maxe]; ll flow[maxe], cap[maxe];
ll cost[maxe];
int gcnt = 0;
void add_edge(int u, int v, ll f, ll c) {
  gcnt ++;
  next[gcnt] = first[u];
  first[u] = gcnt;
  from[gcnt] = u; to[gcnt] = v;
  cap[gcnt] = f; flow[gcnt] = 0;
  cost[gcnt] = c;
}
int rev(int i) {
  return ((i - 1) ^ 1) + 1;
}
int ins_edge(int u, int v, ll f, ll c = 0LL) {
#ifdef LOCAL
  printf("Inserting Edge (%d, %d, %lld, %lld)\n", u, v, f, c);
#endif
  add_edge(u, v, f, c);
  add_edge(v, u, 0, -c);
  return gcnt - 1;
}
 
const ll LINF = 0x7f7f7f7f7f7f7f7fLL;
int tot;
bool spfa(int s, int t, ll &f, ll &c) {
  static ll d[maxno];
  static bool inq[maxno];
  static ll a[maxno]; static int p[maxno];
  std::fill(d, d + tot + 1, LINF);
  std::fill(inq, inq + tot + 1, false);
  std::fill(a, a + tot + 1, 0LL);
  std::fill(p, p + tot + 1, 0);
  d[s] = 0;
  std::queue<int> Q; Q.push(s); inq[s] = true;
  a[s] = LINF;
  while(!Q.empty()) {
    int u = Q.front(); Q.pop();
    inq[u] = false;
    for(int i = first[u]; i; i = next[i]) {
      if(cap[i] > flow[i]) {
        int v = to[i];
        if(d[v] > d[u] + cost[i]) {
          d[v] = d[u] + cost[i];
          a[v] = std::min(a[u], cap[i] - flow[i]); p[v] = i;
          if(!inq[v]) {
            Q.push(v); inq[v] = true;
          }
        }
      }
    }
  }
  if(a[t] == 0LL) return false;
  f += a[t];
  c += a[t] * d[t];
  for(int e = p[t]; e; e = p[from[e]]) {
    flow[e] += a[t];
    flow[rev(e)] -= a[t];
  }
  return true;
}
void mcmf(int s, int t, ll &f, ll &c) {
  while(spfa(s, t, f, c));
}
 
const int maxn = 1005, maxm = 10005;
int E[maxn];
int A[maxn], D[maxn];
int main() {
  scanf("%d%d", &n, &m);
  int s = 0, t = n + 2; tot = n + 2;
  for(int i = 1; i <= n; i ++) scanf("%d", &A[i]);
  for(int i = 1; i <= n + 1; i ++) {
    D[i] = A[i] - A[i - 1];
    if(D[i] >= 0) {
      E[i] = ins_edge(s, i, D[i]);
    } else {
      E[i] = ins_edge(i, t, -D[i]);
    }
  }
  for(int i = 1; i <= n; i ++) {
    ins_edge(i + 1, i, LINF);
  }
  while(m --) {
    int S, T, C; scanf("%d%d%d", &S, &T, &C);
    ins_edge(S, T + 1, LINF, C);
  }
  ll f = 0, c = 0; mcmf(s, t, f, c);
  printf("%lld\n", c);
  return 0;
}

[BZOJ 3171][TJOI2013]循环格

流量平衡入门中……

我竟然想民白了……

这个题就是要求每个点在且仅在一个环中,这样每个点的入、出度都是1。出度肯定是1,接下来我们想想怎么保证入度为1。

我们建两排点(也可以说是拆点?),一排表示流出,一排表示接受。然后流出点向相邻的接收点连边,费用的话考虑是否和原来这一格的方向不一样就行了。

这个不需要判断是否满流啥的……因为一定有解(比如说每个行构成一个环啥的)。

代码:

/**************************************************************
	Problem: 3171
	User: danihao123
	Language: C++
	Result: Accepted
	Time:28 ms
	Memory:13844 kb
****************************************************************/

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <utility>
#include <queue>
const int maxn = 20;
typedef long long ll;
char S[maxn][maxn];
int tot = 0; int num[maxn][maxn][2];
int R, C;
char D[4] = {'L', 'R', 'U', 'D'};
int tow(int i, int j, char dir, int flag) {
  int dx = 0, dy = 0;
  if(dir == 'L') {
    dx = -1;
  } else if(dir == 'R') {
    dx = 1;
  } else if(dir == 'U') {
    dy = -1;
  } else if(dir == 'D') {
    dy = 1;
  }
  int nx = (i + dy + R) % R;
  int ny = (j + dx + C) % C;
  return num[nx][ny][flag];
}
const int maxno = 100005;
const int maxe = 400005;
int first[maxno];
int next[maxe], from[maxe], to[maxe], flow[maxe], cap[maxe];
ll cost[maxe];
int gcnt = 0;
void add_edge(int u, int v, int f, ll c) {
  gcnt ++;
  next[gcnt] = first[u];
  first[u] = gcnt;
  from[gcnt] = u; to[gcnt] = v;
  cap[gcnt] = f; flow[gcnt] = 0;
  cost[gcnt] = c;
}
int rev(int i) {
  return ((i - 1) ^ 1) + 1;
}
void ins_edge(int u, int v, int f, ll c = 0LL) {
#ifdef LOCAL
  printf("Inserting Edge (%d, %d, %d, %lld)\n", u, v, f, c);
#endif
  add_edge(u, v, f, c);
  add_edge(v, u, 0, -c);
}

const ll LINF = 0x7f7f7f7f7f7f7f7fLL;
bool spfa(int s, int t, int &f, ll &c) {
  static ll d[maxno];
  static bool inq[maxno];
  static int a[maxno], p[maxno];
  std::fill(d, d + tot + 1, LINF);
  std::fill(inq, inq + tot + 1, false);
  std::fill(a, a + tot + 1, 0);
  std::fill(p, p + tot + 1, 0);
  d[s] = 0;
  std::queue<int> Q; Q.push(s); inq[s] = true;
  a[s] = 0x7fffffff;
  while(!Q.empty()) {
    int u = Q.front(); Q.pop();
    inq[u] = false;
    for(int i = first[u]; i; i = next[i]) {
      if(cap[i] > flow[i]) {
        int v = to[i];
        if(d[v] > d[u] + cost[i]) {
          d[v] = d[u] + cost[i];
          a[v] = std::min(a[u], cap[i] - flow[i]); p[v] = i;
          if(!inq[v]) {
            Q.push(v); inq[v] = true;
          }
        }
      }
    }
  }
  if(a[t] == 0) return false;
  f += a[t];
  c += (ll(a[t])) * d[t];
  for(int e = p[t]; e; e = p[from[e]]) {
    flow[e] += a[t];
    flow[rev(e)] -= a[t];
  }
  return true;
}
void mcmf(int s, int t, int &f, ll &c) {
  while(spfa(s, t, f, c));
}

int main() {
  tot = 1; int s = 0, t = 1;
  scanf("%d%d", &R, &C);
  for(int i = 0; i < R; i ++) {
    scanf("%s", S[i]);
    for(int j = 0; j < C; j ++) {
      num[i][j][0] = ++ tot;
      num[i][j][1] = ++ tot;
    }
  }
  for(int i = 0; i < R; i ++) {
    for(int j = 0; j < C; j ++) {
      ins_edge(s, num[i][j][0], 1);
      ins_edge(num[i][j][1], t, 1);
      for(int it = 0; it < 4; it ++) {
        int u = num[i][j][0];
        int v = tow(i, j, D[it], 1);
        ins_edge(u, v, 1, (D[it] == S[i][j]) ? 0LL : 1LL);
      }
    }
  }
  int f = 0; ll c = 0;
  mcmf(s, t, f, c);
  printf("%lld\n", c);
  return 0;
}

[UOJ 110][APIO2015]Bali Sculptures

第一次做subtask题= =其实这题也没啥难度吧

其实就是两个subtask……

对于第一个subtask,就是满足\(1\leq n\leq 100\)且\(1\leq a\leq b\leq n\)。我们应该优先满足高位为\(0\),于是乎可以贪心,从高位枚举到低位,看是否能在满足之前几位的限制条件的同时满足这一位答案为\(0\)。这个判定过程的话,可以设计状态\(d[i][k]\)表示前\(i\)位割成\(k\)段是否满足约束条件,枚举断点\(O(n)\)转移。

第二个subtask,虽然有\(n\leq 2000\)但是满足\(a = 1\)。这意味着分段数想怎么小就怎么小,所以我们可以直接去求这个序列要满足限制条件的话最少可以割成几段,这样的话第二维就可以省掉了。

代码:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <utility>
typedef long long ll;
const int maxn = 2005;
ll Y[maxn], S[maxn];
int n, a, b;
const int maxb = 41;
ll st = 0;
namespace Task1 {
  bool d[105][105];
  bool dp(int bi) {
    memset(d, 0, sizeof(d));
    d[0][0] = true;
    for(int i = 1; i <= n; i ++) {
      for(int k = 1; k <= b; k ++) {
        for(int j = 0; j < i; j ++) {
          ll sum = S[i] - S[j];
          if((sum & (st | (1LL << bi))) == 0LL) {
            d[i][k] = (d[i][k] || d[j][k - 1]);
          }
        }
      }
    }
    for(int k = a; k <= b; k ++) {
      if(d[n][k]) return true;
    }
    return false;
  }
};
namespace Task2 {
  int d[maxn];
  bool dp(int bi) {
    d[0] = 0;
    for(int i = 1; i <= n; i ++) {
      d[i] = 0x3f3f3f3f;
      for(int j = 0; j < i; j ++) {
        ll sum = S[i] - S[j];
        if(!(sum & (st | (1LL << bi)))) {
          d[i] = std::min(d[i], d[j] + 1);
        }
      }
    }
    return (d[n] <= b);
  }
};

ll solve() {
  ll ans = 0;
  for(int i = maxb; i >= 0; i --) {
    bool flag = n <= 100 ? Task1::dp(i) : Task2::dp(i);
    if(flag) {
      st |= (1LL << i);
    } else {
      ans |= (1LL << i);
    }
  }
  return ans;
}

int main() {
  scanf("%d%d%d", &n, &a, &b);
  for(int i = 1; i <= n; i ++) {
    scanf("%lld", &Y[i]);
    S[i] = S[i - 1] + Y[i];
  }
  printf("%lld\n", solve());
  return 0;
}

[SWERC2015]Saint John Festival

第一次做像点样子的计算几何吧……

很显然要包含在某个三角形里,就要被包含在大点的凸包里。所以求出大点组成的凸包,然后对于所有小点判断它是否在那个凸包里即可。

代码:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <utility>
#include <deque>
#include <cmath>
#include <vector>
using R = double;
const R eps = 1e-8;
int sign(R x) {
  if(fabs(x) < eps) {
    return 0;
  } else {
    if(x < 0) return -1;
    else return 1;
  }
}
struct Point {
  R x, y;
  Point(R qx = 0, R qy = 0) {
    x = qx; y = qy;
  }
};
typedef Point Vector;
Vector operator +(const Vector &a, const Vector &b) {
  return Vector(a.x + b.x, a.y + b.y);
}
Vector operator -(const Point &a, const Point &b) {
  return Vector(a.x - b.x, a.y - b.y);
}
R dot(const Vector &a, const Vector &b) {
  return a.x * b.x + a.y * b.y;
}
R times(const Vector &a, const Vector &b) {
  return a.x * b.y - a.y * b.x;
}
bool cmp(const Point &a, const Point &b) {
  if(sign(a.x - b.x) == 0) {
    return a.y < b.y;
  } else {
    return a.x < b.x;
  }
}
const int maxn = 10005;
Point P[maxn]; int L;
std::vector<Point> bot, top;
void andrew() {
  // bot.resize(L); top.resize(L);
  std::sort(P + 1, P + 1 + L, cmp);
  for(int i = 1; i <= L; i ++) {
    if(i != 1 && sign(P[i].x - P[i - 1].x) == 0) continue;
    while(bot.size() > 1 && sign(times(P[i] - bot.back(), bot.back() - bot[bot.size() - 2])) >= 0) {
      bot.pop_back();
    }
    bot.push_back(P[i]);
  }
  for(int i = L; i >= 1; i --) {
    if(i != L && sign(P[i].x - P[i + 1].x) == 0) continue;
    while(top.size() > 1 && sign(times(P[i] - top.back(), top.back() - top[top.size() - 2])) >= 0) {
      top.pop_back();
    }
    top.push_back(P[i]);
  }
  std::reverse(top.begin(), top.end());
#ifdef LOCAL
  printf("top :");
  for(auto p : top) {
    printf(" (%.3f, %.3f)", p.x, p.y);
  }
  puts("");
  printf("bot :");
  for(auto p : bot) {
    printf(" (%.3f, %.3f)", p.x, p.y);
  }
  puts("");
#endif
}
R get_p(Point l, Point r, R x) {
  R delta = x - l.x;
  R k = (r.y - l.y) / (r.x - l.x);
#ifdef LOCAL
  printf("k between (%.2lf, %.2lf) and (%.2lf, %.2lf) : %.5lf\n", l.x, l.y, r.x, r.y, k);
#endif
  return l.y + k * delta;
}
bool cmp2(const Point &a, const Point &b) {
  return sign(a.x - b.x) == -1;
}
bool query(const Point &p) {
  if(sign(top.front().x - p.x) == 1 || sign(p.x - top.back().x) == 1) return false;
  auto lp = (std::lower_bound(bot.begin(), bot.end(), p, cmp2));
  auto rp = (std::lower_bound(top.begin(), top.end(), p, cmp2));
  R l;
  if(sign((*lp).x - p.x) == 0) {
    l = (*lp).y;
  } else {
    auto lp2 = lp - 1;
    l = get_p(*lp2, *lp, p.x);
  }
  R r;
  if(sign((*rp).x - p.x) == 0) {
    r = (*rp).y;
  } else {
    auto rp2 = rp - 1;
    r = get_p(*rp2, *rp, p.x);
  }
#ifdef LOCAL
  printf("bd of (%.2lf, %.2lf) : [%.2lf, %.2lf]\n", p.x, p.y, l, r);
#endif
  return (sign(l - p.y) <= 0 && sign(p.y - r) <= 0);
}

int main() {
  scanf("%d", &L);
  for(int i = 1; i <= L; i ++) {
    scanf("%lf%lf", &P[i].x, &P[i].y);
  }
  andrew();
  int S; scanf("%d", &S);
  int cnt = 0;
  while(S --) {
    R x, y; scanf("%lf%lf", &x, &y);
    Point p(x, y);
    if(query(p)) cnt ++;
  }
  printf("%d\n", cnt);
  return 0;
}

[BZOJ 1070][SCOI2007]修车

啊啊啊我为什么要去颓费用流啊……

这个题可以保证每个人的车最后都会被修,所以说也就是让你最小化总等待时间辣!

直接考虑给一个技术人员建时间轴是麻烦而不可取的。我们考虑一点,对于每一个技术人员,若总共修了\(t\)辆车,那么第\(i\)个来找他修车的人会影响后面人的等待时间,换言之我们可以认为第\(i\)个来修车的人给答案做出了\(c\cdot (t - i + 1)\)(这里用\(c\)表示他自己的修车时间,注意他也会影响他自己)的贡献,似乎可以苟最小费用最大流呢。

但问题在于我们并不能提前知道\(t\)是多少,并且这样的话费用不是递增的,一个人可能会优先考虑到后面的位置去修车。那么我们考虑倒过来,去考虑倒数第\(i\)个来修车的人的贡献,显然这个贡献是\(c\cdot i\)。然后接下来就肥肠简单了,,,

代码:

/**************************************************************
	Problem: 1070
	User: danihao123
	Language: C++
	Result: Accepted
	Time:576 ms
	Memory:13928 kb
****************************************************************/

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <utility>
#include <algorithm>
#include <queue>
#include <cassert>
typedef long long ll;
int m, n;
const int maxno = 100005;
const int maxe = 400005;
int first[maxno];
int next[maxe], from[maxe], to[maxe], flow[maxe], cap[maxe];
ll cost[maxe];
int gcnt = 0;
void add_edge(int u, int v, int f, ll c) {
  gcnt ++;
  next[gcnt] = first[u];
  first[u] = gcnt;
  from[gcnt] = u; to[gcnt] = v;
  cap[gcnt] = f; flow[gcnt] = 0;
  cost[gcnt] = c;
}
int rev(int i) {
  return ((i - 1) ^ 1) + 1;
}
void ins_edge(int u, int v, int f, ll c = 0LL) {
#ifdef LOCAL
  printf("Inserting Edge (%d, %d, %d, %lld)\n", u, v, f, c);
#endif
  add_edge(u, v, f, c);
  add_edge(v, u, 0, -c);
}

const ll LINF = 0x7f7f7f7f7f7f7f7fLL;
int tot;
bool spfa(int s, int t, int &f, ll &c) {
  static ll d[maxno];
  static bool inq[maxno];
  static int a[maxno], p[maxno];
  std::fill(d, d + tot + 1, LINF);
  std::fill(inq, inq + tot + 1, false);
  std::fill(a, a + tot + 1, 0);
  std::fill(p, p + tot + 1, 0);
  d[s] = 0;
  std::queue<int> Q; Q.push(s); inq[s] = true;
  a[s] = 0x7fffffff;
  while(!Q.empty()) {
    int u = Q.front(); Q.pop();
    inq[u] = false;
    for(int i = first[u]; i; i = next[i]) {
      if(cap[i] > flow[i]) {
        int v = to[i];
        if(d[v] > d[u] + cost[i]) {
          d[v] = d[u] + cost[i];
          a[v] = std::min(a[u], cap[i] - flow[i]); p[v] = i;
          if(!inq[v]) {
            Q.push(v); inq[v] = true;
          }
        }
      }
    }
  }
  if(a[t] == 0) return false;
  f += a[t];
  c += (ll(a[t])) * d[t];
  for(int e = p[t]; e; e = p[from[e]]) {
    flow[e] += a[t];
    flow[rev(e)] -= a[t];
  }
  return true;
}
void mcmf(int s, int t, int &f, ll &c) {
  while(spfa(s, t, f, c));
}

int pos[105][105][2];
int main() {
  scanf("%d%d", &m, &n);
  int s = 0, t = 1; tot = 1;
  for(int i = 1; i <= n; i ++) {
    ins_edge(s, ++ tot, 1);
  }
  for(int i = 1; i <= m; i ++) {
    for(int j = 1; j <= n; j ++) {
      pos[i][j][0] = ++ tot;
      pos[i][j][1] = ++ tot;
      ins_edge(tot - 1, tot, 1);
      ins_edge(tot, t, 1);
    }
  }
  for(int i = 1; i <= n; i ++) {
    for(int j = 1; j <= m; j ++) {
      int T; scanf("%d", &T);
      for(int k = 1; k <= n; k ++) {
        int id = pos[j][k][0]; int id2 = pos[j][k][1];
        ins_edge(i + 1, id, 1, k * T);
      }
    }
  }
  int f = 0; ll c = 0;
  mcmf(s, t, f, c);
#ifdef LOCAL
  printf("%d\n", f);
#endif
  printf("%.2lf\n", (double(c)) / (double(n)));
  return 0;
}

[BZOJ 1449][JSOI2009]球队收益

二次函数费用流的入门题……我真太弱了……

可以给比赛、队伍都建点,然后\(S\)向每个比赛连容量为1的边,每个比赛向队伍连容量为\(1\)的边,来表示赢家是谁。这样一来一次比赛只有一个队伍赢了。

考虑怎么处理那个二次函数费用。费用函数为\(f(x, y) = C x^2 + D y^2\),由于一个队伍的总比赛次数是已知的,所以\(y\)不需要,不妨假设一个队伍有\(t\)场比赛,则费用函数为\(f(x) = C x^2 + D(t - x)^2\)。

对这个函数做差分:\(\Delta f(x) = f(x + 1) - f(x) = 2Cx - 2D(t - x) + C + D\),然后这个差分是单调不降的。因此我们对所有差分都从队伍向\(T\)连边(费用为这一点的差分),这样一来的话会优先选择费用小的边,保证这个队伍的费用一直合法。

代码:

/**************************************************************
	Problem: 1449
	User: danihao123
	Language: C++
	Result: Accepted
	Time:732 ms
	Memory:1448 kb
****************************************************************/

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <utility>
#include <queue>
#include <cassert>
typedef long long ll;
const int maxn = 5005;
const int maxm = 1005;
const int maxno = maxn + maxm + 5;
const int maxe = (1000 * 2 + 1000 * 3) * 2 + 50;
int first[maxno];
int next[maxe], from[maxe], to[maxe], flow[maxe], cap[maxe];
ll cost[maxe];
int gcnt = 0;
void add_edge(int u, int v, int f, ll c) {
  gcnt ++;
  next[gcnt] = first[u];
  first[u] = gcnt;
  from[gcnt] = u; to[gcnt] = v;
  cap[gcnt] = f; flow[gcnt] = 0;
  cost[gcnt] = c;
}
int rev(int i) {
  return ((i - 1) ^ 1) + 1;
}
void ins_edge(int u, int v, int f, ll c) {
  add_edge(u, v, f, c);
  add_edge(v, u, 0, -c);
}

int n, m;
const ll LINF = 0x7f7f7f7f7f7f7f7fLL;
bool spfa(int s, int t, int &f, ll &c) {
  static ll d[maxno];
  static bool inq[maxno];
  static int a[maxno], p[maxno];
  std::fill(d, d + n + m + 2, LINF);
  std::fill(inq, inq + n + m + 2, false);
  std::fill(a, a + n + m + 2, 0);
  std::fill(p, p + n + m + 2, 0);
  d[s] = 0;
  std::queue<int> Q; Q.push(s); inq[s] = true;
  a[s] = 0x7fffffff;
  while(!Q.empty()) {
    int u = Q.front(); Q.pop();
    inq[u] = false;
    for(int i = first[u]; i; i = next[i]) {
      if(cap[i] > flow[i]) {
        int v = to[i];
        if(d[v] > d[u] + cost[i]) {
          d[v] = d[u] + cost[i];
          a[v] = std::min(a[u], cap[i] - flow[i]); p[v] = i;
          if(!inq[v]) {
            Q.push(v); inq[v] = true;
          }
        }
      }
    }
  }
  if(a[t] == 0) return false;
  f += a[t];
  c += (ll(a[t])) * d[t];
  for(int e = p[t]; e; e = p[from[e]]) {
    flow[e] += a[t];
    flow[rev(e)] -= a[t];
  }
  return true;
}
void mcmf(int s, int t, int &f, ll &c) {
  while(spfa(s, t, f, c));
}

ll win[maxn], lose[maxn], C[maxn], D[maxn];
ll tot[maxn];
int main() {
  scanf("%d%d", &n, &m);
  ll ans = 0;
  for(int i = 1; i <= n; i ++) {
    scanf("%lld%lld%lld%lld", &win[i], &lose[i], &C[i], &D[i]);
    tot[i] = win[i] + lose[i];
  }
  int s = 0, t = n + m + 1;
  for(int i = 1; i <= m; i ++) {
    int a, b; scanf("%d%d", &a, &b);
    ins_edge(0, i + n, 1, 0);
    ins_edge(i + n, a, 1, 0);
    ins_edge(i + n, b, 1, 0);
    tot[a] ++; tot[b] ++;
  }
  for(int i = 1; i <= n; i ++) {
    ans += C[i] * win[i] * win[i] + D[i] * (tot[i] - win[i]) * (tot[i] - win[i]);
    for(ll j = win[i]; j <= (tot[i] - lose[i] - 1LL); j ++) {
      ins_edge(i, t, 1, 2LL * C[i] * j - 2LL * D[i] * (tot[i] - j) + C[i] + D[i]);
    }
  }
  int f = 0; mcmf(s, t, f, ans);
  printf("%lld\n", ans);
  return 0;
}

[LibreOJ 2146][SHOI2017]寿司餐厅

建了半天图……搞出来了几个神奇的最小割……

然后发现这TM不就是最简单的最大权闭合子图吗……

首先和正负点权普通的最大权闭合子图一样,然后任意\(d_{i, i}\)去依赖点\(i\)。对于一个\(d_{i, j}(i < j)\),我们要保证那一天\([i, j]\)全部被吃了,所以说它需要依赖\(d_{i + 1, j}\)和\(d_{i, j - 1}\)。

每种寿司的费用也不难处理,我们把\(mx^2\)和\(cx\)分开考虑。\(mx^2\)单独建点,很显然代号为所有\(x\)都要依赖它。对于\(cx\),可以视为所有点有一个\(x\)的费用。

然后van了……

代码:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <cassert>
#include <algorithm>
#include <utility>
#include <vector>
#include <queue>
const int maxn = 5000005;
const int maxm = 5000005;
int first[maxn];
int next[maxm], to[maxm], cap[maxm], flow[maxm];
int gcnt = 0;
void add_edge(int u, int v, int f) {
  gcnt ++;
  next[gcnt] = first[u];
  first[u] = gcnt;
  to[gcnt] = v;
  cap[gcnt] = f;
  flow[gcnt] = 0;
}
int rev(int i) {
  if(1 & i) {
    return i + 1;
  } else {
    return i - 1;
  }
}
void ins_edge(int u, int v, int f) {
  add_edge(u, v, f); add_edge(v, u, 0);
}

int s, t;
int d[maxn];
bool bfs() {
#ifdef LOCAL
  // puts("A new round :");
#endif
  static bool vis[maxn];
  memset(vis, 0, sizeof(vis));
  d[s] = 1; vis[s] = true;
  std::queue<int> Q; Q.push(s);
  while(!Q.empty()) {
    int u = Q.front(); Q.pop();
    for(int i = first[u]; i; i = next[i]) {
      int v = to[i];
      if(cap[i] > flow[i] && !vis[v]) {
        d[v] = d[u] + 1; vis[v] = true;
#ifdef LOCAL
        // printf("d[%d] : %d\n", v, d[v]);
#endif
        Q.push(v);
      }
    }
  }
  return vis[t];
}
int cur[maxn];
int dfs(int u, int a) {
#ifdef LOCAL
  // printf("State (%d, %d)\n", u, a);
#endif
  if(a == 0 || u == t) return a;
  int fl = 0;
  for(int &i = cur[u]; i; i = next[i]) {
    int v = to[i]; int f;
    if(d[v] == d[u] + 1 && (f = dfs(v, std::min(cap[i] - flow[i], a))) > 0) {
      fl += f; a -= f;
      flow[i] += f; flow[rev(i)] -= f;
      if(a == 0) break;
    }
  }
  if(a > 0) d[u] = -1;
  return fl;
}
int tot;
int dinic() {
  int ans = 0;
  while(bfs()) {
    for(int i = 0; i <= tot; i ++) cur[i] = first[i];
    ans += dfs(s, 0x7fffffff);
  }
  return ans;
}

int ma_p[105], ma_m[1005];
int ma_d[105][105];
int main() {
  tot = 1;
  int n, m; scanf("%d%d", &n, &m);
  int ans = 0;
  s = 0, t = 1;
  for(int i = 1; i <= n; i ++) {
    int a; scanf("%d", &a);
    ma_p[i] = ++ tot;
#ifdef LOCAL
    printf("p[%d] : %d\n", i, tot);
#endif
    if(!ma_m[a]) {
      ma_m[a] = ++ tot;
#ifdef LOCAL
      printf("m[%d] : %d\n", a, tot);
#endif
      ins_edge(tot, 1, m * a * a);
    }
    ins_edge(ma_p[i], 1, a);
    ins_edge(ma_p[i], ma_m[a], 0x7fffffff);
  }
  for(int i = 1; i <= n; i ++) {
    ma_d[i][i] = ++ tot;
    for(int j = i + 1; j <= n; j ++) {
      ma_d[i][j] = ++ tot;
#ifdef LOCAL
      printf("ma_d[%d][%d] : %d\n", i, j, tot);
#endif
    }
  }
  for(int i = 1; i <= n; i ++) {
    int d_i; scanf("%d", &d_i);
    if(d_i >= 0) {
      ans += d_i;
      ins_edge(0, ma_d[i][i], d_i);
    } else {
      ins_edge(ma_d[i][i], 1, -d_i);
    }
    ins_edge(ma_d[i][i], ma_p[i], 0x7fffffff);
    for(int j = i + 1; j <= n; j ++) {
      scanf("%d", &d_i);
      int np = ma_d[i][j];
      if(d_i >= 0) {
        ans += d_i;
        ins_edge(0, np, d_i);
        ins_edge(np, ma_d[i][j - 1], 0x7fffffff);
        ins_edge(np, ma_d[i + 1][j], 0x7fffffff);
      } else {
        ins_edge(np, 1, -d_i);
        ins_edge(np, ma_d[i][j - 1], 0x7fffffff);
        ins_edge(np, ma_d[i + 1][j], 0x7fffffff);
      }
    }
  }
  ans -= dinic();
#ifdef LOCAL
  for(int i = 0; i <= tot; i ++) {
    for(int j = first[i]; j; j = next[j]) {
      if(!(j & 1)) continue;
      int v = to[j];
      if(cap[j] == flow[j]) {
        printf("Edge (%d, %d, %d) deleted.\n", i, v, cap[j]);
      }
    }
  }
#endif
  printf("%d\n", ans);
  return 0;
}

[LibreOJ 2142][SHOI2017]相逢是问候

f**********ck我终于肝掉这道题了……在这种辣鸡题上浪费了大量时间

首先一堆\(c\)套起来的幂让我们想到了上帝与集合的正确用法那道题(这题题解我屯到现在没写,还是算了吧就写这题得了)……那让我们试试欧拉降幂公式?

观察欧拉降幂公式

\[a^x\equiv a^{x\mod \varphi(m) + \varphi(m)}\pmod{m}(a\geq m)\]

在多次降幂之后,最后肯定存在一个膜数为1,最后在对这一个地方进行操作的话,虽然\(A[i]\)的位置会被移到更高的次幂上,但是开始出现膜数1的地方一定只能取到1了,所以再对这些地方操作是不合理的。

每次取\(\varphi(x)\)都会使数至少下降一半,所以一个数最多被操作\(\log_2 p\)次。

然后我就跪了(虽然在BZOJ上水过去了),后来手肝gprof发现瓶颈在快速幂(逃

然后这TM怎么优化……膜了一发题解发现正解非常的KBS……

对于一个幂,他的指数肯定可以表示为\(2^{16} a + b\)且\(a\)最大的形式……然后我们可能用到的膜数是很少的,对于每种膜数大力预处理可能的\(b\)的答案和\(a\)的答案,然后求一次幂就\(O(1)\)了……

代码:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <cmath>
#include <algorithm>
#include <utility>
#include <climits>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/hash_policy.hpp>
const int maxn = 50005;
const int maxno = maxn << 2;
const int siz = 65536;
typedef long long ll;
inline ll phi(ll x) {
  ll bd = sqrt(x + 0.5);
  ll ans = x;
  for(ll i = 2; i <= bd; i ++) {
    if(x % i == 0LL) {
      ans = ans / i * (i - 1LL);
      while(x % i == 0LL) {
        x /= i;
      }
    }
  }
  if(x > 1LL) ans = ans / x * (x - 1LL);
  return ans;
}
int n, m;
ll p, c;
ll f[100]; int fn, bd;
ll pow1[70][siz], pow2[70][siz];
inline void process() {
  f[0] = p; fn = 1LL;
  for(int i = 1; ; i ++) {
    f[i] = phi(f[i - 1]);
    if(f[i] == 1LL) {
      fn = i;
      break;
    }
  }
  ll tmp = 1LL; bd = 0;
  if(c != 1LL) {
    while(tmp * c < p) {
      bd ++; tmp *= c;
    }
  } else {
    bd = 0x7fffffff;
  }
  f[69] = LLONG_MAX;
  for(int i = 0; i <= fn; i ++) {
    ll c1 = c % f[i]; ll c2 = c1;
    int t = 16; while(t --) c2 = (c2 * c2) % f[i];
    pow1[i][0] = 1LL; if(i == fn) pow1[i][0] = 0;
    for(int j = 1; j < siz; j ++) pow1[i][j] = (pow1[i][j - 1] * c1) % f[i];
    pow2[i][0] = 1LL; if(i == fn) pow2[i][0] = 0;
    for(int j = 1; j < siz; j ++) pow2[i][j] = (pow2[i][j - 1] * c2) % f[i];
  }
  int i = 69;
  for(int g = 0; g <= 0; g ++) {
    ll c1 = c % f[i]; ll c2 = c1;
    int t = 16; while(t --) c2 = (c2 * c2) % f[i];
    pow1[i][0] = 1LL;
    for(int j = 1; j < siz; j ++) pow1[i][j] = (pow1[i][j - 1] * c1) % f[i];
    pow2[i][0] = 1LL;
    for(int j = 1; j < siz; j ++) pow2[i][j] = (pow2[i][j - 1] * c2) % f[i];
  }
}
inline ll pow_mod(ll a, ll b, ll p) {
  return (pow1[p][b & (siz - 1)] * pow2[p][b >> 16]) % f[p];
}
ll A[maxn];
int be_done[maxn]; ll sumv[maxno];
bool break_down[maxno];
inline void maintain(int o) {
  int lc = o << 1, rc = o << 1 | 1;
  sumv[o] = (sumv[lc] + sumv[rc]);
  if(sumv[o] >= p) sumv[o] -= p;
  break_down[o] = (break_down[lc] && break_down[rc]);
}

inline ll get_ans(int i) {
  register ll up;
  for(int j = be_done[i]; j >= 0; j --) {
    if(j == be_done[i]) {
      up = A[i];
    } else {
      if(up > bd) {
        up = pow_mod(c % f[j], up, j) + f[j];
      } else {
        up = pow_mod(c, up, 69);
      }
    }
  }
  return up;
}
void build_tree(int o, int L, int R) {
  if(L == R) {
    be_done[L] = 0;
    break_down[o] = 0;
    sumv[o] = A[L];
  } else {
    int M = (L + R) / 2;
    build_tree(o << 1, L, M);
    build_tree(o << 1 | 1, M + 1, R);
    maintain(o);
  }
}
int ql, qr;
void modify(int o, int L, int R) {
  if(L == R) {
    be_done[L] ++;
    if(c == 1LL) {
      sumv[o] = 1LL;
      break_down[o] = true;
    } else {
      sumv[o] = get_ans(L) % p;
      if((A[L] != 0LL && be_done[L] == fn) || (A[L] == 0LL && be_done[L] == fn + 1)) break_down[o] = true;
    }
  } else {
    int M = (L + R) / 2;
    if(ql <= M && !break_down[o << 1]) {
      modify(o << 1, L, M);
    }
    if(qr > M && !break_down[o << 1 | 1]) {
      modify(o << 1 | 1, M + 1, R);
    }
    maintain(o);
  }
}
ll query(int o, int L, int R) {
  if(ql <= L && R <= qr) {
    return sumv[o];
  } else {
    int M = (L + R) / 2;
    ll ans = 0;
    if(ql <= M) {
      ans = (ans + query(o << 1, L, M));
      if(ans >= p) ans -= p;
    }
    if(qr > M) {
      ans = (ans + query(o << 1 | 1, M + 1, R));
      if(ans >= p) ans -= p;
    }
    return ans;
  }
}

int main() {
  // freopen("17.in", "r", stdin);
  // freopen("dummy", "w", stdout);
  scanf("%d%d%lld%lld", &n, &m, &p, &c);
  process();
  for(int i = 1; i <= n; i ++) scanf("%lld", &A[i]);
  build_tree(1, 1, n);
  while(m --) {
    int op; scanf("%d%d%d", &op, &ql, &qr);
    if(op == 0) {
      modify(1, 1, n);
    } else {
      printf("%lld\n", query(1, 1, n));
      // fflush(stdout);
    }
  }
  return 0;
}

[BZOJ 1857][SCOI2010]传送带

三分套三分入门题……

策略肯定是从A走到AB上一点,然后再走到CD上的一个点,再向D走。

很显然答案函数是一个关于那两个点下凸的东西(不会证?GeoGebra之类的东西画一下就好啦!还不如像我这样口胡),所以我们可以先对第一维三分,然后套上对第二维的三分……

代码:

/**************************************************************
	Problem: 1857
	User: danihao123
	Language: C++
	Result: Accepted
	Time:244 ms
	Memory:820 kb
****************************************************************/

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <utility>
#include <cmath>
typedef double R;
const R eps = 1e-6;
int sign(R x) {
  if(fabs(x) < eps) {
    return 0;
  } else {
    if(x < 0) return -1;
    else return 1;
  }
}
struct Point {
  R x, y;
  Point(R qx = 0, R qy = 0) {
    x = qx; y = qy;
  }
};
typedef Point Vector;
Vector operator +(const Vector &a, const Vector &b) {
  return Vector(a.x + b.x, a.y + b.y);
}
Vector operator -(const Point &a, const Point &b) {
  return Vector(a.x - b.x, a.y - b.y);
}
Vector operator *(R x, const Vector &v) {
  return Point(v.x * x, v.y * x);
}
Vector operator *(const Vector &v, R x) {
  return Point(v.x * x, v.y * x);
}
R dot(const Vector &a, const Vector &b) {
  return a.x * b.x + a.y * b.y;
}
R times(const Vector &a, const Vector &b) {
  return a.x * b.y - a.y * b.x;
}
R dist(const Point &a, const Point &b) {
  return sqrt(dot(a - b, a - b));
}
bool cmp(const Point &a, const Point &b) {
  if(sign(a.x - b.x) == 0) {
    return a.y < b.y;
  } else {
    return a.x < b.x;
  }
}
Point A, B, C, D;
R p, q, r;
Vector D_AB, D_DC;
R f(const Point &AB, const Point &CD) {
  return (dist(AB, A) / p + dist(CD, D) / q + dist(AB, CD) / r);
}
R F(Point AB) {
  R L = 0, R = 1;
  int T = 200;
  while(T --) {
    double M1 = L + (R - L) / 3;
    double M2 = R - (R - L) / 3;
    Point P1 = D + M1 * D_DC;
    Point P2 = D + M2 * D_DC;
    double f1 = f(AB, P1), f2 = f(AB, P2);
    if(f1 < f2) {
      R = M2;
    } else {
      L = M1;
    }
  }
  return f(AB, D + L * D_DC);
}
R solve() {
  R L = 0, R = 1;
  int T = 200;
  while(T --) {
    double M1 = L + (R - L) / 3;
    double M2 = R - (R - L) / 3;
    Point P1 = A + M1 * D_AB;
    Point P2 = A + M2 * D_AB;
    double F1 = F(P1), F2 = F(P2);
    if(F1 < F2) {
      R = M2;
    } else {
      L = M1;
    }
  }
  return F(A + L * D_AB);
}

int main() {
  scanf("%lf%lf%lf%lf", &A.x, &A.y, &B.x, &B.y);
  scanf("%lf%lf%lf%lf", &C.x, &C.y, &D.x, &D.y);
  scanf("%lf%lf%lf", &p, &q, &r);
  D_AB = B - A; D_DC = C - D;
  printf("%.2lf\n", solve());
  return 0;
}

[BZOJ 3944]Sum

杜教筛模板题……(然而常数卡卡卡

我还是讲一讲基础的东西吧……

先考虑求欧拉函数的前缀和,我们先找一个它的卷积(这里用\(f=\varphi\ast 1\),且设\(g = 1\)),发现有(用\(S\)表示原函数的前缀和):

\[\sum_{i = 1}^n f(i) = \sum_{i = 1}^n g(i)\times S(\lfloor\frac{n}{i}\rfloor) = \frac{n(n + 1)}{2}\]

第二个和式的第一项是\(g(1)\times S(n) = S(n)\),也就是我们需要的答案。这样的话我们把第一项分割出来,后面的项大力求,然后用\(\frac{n(n + 1)}{2}\)减去那些项就得到了第一项。并且注意到\(\lfloor\frac{n}{i}\rfloor\)的取值种类是\(\sqrt{n}\)级别的,又可以大力优化一波。然后考虑预处理\(O(n^{\frac{2}{3}})\)级别的前缀和(时间和空间都允许),再优化一波。然后考虑把没预处理的状态扔一个哈希表里(反正看起来不会很多),又优化一波……

这样复杂度就是\(O(n^{\frac{2}{3}})\)的了……(又不会证了

求莫比乌斯函数的前缀和的方法类似(并且更简单一点)……

代码(又被卡成苟了):

/**************************************************************
	Problem: 3944
	User: danihao123
	Language: C++
	Result: Accepted
	Time:11292 ms
	Memory:127740 kb
****************************************************************/

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <utility>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/hash_policy.hpp>
typedef long long ll;
const int N = 3500000;
const int maxn = N + 5;
ll phi[maxn], phi_S[maxn];
ll mu[maxn], mu_S[maxn];
void sieve() {
  static bool vis[maxn];
  static int prm[maxn]; int cnt = 0;
  phi[1] = 1; mu[1] = 1;
  for(int i = 2; i <= N; i ++) {
    if(!vis[i]) {
      prm[cnt ++] = i;
      mu[i] = -1; phi[i] = i - 1;
    }
    for(int j = 0; j < cnt && prm[j] * (ll(i)) <= (ll(N)); j ++) {
      int v = prm[j] * i;
      vis[v] = true;
      if(i % prm[j] == 0) {
        mu[v] = 0; phi[v] = phi[i] * prm[j];
        break;
      } else {
        mu[v] = mu[i] * -1;
        phi[v] = phi[i] * phi[prm[j]];
      }
    }
  }
  for(int i = 1; i <= N; i ++) {
    phi_S[i] = phi_S[i - 1] + phi[i];
    mu_S[i] = mu_S[i - 1] + mu[i];
  }
}
__gnu_pbds::gp_hash_table<ll, ll> mu_d;
ll mu_sum(ll n) {
  if(n <= (ll(N))) return mu_S[n];
  if(mu_d.find(n) != mu_d.end()) {
    return mu_d[n];
  }
  ll ans = 1;
  for(ll i = 2; i <= n;) {
    ll nx = n / (n / i);
    ans -= (nx - i + 1LL) * mu_sum(n / i);
    i = nx + 1LL;
  }
  mu_d[n] = ans;
  return ans;
}
ll pre_sum(ll n) {
  return (n * (n + 1LL) / 2LL);
}
__gnu_pbds::gp_hash_table<ll, ll> phi_d;
ll phi_sum(ll n) {
  if(n <= (ll(N))) return phi_S[n];
  if(phi_d.find(n) != phi_d.end()) {
    return phi_d[n];
  }
  ll ans = pre_sum(n);
  for(ll i = 2; i <= n;) {
    ll nx = n / (n / i);
    ans -= (nx - i + 1LL) * phi_sum(n / i);
    i = nx + 1LL;
  }
  phi_d[n] = ans;
  return ans;
}

int main() {
  sieve();
  int T; scanf("%d", &T);
  while(T --) {
    int n; scanf("%d", &n);
    printf("%lld %lld\n", phi_sum(n), mu_sum(n));
  }
  return 0;
}