6 月 20 日省选???不是吧。。。
bzoj - 1449:设第 i 球队总共打了 di 场,如果胜 xi 场则败 di - xi 场,即收益可表示为 xi 的二次函数。把胜利次数看成“资源”转成资源分配模型,二次代价 x^2 拆开 1 + 3 + 5 + ...。然后最小费用最大流。
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 5000;
const int MAXM = 1000;
namespace FlowGraph{
const int MAXV = 2*MAXN, MAXE = 50*MAXM, INF = (1<<30);
struct edge{
int to, cap, flow, cost;
edge *nxt, *rev;
}edges[MAXE + 5], *adj[MAXV + 5], *cur[MAXV + 5], *ecnt = edges;
void addedge(int u, int v, int c, int w) {
edge *p = (++ecnt), *q = (++ecnt);
p->to = v, p->nxt = adj[u], adj[u] = p;
p->cap = c, p->flow = 0, p->cost = w;
q->to = u, q->nxt = adj[v], adj[v] = q;
q->cap = 0, q->flow = 0, q->cost = -w;
p->rev = q, q->rev = p;
// printf("! %d %d %d %d
", u, v, c, w);
}
int hp[MAXV + 5], f[MAXV + 5], s, t;
void update(int x, int k) {
f[x] = k;
while( x ) {
hp[x] = x;
if( (x<<1) <= t && f[hp[x<<1]] < f[hp[x]] )
hp[x] = hp[x << 1];
if( (x<<1|1) <= t && f[hp[x<<1|1]] < f[hp[x]] )
hp[x] = hp[x << 1 | 1];
x >>= 1;
}
}
int d[MAXV + 5], h[MAXV + 5];
bool relabel() {
for(int i=1;i<=t;i++)
h[i] += d[i], d[i] = f[i] = INF, hp[i] = i, cur[i] = adj[i];
update(t, d[t] = 0);
while( f[hp[1]] != INF ) {
int x = hp[1]; update(x, INF);
for(edge *p=adj[x];p;p=p->nxt) {
int c = p->rev->cost + h[x] - h[p->to];
if( d[p->to] > d[x] + c && p->rev->cap > p->rev->flow )
update(p->to, d[p->to] = d[x] + c);
}
}
return d[s] != INF;
}
bool vis[MAXV + 5];
int aug(int x, int tot) {
if( x == t ) return tot;
vis[x] = true; int sum = 0;
for(edge *&p=cur[x];p;p=p->nxt) {
int c = p->cost + h[p->to] - h[x];
if( d[p->to] + c == d[x] && !vis[p->to] && p->cap > p->flow ) {
int del = aug(p->to, min(tot - sum, p->cap - p->flow));
sum += del, p->flow += del, p->rev->flow -= del;
if( sum == tot ) break;
}
}
vis[x] = false; return sum;
}
int min_cost_max_flow(int _s, int _t) {
int cost = 0; s = _s, t = _t;
while( relabel() ) {
int del = aug(s, INF);
cost += del * (d[s] + h[s]);
}
return cost;
}
}
int d[MAXN + 5], n, m;
int w[MAXN + 5], l[MAXN + 5], C[MAXN + 5], D[MAXN + 5];
int pw2(int x) {return x * x;}
int func(int i, int k) {
return C[i]*pw2(w[i] + k) + D[i]*pw2(l[i] + d[i] - k);
}
int main() {
scanf("%d%d", &n, &m);
for(int i=1;i<=n;i++)
scanf("%d%d%d%d", &w[i], &l[i], &C[i], &D[i]);
int s = n + m + 1, t = n + m + 2;
for(int i=1,a,b;i<=m;i++) {
scanf("%d%d", &a, &b), d[a]++, d[b]++;
FlowGraph::addedge(s, n + i, 1, 0);
FlowGraph::addedge(n + i, a, 1, 0);
FlowGraph::addedge(n + i, b, 1, 0);
}
int ans = 0;
for(int i=1;i<=n;i++) {
ans += func(i, 0);
for(int j=1;j<=d[i];j++)
FlowGraph::addedge(i, t, 1, func(i, j) - func(i, j - 1));
}
printf("%d
", ans + FlowGraph::min_cost_max_flow(s, t));
}
uoj - 455:模拟费用流模板题。扫描到送餐员,只有送餐员会反悔;扫描到餐厅时,两者都可能反悔,此时送餐员反悔的代价全部一样,因此集中处理。关于模拟费用流的博客1,博客2。
#include <queue>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef pair<ll, int> pli;
#define fi first
#define se second
const int MAXN = 100000;
const ll INF = ll(1E12);
struct node{
int x, w, c; node() {}
node(int _x, int _w, int _c) : x(_x), w(_w), c(_c) {}
friend bool operator < (node a, node b) {return a.x < b.x;}
}a[2*MAXN + 5];
int main() {
int n, m; ll sum = 0; scanf("%d%d", &n, &m);
for(int i=1;i<=n;i++) scanf("%d", &a[i].x), a[i].w = 0, a[i].c = -1;
for(int i=n+1;i<=n+m;i++) scanf("%d%d%d", &a[i].x, &a[i].w, &a[i].c), sum += a[i].c;
sort(a + 1, a + n + m + 1);
if( sum < n ) {
puts("-1");
return 0;
}
else {
ll ans = 0;
priority_queue<pli, vector<pli>, greater<pli> >q1;
priority_queue<pli, vector<pli>, greater<pli> >q2;
q2.push(make_pair(INF, n)); // 必须要插入一个哨兵结点
for(int i=1;i<=n+m;i++) {
if( a[i].c == -1 ) {
pli p = q2.top(); q2.pop();
ans += (p.fi + a[i].x); p.se--;
q1.push(make_pair(-(p.fi + a[i].x) - a[i].x, 1));
if( p.se ) q2.push(p);
}
else {
int cnt = a[i].c, tmp = 0;
while( !q1.empty() && q1.top().fi + a[i].w + a[i].x < 0 && cnt ) {
pli p = q1.top(); int t = min(cnt, p.se); q1.pop();
ans += t * (p.fi + a[i].w + a[i].x);
tmp += t, cnt -= t, p.se -= t;
if( p.se ) q1.push(p);
q2.push(make_pair(-(p.fi + a[i].w + a[i].x) + a[i].w - a[i].x, t));
}
if( tmp ) q1.push(make_pair(-(a[i].w + a[i].x), tmp));
if( cnt ) q2.push(make_pair(a[i].w - a[i].x, cnt));
}
}
printf("%lld
", ans);
}
}
loj - 6405:树上模拟费用流模板题。甚至不需要优化(把反悔代价相同的存储在一起,这样做是 O(NlogN))可以直接 O(XlogX) 过。不过可并堆中总结点数需要足够大否则要 RE(理论上好像是 4*X,不过开大点更保险)。
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 250000;
const int MAXA = 1000000;
const ll INF = ll(1E12);
namespace heap{
struct node{
ll key; int dis;
node *ch[2];
}pl[6*MAXA + 5], *NIL = pl, *ncnt = pl;
node *newnode(ll k) {
node *p = (++ncnt);
p->key = k, p->dis = 0;
p->ch[0] = p->ch[1] = NIL;
return p;
}
node *merge(node *a, node *b) {
if( a == NIL ) return b;
if( b == NIL ) return a;
if( a->key > b->key ) swap(a, b);
a->ch[1] = merge(a->ch[1], b);
if( a->ch[0]->dis < a->ch[1]->dis ) swap(a->ch[0], a->ch[1]);
a->dis = a->ch[1]->dis + 1;
return a;
}
node *insert(node *x, ll k) {return merge(x, newnode(k));}
node *erase(node *x) {return merge(x->ch[0], x->ch[1]);}
};
struct edge{
int to, dis;
edge *nxt;
}edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt = edges;
void addedge(int u, int v, int c) {
edge *p = (++ecnt);
p->to = v, p->dis = c, p->nxt = adj[u], adj[u] = p;
p = (++ecnt);
p->to = u, p->dis = c, p->nxt = adj[v], adj[v] = p;
}
heap::node *rta[MAXN + 5], *rtb[MAXN + 5];
int a[MAXN + 5], b[MAXN + 5], n;
ll dfs(int x, int f, ll d) {
ll ret = 0; rta[x] = rtb[x] = heap::NIL;
if( a[x] > b[x] ) {
for(int i=1;i<=a[x]-b[x];i++)
rta[x] = heap::insert(rta[x], d);
}
else {
for(int i=1;i<=b[x]-a[x];i++)
rtb[x] = heap::insert(rtb[x], -(d + INF - 2*d) + d), ret += d + INF - 2*d;
}
for(edge *p=adj[x];p;p=p->nxt) {
if( p->to == f ) continue;
ret += dfs(p->to, x, d + p->dis);
rta[x] = merge(rta[x], rta[p->to]);
rtb[x] = merge(rtb[x], rtb[p->to]);
while( rta[x] != heap::NIL && rtb[x] != heap::NIL && rta[x]->key + rtb[x]->key - 2*d < 0 ) {
ll p = rta[x]->key, q = rtb[x]->key; ret += p + q - 2*d;
rta[x] = heap::erase(rta[x]), rtb[x] = heap::erase(rtb[x]);
rta[x] = heap::insert(rta[x], -(p + q - 2*d) + p);
rtb[x] = heap::insert(rtb[x], -(p + q - 2*d) + q);
}
}
return ret;
}
int main() {
scanf("%d", &n);
for(int i=1,u,v,c;i<n;i++)
scanf("%d%d%d", &u, &v, &c), addedge(u, v, c);
for(int i=1;i<=n;i++)
scanf("%d%d", &a[i], &b[i]);
printf("%lld
", dfs(1, 0, 0));
}
loj - 2510:观察到树高不超过 40,不妨以树高为状态设计 dp。定义 dp[p][q][i] 表示以 i 为根的子树,到根的路径上有 p 条红边,q 条绿边的最小代价。枚举保留红边/绿边 O(1) 转移,时间复杂度 O(40^2*n)。
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 20000;
int d[2][2*MAXN + 5], fa[2*MAXN + 5], ch[2][2*MAXN + 5];
ll sac[2*MAXN + 5], sbc[2*MAXN + 5], sc[2*MAXN + 5];
ll dp[42][42][2*MAXN + 5];
int main() {
int n; scanf("%d", &n);
for(int i=1;i<n;i++) {
scanf("%d%d", &ch[0][i], &ch[1][i]);
if( ch[0][i] < 0 ) ch[0][i] = n - ch[0][i] - 1;
if( ch[1][i] < 0 ) ch[1][i] = n - ch[1][i] - 1;
fa[ch[0][i]] = fa[ch[1][i]] = i;
}
ll ans = 0;
for(int i=n,a,b,c;i<2*n;i++) {
scanf("%d%d%d", &a, &b, &c);
ans += 1LL*a*b*c, sac[i] = 1LL*a*c, sbc[i] = 1LL*b*c, sc[i] = c;
}
for(int i=1;i<n;i++)
for(int p=0;p<=1;p++)
for(int q=0;q<=1;q++)
d[p][ch[q][i]] = d[p][i] + (p == q);
for(int i=2*n-1;i>=2;i--)
sac[fa[i]] += sac[i], sbc[fa[i]] += sbc[i], sc[fa[i]] += sc[i];
for(int i=n-1;i>=1;i--)
for(int p=d[0][i];p>=0;p--)
for(int q=d[1][i];q>=0;q--) {
int c0 = ch[0][i], c1 = ch[1][i];
ll s1 = sc[c0]*q + sbc[c0] + dp[p + 1][q][c0] + dp[p][q][c1];
ll s2 = sc[c1]*p + sac[c1] + dp[p][q][c0] + dp[p][q + 1][c1];
dp[p][q][i] = min(s1, s2);
}
printf("%lld
", ans + dp[0][0][1]);
}
bzoj - 5403:简单费用流建模。源点连向 i,j 同偶的非禁止位置, i,j 同奇的非禁止位置连向相邻危险点,危险点拆点中间连费用为危险值,然后右边类似地连向 i,j 同奇的非禁止位置,再把这些点连向汇点。跑流量 <= m 的最大费用流。
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 50;
const int dx[] = {1, -1, 0, 0}, dy[] = {0, 0, 1, -1};
namespace FlowGraph{
const int MAXV = 2*MAXN*MAXN, MAXE = 20*MAXV, INF = (1 << 30);
struct edge{
int to, cap, flow, cost;
edge *nxt, *rev;
}edges[MAXE + 5], *adj[MAXV + 5], *cur[MAXV + 5], *ecnt = edges;
int d[MAXV + 5], h[MAXV + 5], s, t;
void addedge(int u, int v, int c, int w) {
edge *p = (++ecnt), *q = (++ecnt);
p->to = v, p->cap = c, p->flow = 0, p->cost = w;
p->nxt = adj[u], adj[u] = p;
q->to = u, q->cap = 0, q->flow = 0, q->cost = -w;
q->nxt = adj[v], adj[v] = q;
p->rev = q, q->rev = p;
// printf("! %d %d %d %d
", u, v, c, w);
}
int f[MAXV + 5], hp[MAXV + 5];
void update(int x, int k) {
f[x] = k;
while( x ) {
hp[x] = x;
if( (x<<1) <= t && f[hp[x<<1]] < f[hp[x]] )
hp[x] = hp[x<<1];
if( (x<<1|1) <= t && f[hp[x<<1|1]] < f[hp[x]] )
hp[x] = hp[x<<1|1];
x >>= 1;
}
}
bool relabel() {
for(int i=1;i<=t;i++)
h[i] += d[i], f[i] = d[i] = INF, cur[i] = adj[i], hp[i] = i;
update(t, d[t] = 0);
while( f[hp[1]] != INF ) {
int x = hp[1]; update(x, INF);
for(edge *p=adj[x];p;p=p->nxt) {
int c = p->rev->cost + h[x] - h[p->to];
if( p->rev->cap > p->rev->flow && d[p->to] > d[x] + c )
update(p->to, d[p->to] = d[x] + c);
}
}
return d[s] != INF;
}
bool vis[MAXV + 5];
int aug(int x, int tot) {
if( x == t ) return tot;
vis[x] = true; int sum = 0;
for(edge *&p=cur[x];p;p=p->nxt) {
int c = p->cost + h[p->to] - h[x];
if( d[x] == d[p->to] + c && !vis[p->to] && p->cap > p->flow ) {
int del = aug(p->to, min(tot - sum, p->cap - p->flow));
sum += del, p->flow += del, p->rev->flow -= del;
if( sum == tot ) break;
}
}
vis[x] = false; return sum;
}
int min_cost_flow(int _s, int _t, int l) {
int cost = 0; s = _s, t = _t;
while( l && relabel() && (d[s] + h[s]) < 0 ) {
int del = aug(s, l); l -= del;
cost += (d[s] + h[s]) * del;
}
return cost;
}
}
int A[MAXN + 5][MAXN + 5], id[2][MAXN + 5][MAXN + 5], cnt;
int main() {
int ans = 0, n, m, k; scanf("%d%d%d", &n, &m, &k);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d", &A[i][j]), ans += A[i][j];
for(int i=1,X,Y;i<=k;i++)
scanf("%d%d", &X, &Y), A[X][Y] = -1;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) {
if( A[i][j] == -1 ) continue;
else if( A[i][j] == 0 ) {
if( (i + j) % 2 == 0 )
id[0][i][j] = (++cnt);
}
else id[0][i][j] = (++cnt), id[1][i][j] = (++cnt);
}
int s = (++cnt), t = (++cnt);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) {
if( A[i][j] == -1 ) continue;
if( i % 2 == 0 && j % 2 == 0 ) {
FlowGraph::addedge(s, id[0][i][j], 1, 0);
for(int p=0;p<4;p++) {
int x = i + dx[p], y = j + dy[p];
if( x < 1 || y < 1 || x > n || y > n || !A[x][y] ) continue;
FlowGraph::addedge(id[0][i][j], id[0][x][y], 1, 0);
}
}
else if( i % 2 == 1 && j % 2 == 1 ) {
FlowGraph::addedge(id[0][i][j], t, 1, 0);
for(int p=0;p<4;p++) {
int x = i + dx[p], y = j + dy[p];
if( x < 1 || y < 1 || x > n || y > n || !A[x][y] ) continue;
FlowGraph::addedge(id[1][x][y], id[0][i][j], 1, 0);
}
}
else if( A[i][j] )
FlowGraph::addedge(id[0][i][j], id[1][i][j], 1, -A[i][j]);
}
printf("%d
", ans + FlowGraph::min_cost_flow(s, t, m));
}
loj - 2508:将没有锁门的房间合并,然后直接暴力做(指每次寻找左右门的钥匙是否在当前区间里面),当然暴力是会TLE的然后记忆化搜索一下。时间复杂度的分析可以考虑如果一个钥匙藏在门后,则这个门无法被打开。将无法被打开的门看作单向边,每一条链上的复杂度是均摊 O(n),因此总复杂度 O(n)。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXN = 1000000;
int read() {
int x = 0, ch = getchar();
while( ch < '0' || ch > '9' ) ch = getchar();
while( '0' <= ch && ch <= '9' ) x = 10*x + ch - '0', ch = getchar();
return x;
}
int y[MAXN + 5], n, m, p;
int l[MAXN + 5], r[MAXN + 5], id[MAXN + 5];
void func(int x) {
while( true ) {
if( l[x] <= y[l[x] - 1] && y[l[x] - 1] <= r[x] )
func(id[l[x] - 1]), l[x] = l[id[l[x] - 1]];
else if( l[x] <= y[r[x]] && y[r[x]] <= r[x] )
func(id[r[x] + 1]), r[x] = r[id[r[x] + 1]];
else break;
}
}
int main() {
n = read(), m = read(), p = read();
for(int i=0;i<=n;i++) y[i] = -1;
y[0] = 0, y[n] = n + 1;
for(int i=1,x;i<=m;i++) x = read(), y[x] = read();
int cnt = 0, lst = 1;
for(int i=1;i<=n;i++) {
id[i] = (cnt + 1);
if( y[i] != -1 )
cnt++, l[cnt] = lst, r[cnt] = i, lst = i + 1;
}
for(int i=1;i<=cnt;i++)
func(i);
for(int i=1;i<=p;i++) {
int S = read(), T = read();
puts(l[id[S]] <= T && T <= r[id[S]] ? "YES" : "NO");
}
}
loj - 3146:考虑某个路灯亮起/熄灭,它会导致 a 在某个区间 [l1, r1]、b 在某个区间 [l2, r2] 变得可行/不可行(找区间用 set 即可)。“至今所有时刻”可以考虑提前计算代价,询问时减去多的代价。然后就是二维区间加 + 询问。空间不够可以用些 trick 离线处理树套树。
#include <set>
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
typedef pair<int, int> pii;
#define fi first
#define se second
#define mp make_pair
const int MAXN = 300000;
int read() {
int x = 0, ch = getchar();
while( ch > '9' || ch < '0' ) ch = getchar();
while( '0' <= ch && ch <= '9' ) x = 10*x + ch - '0', ch = getchar();
return x;
}
int ch[2][70*MAXN + 5], sum[70*MAXN + 5], ncnt;
int newnode() {
int p = (++ncnt);
ch[0][p] = ch[1][p] = sum[p] = 0;
return p;
}
void update(int &x, int l, int r, int p, int k) {
if( !x ) x = newnode(); sum[x] += k;
if( l == r ) return ;
int m = (l + r) >> 1;
if( p <= m ) update(ch[0][x], l, m, p, k);
else update(ch[1][x], m + 1, r, p, k);
}
int gsum(int x, int l, int r, int p) {
if( l == r ) return sum[x];
int m = (l + r) >> 1;
if( p <= m ) return gsum(ch[0][x], l, m, p);
else return gsum(ch[1][x], m + 1, r, p) + sum[ch[0][x]];
}
vector<pii>v[MAXN + 5];
int rt[MAXN + 5], n, q;
int lowbit(int x) {return x & -x;}
void add(int x, int y, int k) {
// printf("+ %d %d %d
", x, y, k);
for(int i=x;i<=n;i+=lowbit(i))
if( y <= n ) v[i].push_back(mp(y, k)); // update(rt[i], 1, n, y, k);
}
void add(int x1, int x2, int y1, int y2, int k) {
// printf("! %d %d %d %d %d
", x1, x2, y1, y2, k);
add(x2 + 1, y2 + 1, k), add(x1, y1, k);
add(x2 + 1, y1, -k), add(x1, y2 + 1, -k);
}
int fsum(int x, int y, int p) {
// printf("? %d %d
", x, y);
int ret = 0;
for(int i=x;i;i-=lowbit(i))
v[i].push_back(mp(-y, p)); //ret += gsum(rt[i], 1, n, y);
return ret;
}
set<int>st; int s[MAXN + 5];
void modify(int x, int k) {
int l, r;
set<int>::iterator it1 = st.upper_bound(x);
set<int>::iterator it2 = it1; it2--;
if( !s[x] ) it2--, st.erase(x);
else st.insert(x);
l = (*it2), r = (*it1), s[x] ^= 1;
add(l + 1, x, x + 1, r, s[x] ? +k : -k);
}
int query(int l, int r, int k, int p) {
set<int>::iterator it = st.lower_bound(l);
fsum(l, r, p); return ((*it) < r ? 0 : -k);
}
int ans[MAXN + 5]; char str[MAXN + 5], op[10];
int main() {
n = read() + 1, q = read() + 1; scanf("%s", str + 1);
for(int i=1;i<n;i++) st.insert(i); st.insert(0), st.insert(n);
for(int i=1;i<n;i++) if( str[i] - '0' ) modify(i, q);
int qcnt = 0;
for(int i=2,l,r;i<=q;i++) {
scanf("%s", op);
if( op[0] == 't' ) modify(read(), q - i + 1);
else l = read(), r = read(), qcnt++, ans[qcnt] = query(l, r, q - i + 1, qcnt);
}
for(int i=1,root;i<=n;i++) {
root = ncnt = 0;
for(int j=0;j<v[i].size();j++) {
pii p = v[i][j];
if( p.fi > 0 ) update(root, 1, n, p.fi, p.se);
else ans[p.se] += gsum(root, 1, n, -p.fi);
}
}
for(int i=1;i<=qcnt;i++)
printf("%d
", ans[i]);
}
loj - 3156:转移是个 DAG,代价是个二次函数,直接上斜率优化 dp。可以先对 m 条边的出现时刻与结束时刻按时间排序做到 O(m) 的斜率优化(瓶颈在排序的 O(mlogm))。不理解我当时为什么写挂了5分,更不理解为什么NOI上会有这种题。
#include <cmath>
#include <deque>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<ll, ll> pll;
#define fi first
#define se second
#define mp make_pair
const int MAXN = 100000, MAXM = 200000;
const ll INF = ll(6E18);
const ld inf = 9E18, EPS = 1E-9;
int dcmp(ld x) {return fabs(x) < EPS ? 0 : (x > 0 ? 1 : -1);}
ld slope(pll a, pll b) {
if( a.fi == b.fi )
return a.se < b.se ? inf : -inf;
else return ((ld)(a.se - b.se)) / (a.fi - b.fi);
}
deque<pll>que[MAXN + 5];
void insert(int u, pll p) {
while( !que[u].empty() ) {
pll b = que[u].back(); que[u].pop_back();
if( que[u].empty() || dcmp(slope(que[u].back(), b) - slope(b, p)) < 0 ) {
que[u].push_back(b);
break;
}
}
que[u].push_back(p);
}
ll query(int u, ll k) {
while( !que[u].empty() ) {
pll f = que[u].front(); que[u].pop_front();
if( que[u].empty() || dcmp(slope(f, que[u].front()) - k) > 0 ) {
que[u].push_front(f);
break;
}
}
return que[u].front().se - k * que[u].front().fi;
}
struct node{
int t, u, i;
friend bool operator < (const node &a, const node &b) {
return (a.t == b.t) ? (a.i < b.i) : (a.t < b.t);
}
}a[2*MAXM + 5];
ll dp[MAXM + 5]; bool tag[MAXM + 5];
int main() {
freopen("route.in", "r", stdin);
freopen("route.out", "w", stdout);
int n, m, A, B, C;
scanf("%d%d%d%d%d", &n, &m, &A, &B, &C);
for(int i=1,x,y,p,q;i<=m;i++) {
scanf("%d%d%d%d", &x, &y, &p, &q);
a[2*i - 1] = (node){p, x, i}, a[2*i] = (node){q, y, -i};
}
sort(a + 1, a + 2*m + 1);
ll ans = INF; insert(1, mp(0, 0));
for(int i=1;i<=2*m;i++) {
if( a[i].i < 0 ) {
if( tag[-a[i].i] ) {
insert(a[i].u, mp(2*A*a[i].t, dp[-a[i].i] + 1LL*a[i].t*(A*a[i].t - B)));
if( a[i].u == n ) ans = min(ans, dp[-a[i].i] + a[i].t);
}
} else {
if( que[a[i].u].empty() ) continue;
tag[a[i].i] = true;
dp[a[i].i] = query(a[i].u, a[i].t) + 1LL*a[i].t*(A*a[i].t + B) + C;
}
}
printf("%lld
", ans);
}
codefoces - 1314C:把所有子串插入到 trie 中排序。二分答案子串 T,相当于求是否存在 ≥k 种划分,使得每个划分出来的串都 > T,可以 (O(n^2m)) dp。注意到如果 P > T,则以 P 为前缀的所有串都 > T,前缀和优化一下就成 (O(nm)) 了。总时间复杂度 (O(nmlog A))。
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 1000;
const int MAXM = MAXN*MAXN;
int ch[26][MAXM + 5], dfn[MAXM + 5], ncnt, dcnt;
void dfs(int x) {
dfn[x] = (++dcnt);
for(int i=0;i<26;i++)
if( ch[i][x] ) dfs(ch[i][x]);
}
char ans[MAXN + 5];
void print(int x, int k, int d) {
if( dfn[x] == k ) puts(ans);
for(int i=0;i<26;i++)
if( ch[i][x] ) ans[d] = i + 'a', print(ch[i][x], k, d + 1), ans[d] = 0;
}
int id[MAXN + 5][MAXN + 5], n, m; ll k;
ll add(ll x, ll y) {return (x + y > k ? k : x + y);}
ll mul(ll x, ll y) {return (x > k / y ? k : x * y);}
int a[MAXN + 5]; ll f[MAXN + 5][MAXN + 5], sum[MAXN + 5][MAXN + 5];
bool check(int x) {
for(int i=n;i>=1;i--) {
a[i] = 0;
while( i + a[i] <= n && id[i][i + a[i]] <= x )
a[i]++;
}
f[n + 1][0] = sum[n + 1][0] = 1;
for(int i=n;i>=1;i--) {
for(int j=0;j<=m;j++) f[i][j] = 0;
for(int j=1;j<=m;j++) f[i][j] = add(f[i][j], sum[i + a[i] + 1][j - 1]);
for(int j=0;j<=m;j++) sum[i][j] = add(sum[i + 1][j], f[i][j]);
}
return f[1][m] < k;
}
char s[MAXN + 5];
int main() {
scanf("%d%d%lld%s", &n, &m, &k, s + 1);
for(int i=1;i<=n;i++) {
int nw = 0;
for(int j=i;j<=n;j++) {
if( ch[s[j] - 'a'][nw] == 0 )
ch[s[j] - 'a'][nw] = (++ncnt);
id[i][j] = nw = ch[s[j] - 'a'][nw];
}
}
dfs(0);
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
id[i][j] = dfn[id[i][j]];
int l = 1, r = dcnt;
while( l < r ) {
int mid = (l + r) >> 1;
if( check(mid) ) r = mid;
else l = mid + 1;
}
print(0, r, 0);
}
codefoces - 995F:设 dp(i, j) 表示以 i 为根的子树中 i 的工资为 j 的方案数。发现转移只有两类(1)对应位置相乘(2)前缀和,那么 dp(i, j) 是一个关于 j 的 (O(size_i)) 次多项式。求出前 (O(n)) 项 dp 值然后拉格朗日插值。什么时候div1F变成2700难度了。
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 3000;
const int MOD = int(1E9) + 7;
inline int add(int x, int y) {x += y; return (x >= MOD ? x - MOD : x);}
inline int sub(int x, int y) {x -= y; return (x < 0 ? x + MOD : x);}
inline int mul(int x, int y) {return 1LL * x * y % MOD;}
int pow_mod(int b, int p) {
int ret = 1;
for(int i=p;i;i>>=1,b=mul(b,b))
if( i & 1 ) ret = mul(ret, b);
return ret;
}
int ifct[MAXN + 5];
void init() {
ifct[0] = 1; for(int i=1;i<=MAXN;i++) ifct[i] = mul(ifct[i - 1], i);
for(int i=0;i<=MAXN;i++) ifct[i] = pow_mod(ifct[i], MOD - 2);
}
int lf[MAXN + 5], rf[MAXN + 5];
int get_y(int n, int *y, int x) {
lf[0] = 1; for(int i=1;i<=n;i++) lf[i] = mul(lf[i - 1], sub(x, i));
rf[n + 1] = 1; for(int i=n;i>=1;i--) rf[i] = mul(rf[i + 1], sub(x, i));
int ans = 0;
for(int i=1;i<=n;i++) {
int del = mul(mul(lf[i - 1], rf[i + 1]), mul(ifct[i - 1], ifct[n - i]));
ans = ((n - i) & 1) ? sub(ans, mul(del, y[i])) : add(ans, mul(del, y[i]));
}
return ans;
}
int dp[MAXN + 5][MAXN + 5];
int p[MAXN + 5], n, D;
int main() {
init(), scanf("%d%d", &n, &D);
for(int i=2;i<=n;i++) scanf("%d", &p[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n+1;j++)
dp[i][j] = 1;
for(int i=n;i>=1;i--) {
for(int j=1;j<=n+1;j++) dp[i][j] = add(dp[i][j], dp[i][j - 1]);
for(int j=1;j<=n+1;j++) dp[p[i]][j] = mul(dp[p[i]][j], dp[i][j]);
}
printf("%d
", get_y(n + 1, dp[1], D));
}
uoj - 240:题目所述的矩形可以看成区间,限制点 (x, y) 即某个选择的区间完全包含区间 [min(x,y), max(x,y)]。丢弃包含的限制点,可以写出一个 O(n^2*k) 的 dp。当 k > n' 时可以直接算;当 k ≤ n' 时最优恰好选 k 个。然后就是带权二分 + 斜率优化。
#include "aliens.h"
#include <cmath>
#include <vector>
#include <algorithm>
using namespace std;
typedef vector<int> vi;
typedef long long ll;
typedef long double ld;
const int MAXN = 100000;
const ll INF = ll(1E13);
const ld EPS = 1E-9;
int dcmp(ld x) {return fabs(x) < EPS ? 0 : (x < 0 ? -1 : 1);}
struct node{
int l, r;
friend bool operator < (node a, node b) {
return (a.l == b.l) ? (a.r > b.r) : (a.l < b.l);
}
}a[MAXN + 5];
void init(int &n, const vi &r, const vi &c) {
for(int i=0;i<n;i++)
a[i + 1] = (node){min(r[i], c[i]), max(r[i], c[i])};
sort(a + 1, a + n + 1);
int rmost = -1, p = 0;
for(int i=1;i<=n;i++) {
if( rmost >= a[i].r ) continue;
rmost = max(rmost, a[i].r), swap(a[i], a[++p]);
}
a[0] = (node){-1, -1}, n = p;
}
ll pw2(ll x) {return x * x;}
ll trans(int i) {return (a[i+1].l > a[i].r) ? 0 : (pw2(a[i].r - a[i+1].l + 1));}
struct point{ll x, y;};
ld slope(point p, point q) {return ((ld) (p.y - q.y)) / (p.x - q.x);}
struct state{point p; int k;}que[MAXN + 5]; int s, t;
void add_point(state p) {
while( s < t ) {
/*if( dcmp(slope(que[t - 1].p, que[t].p) - slope(que[t].p, p.p)) > 0 ||
(dcmp(slope(que[t - 1].p, que[t].p) - slope(que[t].p, p.p)) == 0 && p.k < que[t].k) )*/
ll d = (que[t].p.y - que[t-1].p.y) * (p.p.x - que[t].p.x) - (p.p.y - que[t].p.y) * (que[t].p.x - que[t-1].p.x);
if( d > 0 || (d == 0 && p.k < que[t].k) )
t--;
else break;
}
que[++t] = p;
}
state query(ll k) {
while( s < t ) {
/*if( dcmp(slope(que[s].p, que[s + 1].p) - k) < 0 ||
(dcmp(slope(que[s].p, que[s + 1].p) - k) == 0 && que[s].k > que[s + 1].k) )*/
ll d = (que[s + 1].p.y - que[s].p.y) - k * (que[s + 1].p.x - que[s].p.x);
if( d < 0 || (d == 0 && que[s].k > que[s + 1].k) )
s++;
else break;
}
return que[s];
}
ll dp[MAXN + 5]; int cnt[MAXN + 5];
int check(int n, ll del) {
que[s = t = 1] = (state){(point){a[1].l, pw2(a[1].l)}, 0};
for(int i=1;i<=n;i++) {
state p = query(2*(a[i].r + 1));
cnt[i] = p.k + 1, dp[i] = p.p.y - 2*(a[i].r + 1)*p.p.x + pw2(a[i].r + 1) + del;
if( i != n ) add_point((state){(point){a[i + 1].l, dp[i] + pw2(a[i + 1].l) - trans(i)}, cnt[i]});
}
return cnt[n];
}
ll take_photos(int n, int m, int k, vi r, vi c) {
init(n, r, c);
/*
for(int i=1;i<=n;i++) {
for(int j=0;j<=k;j++) dp[i][j] = INF;
for(int j=1;j<=i;j++)
for(int p=1;p<=k;p++)
dp[i][p] = min(dp[i][p], dp[j-1][p-1] + pw2(a[i].r-a[j].l+1) - trans(j-1));
}
ll ans = INF;
for(int i=1;i<=k;i++)
ans = min(ans, dp[n][i]);
return ans;
*/
if( k >= n ) {
ll ans = 0;
for(int i=1;i<=n;i++)
ans = ans + pw2(a[i].r - a[i].l + 1) - trans(i - 1);
return ans;
} else {
ll le = -INF, ri = INF;
while( le < ri ) {
ll mid = (ll)floor((le + ri) / 2.0);
if( check(n, mid) <= k ) ri = mid;
else le = mid + 1;
}
check(n, le); return dp[n] - le*k;
}
}
loj - 3058:单位根反演。最后得到 (ans_t = sum_{i=0}^{k-1} a_iw_{k}^{-it})。做神奇变换(据说叫作 chirp-Z 变换)(w_k^{-it} = w_k^{{tchoose 2}+{ichoose2}-{i+tchoose 2}}),这样就可以把乘积项拆解。然后任意模数 fft。
#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long double ld;
typedef long long ll;
#define rep(i, x, n) for(int i=x;i<n;i++)
const int MAXK = 65536;
const int SQRT = 32768;
const ld PI = acos(-1);
int p;
inline int add(int x, int y) {x += y; return x >= p ? x - p : x;}
inline int sub(int x, int y) {x -= y; return x < 0 ? x + p : x;}
inline int mul(int x, int y) {return (int)(1LL * x * y % p);}
struct complex{
ld r, i;
friend complex operator + (const complex &x, const complex &y) {
return (complex){x.r + y.r, x.i + y.i};
}
friend complex operator - (const complex &x, const complex &y) {
return (complex){x.r - y.r, x.i - y.i};
}
friend complex operator * (const complex &x, const complex &y) {
return (complex){x.r*y.r - x.i*y.i, x.i*y.r + y.i*x.r};
}
friend complex operator / (const complex x, const double k) {
return (complex){x.r / k, x.i / k};
}
friend complex conj(const complex x) {
return (complex){x.r, -x.i};
}
};
void fft(complex *A, int n, int type) {
for(int i=0,j=0;i<n;i++) {
if( i < j ) swap(A[i], A[j]);
for(int k=(n>>1);(j^=k)<k;k>>=1);
}
for(int i=1,s=2,t=1;s<=n;i++,s<<=1,t<<=1) {
complex u = (complex){cos(type*2*PI/s), sin(type*2*PI/s)};
for(int j=0;j<n;j+=s) {
complex r = (complex){1, 0};
for(int k=0;k<t;k++,r=r*u) {
complex x = A[j + k], y = A[j + k + t] * r;
A[j + k] = x + y, A[j + k + t] = x - y;
}
}
}
if( type == -1 ) {
for(int i=0;i<n;i++)
A[i] = A[i] / n;
}
}
int length(int n) {
int len; for(len = 1; len < n; len <<= 1);
return len;
}
complex a1[6*MAXK + 5], b1[6*MAXK + 5];
complex ta[6*MAXK + 5], tb[6*MAXK + 5];
void poly_mul(int *A, int n, int *B, int m, int *C) {
int len = length(n + m - 1);
rep(i, 0, n) a1[i] = (complex){(ld)(A[i] / SQRT), -(ld)(A[i] % SQRT)};
rep(i, 0, m) b1[i] = (complex){(ld)(B[i] / SQRT), -(ld)(B[i] % SQRT)};
fft(a1, len, 1), fft(b1, len, 1);
rep(i, 0, len) {
complex p1 = (a1[i] + conj(a1[i == 0 ? 0 : len - i])) / 2;
complex p2 = (a1[i] - conj(a1[i == 0 ? 0 : len - i])) / 2 * (complex){0, 1};
complex q1 = (b1[i] + conj(b1[i == 0 ? 0 : len - i])) / 2;
complex q2 = (b1[i] - conj(b1[i == 0 ? 0 : len - i])) / 2 * (complex){0, 1};
ta[i] = p1*q1 + (complex){0, 1}*p2*q2, tb[i] = p1*q2 + p2*q1;
}
fft(ta, len, -1), fft(tb, len, -1);
for(int i=0;i<n+m-1;i++)
C[i] = add(mul(SQRT, add(mul(SQRT, (ll)(ta[i].r + 0.5) % p), (ll)(tb[i].r + 0.5) % p)), (ll)(ta[i].i + 0.5) % p);
}
int pow_mod(int b, int k) {
int ret = 1;
for(int i=k;i;i>>=1,b=mul(b,b))
if( i & 1 ) ret = mul(ret, b);
return ret;
}
int a[50], cnt;
int find_root() {
int lim = (int)sqrt(p - 1), t = p - 1;
rep(i, 2, lim + 1) {
if( t % i == 0 ) {
a[++cnt] = (p - 1) / i;
while( t % i == 0 )
t /= i;
}
}
if( t != 1 ) a[++cnt] = (p - 1) / t;
rep(i, 2, p) {
bool flag = true;
for(int j=1;j<=cnt;j++)
if( pow_mod(i, a[j]) == 1 ) {
flag = false;
break;
}
if( flag ) return i;
}
return -1;
}
int n;
struct matrix{
int m[3][3];
friend matrix operator * (const matrix &A, const matrix &B) {
matrix C; rep(i, 0, n) rep(j, 0, n) C.m[i][j] = 0;
rep(i, 0, n) rep(k, 0, n) rep(j, 0, n)
C.m[i][j] = add(C.m[i][j], mul(A.m[i][k], B.m[k][j]));
return C;
}
}A, M;
matrix mpow(matrix B, int L) {
matrix R; rep(i, 0, n) rep(j, 0, n) R.m[i][j] = (i == j);
for(int i=L;i;i>>=1,B=B*B)
if( i & 1 ) R = R*B;
return R;
}
int w[MAXK + 5], k;
void get_w(int g) {
for(int i=0;i<k;i++)
w[i] = pow_mod(g, (p - 1) / k * i);
}
int f[2*MAXK + 5], g[MAXK + 5], ans[3*MAXK + 5];
int main() {
int x, y, L; scanf("%d%d%d%d%d%d", &n, &k, &L, &x, &y, &p), x--, y--;
rep(i, 0, n) rep(j, 0, n) scanf("%d", &A.m[i][j]);
get_w(find_root()); int ivk = pow_mod(k, p - 2);
rep(i, 0, k) {
rep(s, 0, n) rep(t, 0, n)
M.m[s][t] = add(mul(w[i], A.m[s][t]), s == t);
g[i] = mul(mul(mpow(M, L).m[x][y], ivk), w[1LL*i*(i+1)/2%k]);
}
rep(i, 0, 2*k) f[i] = w[(k - 1LL*i*(i+1)/2%k)%k]; reverse(f, f + 2*k);
poly_mul(f, 2*k, g, k, ans), reverse(ans, ans + 2*k);
rep(i, 0, k) printf("%d
", mul(ans[i], w[1LL*i*(i+1)/2%k]));
}
loj - 3054:身体和尾巴分开统计。尾巴以每个点为原点做极角扫描线,每个点处理 3 次:加入待选点;从待选点中删除;询问。先删后询再加。身体处理出每对点的中垂线与连线,共线拿出来扫一遍统计答案。需要避免使用浮点数(如不要把极角算出来再排序,误差大)。细节较多。
#include <cmath>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define fi first
#define se second
#define pr make_pair
const int MAXN = 1000;
struct point{
ll x, y; point() {}
point(ll _x, ll _y) : x(_x), y(_y) {}
friend point operator + (const point &a, const point &b) {return point(a.x + b.x, a.y + b.y);}
friend point operator - (const point &a, const point &b) {return point(a.x - b.x, a.y - b.y);}
friend point operator * (const point &a, const ll &k) {return point(a.x * k, a.y * k);}
friend ll operator * (const point &a, const point &b) {return a.x*b.x + a.y*b.y;}
friend ll operator ^ (const point &a, const point &b) {return a.x*b.y - a.y*b.x;}
friend point normal(const point &a) {return point(a.y, -a.x);}
friend ll length(const point &a) {return a * a;}
friend point middle(const point &a, const point &b) {return point((a.x + b.x) / 2, (a.y + b.y) / 2);}
}pnt[MAXN + 5]; int n;
bool cmp(const point &a, const point &b) {
if( a.y >= 0 && b.y < 0 ) return false;
else if( a.y < 0 && b.y >= 0 ) return true;
else if( a.y == 0 && b.y == 0 ) return a.x > 0 && b.x < 0;
else return (a ^ b) > 0;
}
bool equal(const point &a, const point &b) {
return !(cmp(a, b)) && !(cmp(b, a));
}
struct node{
point p; int id, type;
friend bool operator < (const node &a, const node &b) {
return equal(a.p, b.p) ? a.type < b.type : cmp(a.p, b.p);
}
};
vector<node>v;
int ans1[MAXN + 5][MAXN + 5], t[MAXN + 5], nwans1;
void add(int x) {nwans1 += 2*t[x]; t[x]++;}
void erase(int x) {t[x]--; nwans1 -= 2*t[x];}
ll d[MAXN + 5]; int lens[MAXN + 5], dcnt;
void solve1() {
for(int i=1;i<=n;i++) {
dcnt = 0;
for(int j=1;j<=n;j++)
if( i != j ) d[++dcnt] = length(pnt[j] - pnt[i]);
sort(d + 1, d + dcnt + 1), dcnt = unique(d + 1, d + dcnt + 1) - d - 1;
for(int j=1;j<=n;j++)
if( i != j ) lens[j] = lower_bound(d + 1, d + dcnt + 1, length(pnt[j] - pnt[i])) - d;
v.clear(); nwans1 = 0;
for(int j=1;j<=dcnt;j++) t[j] = 0;
for(int j=1;j<=n;j++)
if( i != j ) {
v.push_back((node){pnt[i] - pnt[j], j, 1});
v.push_back((node){normal(pnt[j] - pnt[i]), j, 2});
v.push_back((node){pnt[j] - pnt[i], j, 3});
}
sort(v.begin(), v.end());
for(int j=1;j<=n;j++)
if( i != j && (pnt[j].y > pnt[i].y || (pnt[j].y == pnt[i].y && pnt[j].x < pnt[i].x)) )
add(lens[j]);
for(unsigned j=0;j<v.size();j++) {
if( v[j].type == 1 ) erase(lens[v[j].id]);
else if( v[j].type == 3 ) add(lens[v[j].id]);
else {
int x = min(i, v[j].id), y = max(i, v[j].id);
ans1[x][y] += nwans1;
}
}
}
}
struct line{
point a, ab; line() {}
line(point _a, point _ab) : a(_a) {
ab = (_ab.x + _ab.y < 0 || (_ab.x + _ab.y == 0 && _ab.x < 0)) ? point(-_ab.x, -_ab.y) : _ab;
}
bool on_left(point x) {return (ab ^ (x - a)) > 0;}
friend bool operator < (line a, line b) {
return equal(a.ab, b.ab) ? a.on_left(b.a) : cmp(a.ab, b.ab);
}
friend bool operator == (line a, line b) {
return (a.ab ^ b.ab) == 0 && (a.ab ^ (b.a - a.a)) == 0;
}
};
struct type{
line l; int id;
friend bool operator < (type a, type b) {
return (a.l < b.l);
}
};
vector<type>vl;
ll d2[MAXN*MAXN + 5]; int dcnt2;
int sum[MAXN*MAXN + 5], pos[MAXN*MAXN + 5];
vector<pii>v1; int ans2[MAXN + 5][MAXN + 5];
void solve2() {
for(int i=1;i<=n;i++)
pnt[i].x *= 2, pnt[i].y *= 2;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if( i != j ) {
line l = line(middle(pnt[i], pnt[j]), normal(pnt[i] - pnt[j]));
vl.push_back((type){l, -1});
l = line(pnt[i], pnt[i] - pnt[j]);
vl.push_back((type){l, i});
}
sort(vl.begin(), vl.end());
for(unsigned i=0,j;i<vl.size();i=j+1) {
for(j = i; j < vl.size() - 1 && vl[i].l == vl[j + 1].l; j++);
if( vl[i].l.ab.x == 0 ) {
dcnt2 = 0;
for(unsigned p=i;p<=j;p++)
d2[++dcnt2] = vl[p].l.a.y;
sort(d2 + 1, d2 + dcnt2 + 1), dcnt2 = unique(d2 + 1, d2 + dcnt2 + 1) - d2 - 1;
for(int p=1;p<=dcnt2;p++)
sum[p] = 0, pos[p] = -1;
for(unsigned p=i;p<=j;p++) {
int x = lower_bound(d2 + 1, d2 + dcnt2 + 1, vl[p].l.a.y) - d2;
if( vl[p].id < 0 ) sum[x]++;
else pos[x] = vl[p].id;
}
} else {
dcnt2 = 0;
for(unsigned p=i;p<=j;p++)
d2[++dcnt2] = vl[p].l.a.x;
sort(d2 + 1, d2 + dcnt2 + 1), dcnt2 = unique(d2 + 1, d2 + dcnt2 + 1) - d2 - 1;
for(int p=1;p<=dcnt2;p++)
sum[p] = 0, pos[p] = -1;
for(unsigned p=i;p<=j;p++) {
int x = lower_bound(d2 + 1, d2 + dcnt2 + 1, vl[p].l.a.x) - d2;
if( vl[p].id < 0 ) sum[x]++;
else pos[x] = vl[p].id;
}
}
v1.clear(); int tmp = 0;
for(int p=1;p<=dcnt2;p++) {
if( pos[p] != -1 ) {
for(unsigned q=0;q<v1.size();q++) {
int x = min(v1[q].fi, pos[p]), y = max(v1[q].fi, pos[p]);
ans2[x][y] += tmp - v1[q].se;
}
}
tmp += sum[p];
if( pos[p] != -1 )
v1.push_back(pr(pos[p], tmp));
}
}
}
int main() {
scanf("%d", &n);
for(int i=1,x,y;i<=n;i++)
scanf("%d%d", &x, &y), pnt[i] = point(x, y);
solve1(), solve2();
ll ans = 0;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++) {
ans += 1LL*ans1[i][j]*ans2[i][j];
// printf("%d %d : %d %d
", i, j, ans1[i][j], ans2[i][j]);
}
printf("%lld
", ans);
}
loj - 2540:生成一个随机排列的过程等价于生成一个无限长的可重复序列,取每个元素的第一次出现位置。注意到这道题中重复加点不会使最大独立集变大,因此可以直接等价转化成生成一个无限长的可重复序列。然后直接 dp 当前最大独立集是什么即可。
#include <cstdio>
#include <algorithm>
using namespace std;
const int MOD = 998244353;
inline int add(int x, int y) {x += y; return x >= MOD ? x - MOD : x;}
inline int sub(int x, int y) {x -= y; return x < 0 ? x + MOD : x;}
inline int mul(int x, int y) {return (int)(1LL * x * y % MOD);}
int pow_mod(int b, int p) {
int ret = 1;
for(int i=p;i;i>>=1,b=mul(b,b))
if( i & 1 ) ret = mul(ret, b);
return ret;
}
int G[20], inv[25], n, m, t;
bool tg[1<<20]; int bts[1<<20], dp[1<<20];
int main() {
scanf("%d%d", &n, &m), t = (1 << n);
for(int i=1,u,v;i<=m;i++) {
scanf("%d%d", &u, &v), u--, v--;
G[u] |= (1 << v), G[v] |= (1 << u);
}
int mx = 0; tg[0] = true;
for(int s=1;s<t;s++) {
for(int i=0;i<n;i++)
if( (s >> i) & 1 ) {
tg[s] = tg[s^(1<<i)] && !(G[i] & s);
break;
}
bts[s] = bts[s >> 1] + (s & 1);
if( tg[s] ) mx = max(mx, bts[s]);
}
for(int i=1;i<=n;i++) inv[i] = pow_mod(i, MOD - 2);
for(int s=t-1;s>=0;s--) {
if( !tg[s] ) continue;
int sum = 0, cnt = 0;
for(int i=0;i<n;i++)
if( !((s >> i) & 1) && tg[s|(1<<i)] )
sum = add(sum, dp[s|(1<<i)]), cnt++;
if( cnt == 0 ) dp[s] = (bts[s] == mx);
else dp[s] = mul(sum, inv[cnt]);
}
printf("%d
", dp[0]);
}
loj - 2529:二分每个点 x 所能影响到的左边界(右边界同理),找到能在 x 到达之前到达二分值的点 y 距离二分值的最短距离,(O(KlogK)) 预处理一下 st 表 + 二分查找即可。在同一时间以同一距离到达同一个点的算右边的(不然会算重)。时间复杂度 (O(nlog^2n))((sum K) 与 (n) 同阶)。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef pair<int, ll> pil;
#define fi first
#define se second
#define pr make_pair
const int MAXN = 200000;
const ll INF = ll(1E18);
pil a[MAXN + 5]; ll sum[MAXN + 5]; int n, m, K;
ll st[2][20][MAXN + 5]; int lg[MAXN + 5];
void init_st() {
for(int i=2;i<=K;i++) lg[i] = lg[i >> 1] + 1;
for(int i=1;i<=K;i++) {
st[0][0][i] = a[i].se + sum[a[i].fi];
st[1][0][i] = a[i].se - sum[a[i].fi];
}
for(int o=0;o<=1;o++) {
for(int j=1;j<=lg[K];j++) {
int t = (1 << (j - 1));
for(int i=1;i+t<=K;i++)
st[o][j][i] = min(st[o][j - 1][i], st[o][j - 1][i + t]);
}
}
}
ll rmq(int o, int l, int r) {
if( l > r ) return INF;
int k = lg[r - l + 1], p = (1 << k);
return min(st[o][k][l], st[o][k][r - p + 1]);
}
int lf[MAXN + 5];
void getl(int x) {
int le = 1, ri = a[x].fi;
while( le < ri ) {
int mid = (le + ri) >> 1;
int ql = upper_bound(a + 1, a + K + 1, pr(2*mid - a[x].fi, INF)) - a;
int qm = lower_bound(a + 1, a + K + 1, pr(mid, -1LL)) - a - 1;
int qr = lower_bound(a + 1, a + K + 1, pr(a[x].fi, -1LL)) - a - 1;
bool flag = false;
if( sum[mid] + rmq(1, ql, qm) <= st[0][0][x] - sum[mid] )
flag = true;
else if( rmq(0, qm + 1, qr) - sum[mid] <= st[0][0][x] - sum[mid] )
flag = true;
else if( 1 <= ql - 1 && a[ql - 1].fi + a[x].fi == 2*mid &&
sum[mid] + st[1][0][ql - 1] < st[0][0][x] - sum[mid] )
flag = true;
/*
for(int p=1;p<=K;p++) {
if( l < a[p].fi && a[p].fi < r ) {
if( dist(p, mid) <= dist(x, mid) ) {
flag = true;
break;
}
} else if( a[p].fi == l ) {
if( dist(p, mid) < dist(x, mid) ) {
flag = true;
break;
}
}
}
*/
if( flag ) le = mid + 1;
else ri = mid;
}
lf[x] = ri;
}
int rf[MAXN + 5];
void getr(int x) {
int le = a[x].fi, ri = n;
while( le < ri ) {
int mid = (le + ri + 1) >> 1;
int ql = upper_bound(a + 1, a + K + 1, pr(a[x].fi, INF)) - a;
int qm = lower_bound(a + 1, a + K + 1, pr(mid, -1LL)) - a - 1;
int qr = upper_bound(a + 1, a + K + 1, pr(2*mid - a[x].fi, INF)) - a - 1;
bool flag = false;
if( sum[mid] + rmq(1, ql, qm) <= st[1][0][x] + sum[mid] )
flag = true;
else if( rmq(0, qm + 1, qr) - sum[mid] <= st[1][0][x] + sum[mid] )
flag = true;
/*
for(int p=1;p<=K;p++) {
if( l < a[p].fi && a[p].fi <= r ) {
if( dist(p, mid) <= dist(x, mid) ) {
flag = true;
break;
}
}
}
*/
if( flag ) ri = mid - 1;
else le = mid;
}
rf[x] = le;
}
int main() {
scanf("%d%d", &n, &m);
for(int i=2;i<=n;i++)
scanf("%lld", &sum[i]), sum[i] += sum[i - 1];
for(int i=1;i<=m;i++) {
scanf("%d", &K);
for(int j=1;j<=K;j++)
scanf("%d%lld", &a[j].fi, &a[j].se);
sort(a + 1, a + K + 1), init_st();
for(int j=1;j<=K;j++)
getl(j), getr(j);
ll ans = 0;
for(int j=1;j<=K;j++)
ans += (rf[j] - lf[j] + 1);
printf("%lld
", ans);
}
}
loj - 6517:一看就是莫队但是不知道怎么莫队。先建 trie。考虑把字符串拼在一起形成长度为 (O(sum |S|)) 的序列,之后在新序列上用回滚莫队(从大往小滚) + 链表就可以 (O(nsqrt{n})) 了。
#include <cstdio>
#include <vector>
#include <cassert>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pll;
#define fi first
#define se second
#define pr make_pair
#define rep(i, x, n) for(int i=x;i<=n;i++)
#define per(i, x, n) for(int i=x;i>=n;i--)
#define pb push_back
const int MAXN = 100000;
const int MAXS = 300000;
const int BLOCK = 950;
ll gcd(ll x, ll y) {return (y == 0 ? x : gcd(y, x % y));}
void print(pll p) {
ll d = gcd(p.fi, p.se); p.fi /= d, p.se /= d;
printf("%lld/%lld
", p.fi, p.se);
}
struct query{
int l, r, id;
friend bool operator < (const query &a, const query &b) {
return a.r > b.r;
}
}; vector<query>qry[BLOCK + 5];
struct modify{ll *p, k;}stk[10*MAXS + 5]; int tp;
void restore(int tim) {while( tp > tim ) (*stk[tp].p) = stk[tp].k, tp--;}
ll tot[MAXS + 5], lst[MAXS + 5], nxt[MAXS + 5], nwans;
void update(int x) {
stk[++tp] = (modify){&nwans, nwans}, nwans -= (nxt[x] - x) * (x - lst[x]);
stk[++tp] = (modify){nxt + lst[x], nxt[lst[x]]}, nxt[lst[x]] = nxt[x];
stk[++tp] = (modify){lst + nxt[x], lst[nxt[x]]}, lst[nxt[x]] = lst[x];
}
ll cnt[MAXS + 5], f[MAXS + 5], vs[MAXS + 5], lb[MAXS + 5];
ll vis[MAXS + 5]; int id[MAXS + 5], g[MAXS + 5], dep[MAXS + 5];
void remove(int x) {
stk[++tp] = (modify){f + id[x], f[id[x]]}, f[id[x]] -= vs[x];
stk[++tp] = (modify){cnt + id[x], cnt[id[x]]}, cnt[id[x]]--;
int y = dep[id[x]];
if( !vis[id[x]] && (cnt[id[x]] == 0 || f[id[x]] < lb[y]) ) {
stk[++tp] = (modify){tot + g[y], tot[g[y]]}, tot[g[y]]--;
stk[++tp] = (modify){vis + id[x], vis[id[x]]}, vis[id[x]] = 1;
if( tot[g[y]] == 0 ) update(g[y]);
}
}
ll A, B, C; pll ans[MAXN + 5];
int len[MAXN + 5], N, M, L, ncnt;
void solve() {
rep(i, 0, len[N] - 1) f[id[i]] += vs[i], cnt[id[i]]++;
rep(i, 1, ncnt) {
if( f[i] >= lb[dep[i]] )
tot[g[dep[i]]]++;
else vis[i] = true;
}
int lt = 0, rt = L + 1;
rep(i, 1, L)
if( tot[i] ) {
nwans += 1LL*(i - lt)*(L - i + 1);
lst[i] = lt, lt = i;
}
per(i, L, 1) if( tot[i] ) nxt[i] = rt, rt = i;
lst[L + 1] = lt, nxt[0] = rt;
ll sum = 1LL * L * (L + 1) / 2;
rep(i, 0, BLOCK) {
sort(qry[i].begin(), qry[i].end());
int bl = i*BLOCK, tim1 = tp, nwr = len[N] - 1;
for(unsigned j=0;j<qry[i].size();j++) {
query q = qry[i][j];
while( nwr > q.r ) remove(nwr--);
int tim = tp;
for(int p=bl;p<q.l;p++) remove(p);
ans[q.id] = pr(nwans, sum), restore(tim);
}
int br = min((i + 1)*BLOCK - 1, len[N] - 1);
restore(tim1); rep(j, bl, br) remove(j);
}
}
char str[MAXS + 5]; ll v[MAXN + 5]; int ch[26][MAXS + 5];
int main() {
scanf("%d%lld%lld%lld", &N, &A, &B, &C);
rep(i, 1, N) scanf("%lld", &v[i]);
rep(i, 1, N) {
scanf("%s", str + len[i - 1]);
int nwl = strlen(str + len[i - 1]);
L = max(L, nwl), len[i] = len[i - 1] + nwl;
int nw = 0;
rep(j, len[i - 1], len[i] - 1) {
if( ch[str[j] - 'a'][nw] == 0 )
dep[ch[str[j] - 'a'][nw] = (++ncnt)] = dep[nw] + 1;
id[j] = nw = ch[str[j] - 'a'][nw], vs[j] = v[i];
}
}
rep(i, 1, L) {
scanf("%d", &g[i]);
lb[i] = (C < A*i ? 0 : (C - A*i + B - 1) / B);
}
scanf("%d", &M);
rep(i, 1, M) {
int l, r; scanf("%d%d", &l, &r), l = len[l - 1], r = len[r] - 1;
qry[l / BLOCK].pb((query){l, r, i});
}
solve();
rep(i, 1, M) print(ans[i]);
}
loj - 6518:论文题。详见《浅谈保序回归问题 高睿泉》。其中证明了全局最优解对应了将值域区间缩到 [a, a + 1](即 ≤a 的变成 a;>a 的变成 a + 1)的最优解。于是二分 + 最大权闭合子图。
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 5000;
const int MAXM = 15000;
const int INF = (1 << 30);
namespace FlowGraph{
const int MAXV = 50*MAXN;
const int MAXE = 100*MAXM;
struct edge{
int to, cap, flow;
edge *nxt, *rev;
}edges[MAXE + 5], *adj[MAXV + 5], *cur[MAXV + 5], *ecnt;
int n, s, t;
void clear(int _n) {
n = _n, ecnt = edges;
for(int i=0;i<=n;i++)
adj[i] = NULL;
}
void addedge(int u, int v, int c) {
edge *p = (++ecnt), *q = (++ecnt);
(*p) = (edge){v, c, 0, adj[u], q}, adj[u] = p;
(*q) = (edge){u, 0, 0, adj[v], p}, adj[v] = q;
// printf("! %d %d %d
", u, v, c);
}
int dis[MAXV + 5], que[MAXV + 5], hd, tl;
bool relabel() {
for(int i=0;i<=n;i++)
dis[i] = n + 5, cur[i] = adj[i];
dis[que[hd = tl = 1] = t] = 0;
while( hd <= tl ) {
int x = que[hd++];
for(edge *p=adj[x];p;p=p->nxt) {
if( dis[p->to] > dis[x] + 1 && p->rev->cap > p->rev->flow )
dis[p->to] = dis[x] + 1, que[++tl] = p->to;
}
}
return !(dis[s] == n + 5);
}
int aug(int x, int tot) {
if( x == t ) return tot;
int sum = 0;
for(edge *&p=cur[x];p;p=p->nxt) {
if( p->cap > p->flow && dis[p->to] + 1 == dis[x] ) {
int del = aug(p->to, min(tot - sum, p->cap - p->flow));
p->flow += del, p->rev->flow -= del, sum += del;
if( sum == tot ) break;
}
}
return sum;
}
int max_flow(int _s, int _t) {
int flow = 0; s = _s, t = _t;
while( relabel() )
flow += aug(s, INF);
return flow;
}
}
int abs(int x) {return x >= 0 ? x : -x;}
int a[MAXN + 5], N, M, ans;
bool tag[FlowGraph::MAXV + 5]; int b[MAXN + 5], d[MAXN + 5], dcnt;
int le[2][MAXN + 5], ri[2][MAXN + 5];
int tmp[MAXN + 5], id[2][4*MAXN + 5];
void build_segtree(int x, int l, int r, int &cnt) {
if( l == r ) {
id[0][x] = id[1][x] = tmp[l];
return ;
}
else {
id[0][x] = (++cnt), id[1][x] = (++cnt);
int m = (l + r) >> 1;
build_segtree(x << 1, l, m, cnt);
build_segtree(x << 1 | 1, m + 1, r, cnt);
}
}
void link_segtree(int f, int x, int l, int r) {
if( f ) {
FlowGraph::addedge(id[1][f], id[1][x], INF);
FlowGraph::addedge(id[0][x], id[0][f], INF);
}
if( l == r ) return ;
int m = (l + r) >> 1;
link_segtree(x, x << 1, l, m);
link_segtree(x, x << 1 | 1, m + 1, r);
}
void add_edge(int x, int l, int r, int ql, int qr, int y, int o) {
if( ql > r || qr < l ) return ;
if( ql <= l && r <= qr ) {
if( o == 0 ) FlowGraph::addedge(id[o][x], y, INF);
else FlowGraph::addedge(y, id[o][x], INF);
return ;
}
int m = (l + r) >> 1;
add_edge(x << 1, l, m, ql, qr, y, o);
add_edge(x << 1 | 1, m + 1, r, ql, qr, y, o);
}
void dfs(int x) {
if( tag[x] ) return ;
tag[x] = true;
for(FlowGraph::edge *p=FlowGraph::adj[x];p;p=p->nxt)
if( p->cap > p->flow ) dfs(p->to);
}
void clear_tag(int x) {
if( tag[x] ) tag[x] = false;
else return ;
for(FlowGraph::edge *p=FlowGraph::adj[x];p;p=p->nxt)
if( p->cap > p->flow ) clear_tag(p->to);
}
void divide(int l, int r, int L, int R) {
if( l > r ) return ;
if( L == R ) {
for(int i=l;i<=r;i++)
ans += abs(d[L] - a[b[i]]);
return ;
}
int mid = (L + R) >> 1, s = 0, t = 1, cnt = r - l + 2;
for(int i=l;i<=r;i++) tmp[i - l + 1] = i - l + 2;
build_segtree(1, 1, r - l + 1, cnt), FlowGraph::clear(cnt);
link_segtree(0, 1, 1, r - l + 1);
sort(b + l, b + r + 1);
for(int i=l;i<=r;i++) {
if( a[b[i]] <= d[mid] )
FlowGraph::addedge(s, i - l + 2, 1);
else FlowGraph::addedge(i - l + 2, t, 1);
}
for(int o=0;o<=1;o++) {
for(int i=l;i<=r;i++) {
int lft = lower_bound(b + l, b + r + 1, le[o][b[i]]) - b - l + 1;
int rgt = upper_bound(b + l, b + r + 1, ri[o][b[i]]) - b - l;
if( lft == rgt ) continue;
add_edge(1, 1, r - l + 1, lft, rgt, i - l + 2, o);
}
}
FlowGraph::max_flow(s, t), dfs(s);
int p = l;
for(int i=l;i<=r;i++)
if( tag[i - l + 2] ) swap(b[i], b[p++]);
clear_tag(s), divide(l, p - 1, L, mid), divide(p, r, mid + 1, R);
}
int main() {
scanf("%d%d", &N, &M);
for(int i=1;i<=N;i++) {
scanf("%d", &a[i]), d[i] = a[i];
le[0][i] = le[1][i] = ri[0][i] = ri[1][i] = i;
}
sort(d + 1, d + N + 1), dcnt = unique(d + 1, d + N + 1) - d - 1;
for(int i=1,t,l,r,k;i<=M;i++) {
scanf("%d%d%d%d", &t, &l, &r, &k);
le[t][k] = min(le[t][k], l), ri[t][k] = max(ri[t][k], r);
}
for(int i=1;i<=N;i++) b[i] = i;
divide(1, N, 1, dcnt), printf("%d
", ans);
}
loj - 2570:考虑被定位的区间,可以看成某个结点往上走的所有左儿子/右儿子(如果左儿子/右儿子不是自己的话)。然后树上倍增 + 分 类 大 讨 论。写的常数好像比较大。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef pair<ll, int> pli;
#define fi first
#define se second
#define pr make_pair
const int MAXN = 400000;
int ch[2][MAXN + 5], le[MAXN + 5], ri[MAXN + 5], id[MAXN + 5], cnt;
int build(int l, int r) {
int x = (++cnt); le[x] = l, ri[x] = r;
if( l != r ) {
int m; scanf("%d", &m);
ch[0][x] = build(l, m);
ch[1][x] = build(m + 1, r);
} else id[l] = x;
return x;
}
int fa[20][MAXN + 5], dep[MAXN + 5];
pli f[2][20][MAXN + 5], g[2][20][MAXN + 5]; int mst[2][MAXN + 5];
pli merge(const pli &a, const pli &b) {return pr(a.fi + b.fi, a.se + b.se);}
void dfs(int x, int pre, int o) {
if( !x ) return ;
if( o == -1 )
mst[0][x] = mst[1][x] = x;
else {
fa[0][x] = pre, dep[x] = dep[pre] + 1, mst[o][x] = mst[o][pre], mst[!o][x] = x;
f[o][0][x] = pr(dep[pre] + 1, 1), f[!o][0][x] = pr(0, 0);
g[o][0][x] = pr(dep[pre] + 1 - 2*dep[pre], 1), g[!o][0][x] = pr(0, 0);
for(int i=1;i<20;i++) {
for(int p=0;p<=1;p++) {
f[p][i][x] = merge(f[p][i-1][x], f[p][i-1][fa[i-1][x]]);
g[p][i][x] = merge(g[p][i-1][x], g[p][i-1][fa[i-1][x]]);
}
fa[i][x] = fa[i-1][fa[i-1][x]];
}
}
dfs(ch[0][x], x, 0), dfs(ch[1][x], x, 1);
}
int lca(int u, int v) {
if( dep[u] < dep[v] ) swap(u, v);
for(int i=19;i>=0;i--)
if( dep[fa[i][u]] >= dep[v] )
u = fa[i][u];
if( u == v ) return u;
for(int i=19;i>=0;i--)
if( fa[i][u] != fa[i][v] )
u = fa[i][u], v = fa[i][v];
return fa[0][u];
}
int dist(int u, int v) {return dep[u] + dep[v] - 2*dep[lca(u, v)];}
pli query_f(int x, int d, int o) {
pli ret = pr(0, 0);
for(int i=19;i>=0;i--)
if( dep[fa[i][x]] >= d )
ret = merge(ret, f[o][i][x]), x = fa[i][x];
return ret;
}
pli query_g(int x, int d, int o) {
pli ret = pr(0, 0);
for(int i=19;i>=0;i--)
if( dep[fa[i][x]] >= d )
ret = merge(ret, g[o][i][x]), x = fa[i][x];
return ret;
}
int anc(int x, int d) {
for(int i=19;i>=0;i--)
if( dep[fa[i][x]] >= d ) x = fa[i][x];
return x;
}
ll work(int x, int y, int u, int o) {
pli k = query_g(x, dep[y], o);
return k.fi + 1LL*k.se*dep[u];
}
ll get(int u, int x, int p, int o) {
int y = lca(u, p);
if( y == p )
return dist(p, u) + work(p, x, u, o);
else if( y < x ) {
pli k = query_f(p, dep[x], o);
return dist(p, u) + k.fi + 1LL*k.se*(dep[u] - 2*dep[y]);
} else {
pli k = query_f(p, dep[y] + 1, o); int np = anc(p, dep[y] + 1);
ll ans = k.fi + 1LL*k.se*(dep[u] - 2*dep[y]);
if( ch[o][y] == np ) ans += dist(u, ch[!o][y]);
return dist(p, u) + ans + work(y, x, u, o);
}
}
ll solve(int u, int l, int r) {
int p = lca(id[l], id[r]);
if( l == le[p] && ri[p] == r ) return dist(p, u);
else if( l == le[p] ) return get(u, p, mst[1][id[r]], 1);
else if( r == ri[p] ) return get(u, p, mst[0][id[l]], 0);
else return solve(u, l, ri[ch[0][p]]) + solve(u, le[ch[1][p]], r);
}
int n, m;
int main() {
scanf("%d", &n), build(1, n), dfs(1, 0, -1);
scanf("%d", &m);
for(int i=1,u,l,r;i<=m;i++) {
scanf("%d%d%d", &u, &l, &r);
printf("%lld
", solve(u, l, r));
}
}
codeforces - 235C:循环同构串,其实就是复制两遍。注意在 SAM 上打 tag 避免重复计数。
提交记录链接。
codefoces - 547E:广义后缀自动机 + 可持久化线段树合并。(我写的广义后缀自动机)有个问题:有些点的父亲连向了 len 相同的点。首先基数排序没用,还得用 dfs;其次每个串定位的结点可能要跳几个父亲才能到达真实结点。
提交记录链接。
loj - 6625:合数部分只会在最小质因子处统计,质数部分发现是个质数之和,因此直接 min_25 筛即可。
提交记录链接。
loj - 3069:记 (f'(n) = frac{f(n)}{4}),通过打表不难发现 (f'(n)) 是积性函数,且 (egin{cases}f'(p^c)=1 & (pmod 4 = 1 ||p=2)\f'(p^c)=2c+1 &(pmod 4 = 3)end{cases})。然后直接 min-25 筛,前半部分做 dp 求模 4 等于 1/3 的质数个数。证明详见2019年论文《<整点计数>命题报告以及对高斯整数的若干研究》。
提交记录链接。
codeforces - 1043F:连边 ((i, j)) 当且仅当 (j|i) 且 (exist x, gcd(x,frac{i}{j}) = 1)。莫比乌斯反演算一下 (gcd(x,frac{i}{j}) = 1) 的 (x) 个数。然后跑最短路即可。
提交记录链接。
codeforces - 645F:记 (i) 的倍数有 (b_i) 个,答案为 (sum_{i=1}phi(i) imesinom{b_i}{k})。每次只会更改 (O(sqrt{A})) 个,暴力改即可。
提交记录链接。
codeforces - 1295F:分段拉格朗日插值。具体分段方法可参考 「NOI2019」机器人 。时间复杂度 (O(n^3))。
提交记录链接。
loj - 6511:和 cf1307G 的处理方法差不多:列线性规划,对偶成费用流。最后关于 (sum_{i=1}b_{i,x}-y_xleq c_x) 的处理,首先拆点变成 (p_x-y_xleq c_x),然后建两条边 ((c_x,t_x)) 与 ((infin, 0))。求答案可以二分,也可以每增广一次更新一次,因为最优解一定在凸包上(在此感谢 vuq 中的 myh 大佬)。
提交记录链接。
loj - 6259:对网格外新建 0 号点,合法方案是一个以 0 号点为根的内向树。已存在的连通块是以 0号点/未确定点 为根的内向树,因此可将已有的连通块缩掉(如果成环则无解)。然后有向图 matrix-tree。
提交记录链接。