决定从头到尾干一波BZOJ!
可能会写没几题就停下吧,但还是想学学新姿势啦。

1001. [BeiJing2006]狼抓兔子

即求 $(1, 1)$ 到 $(n, m)$ 的最小割。跑 dinic 即可。

#include <bits/stdc++.h>
using namespace std; inline int read() {
int x = , f = ; char ch = getchar();
while (ch < '' || ch > '') { if (ch == '-') f = -; ch = getchar(); }
while (ch >= '' && ch <= '') { x = x * + ch - ; ch = getchar(); }
return x * f;
} const int N = 1e6 + ;
const int INF = 0x3f3f3f3f;
struct E { int v, ne, f; } e[N * ];
int head[N], cnt, n, m, iter[N], level[N]; inline void add(int u, int v, int f) {
e[cnt].v = v; e[cnt].f = f; e[cnt].ne = head[u]; head[u] = cnt++;
e[cnt].v = u; e[cnt].f = f; e[cnt].ne = head[v]; head[v] = cnt++;
} bool bfs(int s, int t) {
for (int i = ; i <= t; i++) level[i] = -, iter[i] = head[i];
queue<int> que;
que.push(s);
level[s] = ;
while (!que.empty()) {
int u = que.front(); que.pop();
for (int i = head[u]; ~i; i = e[i].ne) {
int v = e[i].v, f = e[i].f;
if (level[v] < && f) {
level[v] = level[u] + ;
que.push(v);
}
}
}
return level[t] != -;
} int dfs(int u, int t, int f) {
if (u == t || !f) return f;
int flow = ;
for (int i = iter[u]; ~i; i = e[i].ne) {
iter[u] = i;
int v = e[i].v;
if (level[v] == level[u] + && e[i].f) {
int w = dfs(v, t, min(f, e[i].f));
if (!w) continue;
e[i].f -= w, e[i^].f += w;
flow += w, f -= w;
if (f <= ) break;
}
}
return flow;
} int main() {
memset(head, -, sizeof(head));
n = read(), m = read();
for (int i = ; i <= n; i++) {
for (int j = ; j < m; j++) {
int f = read();
add((i - ) * m + j, (i - ) * m + j + , f);
}
}
for (int i = ; i < n; i++) {
for (int j = ; j <= m; j++) {
int f = read();
add((i - ) * m + j, i * m + j, f);
}
}
for (int i = ; i < n; i++) {
for (int j = ; j < m; j++) {
int f = read();
add((i - ) * m + j, i * m + j + , f);
}
}
int ans = ;
int s = , t = n * m;
for (; bfs(s, t); ans += dfs(s, t, INF));
printf("%d\n", ans);
return ;
}

1002. [FJOI2007]轮状病毒
生成树计数。
基尔霍夫矩阵为度数矩阵减去邻接矩阵。
无向图生成树计数为基尔霍夫矩阵的行列式
可得递推方程
$ans = 3 \times f(n - 1) - 2 \times f(n - 2) - 2$
$f(n) = 3 \times f(n - 1) - f(n - 2)$
加上高精度即可。
注意算行列式时多写几行容易看。

#include <bits/stdc++.h>
using namespace std; struct Bigi {
int a[], len;
Bigi() {
memset(a, , sizeof(a));
len = ;
}
friend Bigi operator * (int x, Bigi b) {
Bigi res;
res.len = b.len + ;
for (int i = ; i <= res.len; i++) {
res.a[i] += b.a[i] * x;
res.a[i + ] += res.a[i] / ;
res.a[i] %= ;
}
while (res.len > && res.a[res.len] == ) res.len--;
return res;
}
friend Bigi operator - (Bigi x, Bigi y) {
Bigi res;
res.len = x.len;
for (int i = ; i <= res.len; i++) {
res.a[i] = x.a[i] - y.a[i];
while (res.a[i] < ) {
res.a[i] += ;
x.a[i + ]--;
}
}
while (res.len > && res.a[res.len] == ) res.len--;
return res;
}
friend Bigi operator + (Bigi x, int y) {
Bigi res = x;
res.a[] += y;
for (int i = ; i <= res.len; i++) {
if (res.a[i] >= ) {
res.a[i] -= ;
res.a[i + ]++;
} else {
break;
}
}
while (res.a[res.len + ]) res.len++;
return res;
}
void print() {
for (int i = len; i; i--)
printf("%d", a[i]);
puts("");
}
} big[], ans; int main() {
int n;
scanf("%d", &n);
big[] = big[] + ;
big[] = big[] + ;
if (n <= ) {
big[n].print();
return ;
}
for (int i = ; i <= n; i++)
big[i] = * big[i - ] - big[i - ];
ans = * big[n - ] - * big[n - ];
ans = ans + (-);
ans.print();
return ;
}

1003. [ZJOI2006]物流运输
$cost[i][j]$ 表示第 $i$ 天到第 $j$ 天都走同一条路线时每天的最小花费,即为 $1$ 到 $m$ 的最短路。dijkstra即可。
然后 $dp[i]$ 表示到第 $i$ 天的最小花费
$dp[i] = min(dp[j] + cost[j + 1][i] * (i - j) + k)$

#include <bits/stdc++.h>
#define pii pair<int, int>
#define fi first
#define se second
using namespace std; const int INF = ;
const int N = ;
int cost[N][N], dp[N], n, m, k, e;
vector<pii> G[N];
bool ban[N][N], unable[N], done[N];
int dis[N]; inline void checkmin(int &a, int b) {
if (a > b) a = b;
} int dijkstra(int l, int r) {
for (int i = ; i <= m; i++) {
unable[i] = ;
done[i] = ;
dis[i] = INF;
for (int j = l; j <= r; j++)
unable[i] |= ban[j][i];
}
if (unable[] || unable[m]) return INF;
priority_queue<pii, vector<pii>, greater<pii> > que;
dis[] = ;
que.push(pii(, ));
while (!que.empty()) {
auto pp = que.top(); que.pop();
int u = pp.se;
if (done[u]) continue;
done[u] = ;
for (auto p: G[u]) {
int v = p.se, c = p.fi;
if (!unable[v] && dis[v] > dis[u] + c) {
dis[v] = dis[u] + c;
que.push(pii(dis[v], v));
}
}
}
return dis[m];
} int main() {
//freopen("in.txt", "r", stdin);
scanf("%d%d%d%d", &n, &m, &k, &e);
for (int u, v, c; e--; ) {
scanf("%d%d%d", &u, &v, &c);
G[u].push_back(pii(c, v));
G[v].push_back(pii(c, u));
}
int q;
scanf("%d", &q);
for (int u, a, b; q--; ) {
scanf("%d%d%d", &u, &a, &b);
for (int i = a; i <= b; i++)
ban[i][u] = ;
}
for (int i = ; i <= n; i++)
for (int j = ; j <= n; j++)
cost[i][j] = dijkstra(i, j);
for (int i = ; i <= n; i++) {
dp[i] = cost[][i] * i;
for (int j = ; j < i; j++)
checkmin(dp[i], dp[j] + cost[j + ][i] * (i - j) + k);
}
printf("%d\n", dp[n]);
return ;
}

1005. [HNOI2008]明明的烦恼
prufer序列为无根树的一种数列。长度为 $n$ - $2$
prufer转无根树
将最小编号的叶子删去,prufer序列加入其父亲。重复至树只剩下两个节点。
无根树转prufer
取出prufer首元素,与待选点集中最小未出现在prufer序列中的点连边,并将该点在待选点集中删去,直至待选点集剩下两个节点,将这两个节点连边。待选点集初始为 $1$ ~ $n$。
一个节点在prufer序列中出现次数为该节点度数减一。
判断无解的情况:出现度数为 $0$ 的点,在prufer序列中出现次数超过 $n$ - $2$。
有解情况下,设 $cnt$ 为有度数要求的节点个数,$sum = \sum_{i = 1} ^{cnt}(d_i - 1)$。
那么答案为 $C_{n-2}^{sum} \times \dfrac{sum!}{\prod_{i=1}^{cnt}(d_i-1)!} \times (n-cnt)^{n-2-sum}$
化简得到$\dfrac{(n-2)!}{(n-sum-2)! \times \prod_{i=1}^{cnt}(d_i-1)!} \times (n-cnt)^{n-2-sum}$

#include <bits/stdc++.h>

const int N = ;
const int MOD = ;
int num[N];
int prime[N], tol, d[N], c[N];
bool vis[N]; void init() {
for (int i = ; i < N; i++) {
if (!vis[i]) prime[++tol] = i;
for (int j = ; j <= tol && i * prime[j] < N; j++) {
vis[i * prime[j]] = ;
if (i % prime[j] == ) break;
}
}
} void add(int x, int o) {
for (int i = ; i <= tol; i++) {
while (x % prime[i] == )
c[i] += o, x /= prime[i];
}
} int main() {
init();
int n;
scanf("%d", &n);
bool flag = ;
int sum = , cnt = ;
for (int i = ; i <= n; i++) {
scanf("%d", d + i);
if (!d[i] || d[i] > n - ) flag = ;
if (d[i] != -) sum += d[i] - , cnt++;
}
if (n == ) {
if (!d[]) puts("");
else puts("");
return ;
}
if (sum > n - || flag) {
puts("");
return ;
}
for (int i = n - - sum + ; i <= n - ; i++)
add(i, );
for (int i = ; i <= n; i++) {
if (d[i] > -) {
for (int j = ; j < d[i]; j++)
add(j, -);
}
}
int len = ;
num[++len] = ;
for (int i = ; i <= n - - sum; i++) {
for (int j = ; j <= len; j++) num[j] *= n - cnt;
for (int j = ; j <= len; j++) {
if (num[j] >= MOD) {
num[j + ] += num[j] / MOD;
num[j] %= MOD;
}
}
while (num[len + ]) {
num[len + ] += num[len] / MOD;
num[len] %= MOD;
len++;
}
}
for (int i = ; i <= tol; i++) {
while (c[i]) {
for (int j = ; j <= len; j++) num[j] *= prime[i];
for (int j = ; j <= len; j++) {
if (num[j] >= MOD) {
num[j + ] += num[j] / MOD;
num[j] %= MOD;
}
}
while (num[len + ]) {
num[len + ] += num[len] / MOD;
num[len] %= MOD;
len++;
}
c[i]--;
}
}
printf("%d", num[len]);
for (int i = len - ; i; i--) printf("%04d", num[i]);
puts("");
return ;
}

1007. [HNOI2008]水平可见直线
可见的直线为一下凸壳。
先按斜率和截距从小到大排序,再用单调栈判断交点的相对位置即可。

#include <bits/stdc++.h>

const int N = 5e4 + ;
const double eps = 1e-; inline int dcmp(double x) {
if (fabs(x) < eps) return ;
return x < ? - : ;
} struct P {
double x, y;
int id;
inline bool operator < (const P &rhs) const {
if (dcmp(x - rhs.x) == ) return y < rhs.y;
return x < rhs.x;
}
} p[N]; int st[N], top; inline double crossx(const P &a, const P &b) {
return (a.y - b.y) / (b.x - a.x);
} void ins(int id) {
const P &cur = p[id];
while (top) {
if (dcmp(p[st[top]].x - cur.x) == ) top--;
else if (top > && dcmp(crossx(p[st[top]], cur) - crossx(p[st[top]], p[st[top - ]])) <= ) top--;
else break;
}
st[++top] = id;
} int ans[N]; int main() {
int n;
scanf("%d", &n);
for (int i = ; i <= n; i++) {
scanf("%lf%lf", &p[i].x, &p[i].y);
p[i].id = i;
}
std::sort(p + , p + + n);
for (int i = ; i <= n; i++)
ins(i);
for (int i = ; i <= top; i++)
ans[p[st[i]].id] = ;
for (int i = ; i <= n; i++)
if (ans[i])
printf("%d ", i);
return ;
}

1008. [HNOI2008]越狱
总方案数 - 相邻颜色均不同的方案数。

#include <bits/stdc++.h>
#define ll long long const int MOD = ; int qp(int a, ll b) {
a %= MOD;
int ans = ;
while (b) {
if (b & ) ans = 1LL * ans * a % MOD;
a = 1LL * a * a % MOD;
b >>= ;
}
return ans;
} int main() {
int m;
ll n;
scanf("%d%lld", &m, &n);
int ans = qp(m, n);
ans = (ans - 1LL * m * qp(m - , n - ) % MOD) % MOD;
ans = (ans + MOD) % MOD;
printf("%d\n", ans);
return ;
}

1009. [HNOI2008]GT考试

显然有一个DP方程 $dp[i][j]$ 表示到第 $i$ 位已经末尾匹配了 $j$ 位的方案数。

暴力的话就枚举下一位放啥,看放完之后又匹配了多少。

这里可以引入一个 $f[i][j]$ 数组表示从不吉利数字当前匹配了 $i$ 位,加上一个字符能匹配 $j$ 位的方案数。

这一部分可以用kmp得到。

然后dp的转移方程即为 $dp[i][j] = \sum dp[i - 1][k] \times f[k][j]$

答案为 $\sum_{i = 0}^{m - 1} dp[n][i]$

#include <bits/stdc++.h>

const int N = ;
int MOD, n, m;
char s[N];
int ne[N]; struct Mat {
int mat[][];
Mat() {
memset(mat, , sizeof mat);
}
Mat operator * (const Mat &rhs) const {
Mat c;
for (int i = ; i < n; i++)
for (int j = ; j < n; j++)
for (int k = ; k < n; k++)
(c.mat[i][j] += mat[i][k] * rhs.mat[k][j]) %= MOD;
return c;
}
}; Mat qp(Mat ans, Mat a, int b) {
while (b) {
if (b & ) ans = ans * a;
a = a * a;
b >>= ;
}
return ans;
} void kmp() {
int i = , j = ne[] = -;
while (i < n) {
while (j != - && s[i] != s[j]) j = ne[j];
ne[++i] = ++j;
}
} int main() {
scanf("%d%d%d", &m, &n, &MOD);
scanf("%s", s);
kmp();
Mat b;
for (int i = ; i < n; i++) {
for (int j = ; j < ; j++) {
int k = i;
while (k != - && s[k] != j + '') k = ne[k];
k++;
if (k < n) b.mat[i][k]++;
}
}
Mat a;
a.mat[][] = ;
a = qp(a, b, m);
int ans = ;
for (int i = ; i < n; i++)
ans += a.mat[][i];
ans %= MOD;
printf("%d\n", ans);
return ;
}

1010. [HNOI2008]玩具装箱toy

$dp[i]$ 表示以 $i$ 物品为结尾的最小费用

$dp[i] = min(dp[j] + (j - i + \sum c_k - l)^2)$

斜率优化一下即可。

#include <bits/stdc++.h>
#define ll long long
using namespace std; const int N = 5e4 + ;
ll sum[N], l, c[N], dp[N];
int que[N], n; template <class T>
inline T sqr (T a) {
return a * a;
} inline ll X(int i) {
return sum[i];
} inline ll Y(int i) {
return dp[i] + sqr(sum[i]) + * l * sum[i];
} inline double K(int i, int j) {
return 1.0 * (Y(i) - Y(j)) / (X(i) - X(j));
} int main() {
scanf("%d%lld", &n, &l);
for (int i = ; i <= n; i++)
scanf("%lld", &c[i]), sum[i] = sum[i - ] + c[i];
for (int i = ; i <= n; i++)
sum[i] += i;
int head = , tail = ;
l++;
for (int i = ; i <= n; i++) {
while (head < tail && K(que[head], que[head + ]) < * sum[i]) head++;
int j = que[head];
dp[i] = dp[j] + sqr(sum[i] - sum[j] - l);
while (head < tail && K(que[tail - ], que[tail]) > K(que[tail], i)) tail--;
que[++tail] = i;
}
printf("%lld\n", dp[n]);
return ;
}

1011. [HNOI2008]遥远的行星

怀疑是没有spj。反正是个瞎搞题?

1012. [JSOI2008]最大数maxnumber

开足够位置就变成单点修改区间查询。

线段树维护即可。

#include <bits/stdc++.h>
#define ll long long const int N = 2e5 + ; struct Seg {
#define lp p << 1
#define rp p << 1 | 1
ll tree[N << ];
void pushup(int p) {
tree[p] = std::max(tree[lp], tree[rp]);
}
void update(int p, int l, int r, int pos, ll v) {
if (l == r) {
tree[p] = v;
return;
}
int mid = l + r >> ;
if (pos <= mid) update(lp, l, mid, pos, v);
else update(rp, mid + , r, pos, v);
pushup(p);
}
ll query(int p, int l, int r, int x, int y) {
if (x <= l && y >= r) return tree[p];
int mid = l + r >> ;
ll ans = ;
if (x <= mid) ans = std::max(ans, query(lp, l, mid, x, y));
if (y > mid) ans = std::max(ans, query(rp, mid + , r, x, y));
return ans;
}
} seg; int main() {
int n;
ll D;
scanf("%d%lld", &n, &D);
ll ans = ;
int cnt = ;
for (int i = ; i <= n; i++) {
char s[];
ll num;
scanf("%s%lld", s, &num);
if (s[] == 'A') {
num += ans;
num %= D;
seg.update(, , n, ++cnt, num);
} else {
printf("%lld\n", ans = seg.query(, , n, cnt - num + , cnt));
}
}
return ;
}

1013. [JSOI2008]球形空间产生器sphere

圆心坐标为 $(a_1, a_2, a_3, \cdots, a_n)$

那么可以列出 $n$ 条等式,两边的平方项都消掉了。那么高斯消元求解即可。

#include <bits/stdc++.h>

const int N = ;
const double eps = 1e-;
int n;
double a[N][N];
struct Node {
double d[N];
} p[N]; void gauss() {
for (int i = ; i <= n; i++) {
int r = i;
for (int j = i + ; j <= n; j++) {
if (std::fabs(a[r][i]) < std::fabs(a[j][i]))
r = j;
}
if (r != i) std::swap(a[i], a[r]);
for (int j = ; j <= n; j++) {
if (j != i) {
double t = a[j][i] / a[i][i];
for (int k = i; k <= n + ; k++)
a[j][k] -= a[i][k] * t;
}
}
}
for (int i = ; i <= n; i++)
a[i][n + ] /= a[i][i];
} int main() {
scanf("%d", &n);
for (int i = ; i <= n + ; i++)
for (int j = ; j <= n; j++)
scanf("%lf", &p[i].d[j]);
for (int i = ; i <= n; i++) {
for (int j = ; j <= n; j++) {
a[i][j] = * p[i].d[j] - * p[i + ].d[j];
a[i][n + ] += p[i].d[j] * p[i].d[j] - p[i + ].d[j] * p[i + ].d[j];
}
}
gauss();
for (int i = ; i <= n; i++)
printf("%.3f%c", a[i][n + ], " \n"[i == n]);
return ;
}

1014. [JSOI2008]火星人prefix

如果没有插入操作,仅仅只有修改操作,那么可以用线段树维护区间哈希值,对于一个查询,二分长度,再判是否相等即可。

现在多了插入操作,那么就用 splay 来维护哈希值,与线段树不同的只是多了非叶子节点也代表一个字符。

#include <bits/stdc++.h>
#define ull unsigned long long const int N = 2e5 + ;
ull base[N];
const ull BASE = ;
int tol, root, m;
char s[N]; struct Splay {
int ch[N][], fa[N], sz[N];
ull val[N], ha[N];
inline bool chk(int x) {
return ch[fa[x]][] == x;
}
inline void pushup(int x) {
sz[x] = sz[ch[x][]] + sz[ch[x][]] + ;
ha[x] = ha[ch[x][]] + val[x] * base[sz[ch[x][]]] + ha[ch[x][]] * base[(sz[ch[x][]] + )];
}
void rotate(int x) {
int y = fa[x], z = fa[y], k = chk(x), w = ch[x][k ^ ];
ch[y][k] = w; fa[w] = y;
ch[z][chk(y)] = x; fa[x] = z;
ch[x][k ^ ] = y; fa[y] = x;
pushup(y); pushup(x);
}
void splay(int x, int goal = ) {
while (fa[x] != goal) {
int y = fa[x], z = fa[y];
if (z != goal)
rotate((chk(x) == chk(y)) ? y : x);
rotate(x);
}
pushup(x);
if (!goal) root = x;
pushup(x);
}
int kth(int k) {
int cur = root;
while () {
if (ch[cur][] && k <= sz[ch[cur][]])
cur = ch[cur][];
else if (k > + sz[ch[cur][]])
k -= + sz[ch[cur][]], cur = ch[cur][];
else
break;
}
return cur;
}
ull gethash(int l, int len) {
int x = kth(l), y = kth(l + len + );
splay(x), splay(y, x);
return ha[ch[y][]];
}
void insert(int x, int v) {
int l = kth(x + ), r = kth(x + );
splay(l); splay(r, l);
val[++tol] = v; fa[tol] = r; ch[r][] = tol;
splay(tol);
}
void update(int x, int v) {
int l = kth(x), r = kth(x + );
splay(l); splay(r, l);
val[ch[r][]] = v;
splay(ch[r][]);
}
int query(int x, int y) {
int l = , r = tol - std::max(x, y) - ;
int ans = ;
while (l <= r) {
int mid = l + r >> ;
if (gethash(x, mid) == gethash(y, mid)) ans = mid, l = mid + ;
else r = mid - ;
}
return ans;
}
void init() {
ch[][] = ;
root = , tol = ;
fa[] = ;
pushup();
pushup();
}
} splay; int main() {
for (int i = base[] = ; i < N; i++)
base[i] = base[i - ] * BASE;
scanf("%s%d", s + , &m);
int n = strlen(s + );
splay.init();
for (int i = ; i <= n; i++)
splay.insert(i - , s[i]);
for (int x, y; m--; ) {
char s[];
scanf("%s", s);
if (s[] == 'Q') {
scanf("%d%d", &x, &y);
printf("%d\n", splay.query(x, y));
} else if (s[] == 'R') {
char ss[];
scanf("%d%s", &x, s);
splay.update(x, s[]);
} else {
char ss[];
scanf("%d%s", &x, s);
splay.insert(x, s[]);
}
}
return ;
}

1015. [JSOI2008]星球大战starwar

并查集不好直接删除边,那么考虑时光倒流,反向加边即可。

#include <bits/stdc++.h>

const int N = 4e5 + ;

struct Edge {
int x, y;
} e[N];
std::vector<int> vec[N]; int n, m, fa[N], a[N], res, ans[N];
bool vis[N]; int getfa(int x) {
return x == fa[x] ? x : fa[x] = getfa(fa[x]);
} void merge(int x, int y) {
x = getfa(x), y = getfa(y);
if (x != y) {
fa[x] = y;
res--;
}
} int main() {
scanf("%d%d", &n, &m);
for (int i = ; i < n; i++)
fa[i] = i;
for (int i = ; i < m; i++)
scanf("%d%d", &e[i].x, &e[i].y), vec[e[i].x].push_back(e[i].y), vec[e[i].y].push_back(e[i].x);
int k;
scanf("%d", &k);
for (int i = ; i <= k; i++)
scanf("%d", a + i), vis[a[i]] = ;
res = n - k;
for (int i = ; i < m; i++)
if (!vis[e[i].x] && !vis[e[i].y])
merge(e[i].x, e[i].y);
for (int i = k; i; i--) {
ans[i] = res;
int u = a[i];
vis[u] = ;
res++;
for (int v: vec[u])
if (!vis[v])
merge(u, v);
}
printf("%d\n", res);
for (int i = ; i <= k; i++)
printf("%d\n", ans[i]);
return ;
}

1016. [JSOI2008]最小生成树计数

最小生成树会出现多个是因为权值相同的边可替换。把处理同权值的边称为一个阶段,若处理一个阶段中的边的顺序会影响该阶段后连通块的连通性,那么这就与可替换相矛盾。

所以处理完一个阶段后这棵树的连通性应该是固定的。(感觉解释不太清楚,但是应该可以意会吧)。

用并查集 $fa$ 和 邻接矩阵 $G$ 表示每一时刻树的连通情况以及邻接情况。

用并查集 $pre$ 表示上一阶段树的连通情况。

处理一个阶段后,对每个连通块用矩阵树定理求一遍生成树个数,根据乘法原理相乘。

然后再把连通块缩成点再处理下一阶段。

#include <bits/stdc++.h>
using namespace std; const int N = ;
const int MOD = ; struct E {
int u, v, cost;
inline bool operator < (const E &rhs) const {
return cost < rhs.cost;
}
void read() {
scanf("%d%d%d", &u, &v, &cost);
}
} edge[N * ]; int getfa(int x, int *fa) {
return x == fa[x] ? x : fa[x] = getfa(fa[x], fa);
} int Gauss(int a[N][N], int n) {
int ans = ;
for (int i = ; i <= n; i++) {
for (int k = i + ; k <= n; ++k) {
while (a[k][i]) {
int d = a[i][i] / a[k][i];
for (int j = i; j <= n; j++)
a[i][j] = (a[i][j] - d * a[k][j] + MOD) % MOD;
swap(a[i], a[k]);
ans = -ans;
}
}
ans = 1LL * ans * a[i][i] % MOD;
ans = (ans + MOD) % MOD;
}
return ans;
} int A[N][N], G[N][N], fa[N], pre[N];
vector<int> vec[N];
bool vis[N]; int main() {
int n, m;
scanf("%d%d", &n, &m);
for (int i = ; i < m; i++)
edge[i].read();
sort(edge, edge + m);
for (int i = ; i <= n; i++) pre[i] = i;
int last = -;
int ans = ;
for (int k = ; k <= m; k++) {
if (last != edge[k].cost || k == m) {
for (int i = ; i <= n; i++) {
if (vis[i]) {
int u = getfa(i, fa);
vec[u].push_back(i);
vis[i] = ;
}
}
for (int i = ; i <= n; i++) {
if (vec[i].size() > ) {
memset(A, , sizeof(A));
int len = vec[i].size();
for (int a = ; a < len; a++)
for (int b = a + ; b < len; b++) {
int u = vec[i][a], v = vec[i][b];
A[a][b] = A[b][a] = -G[u][v];
A[a][a] += G[u][v];
A[b][b] += G[u][v];
}
ans = ans * Gauss(A, len - ) % MOD;
for (int a = ; a < len; a++)
pre[vec[i][a]] = i;
}
}
for (int i = ; i <= n; i++)
fa[i] = getfa(i, pre), vec[i].clear();
if (k == m) break;
last = edge[k].cost;
}
int u = edge[k].u, v = edge[k].v;
u = getfa(u, pre); v = getfa(v, pre);
if (u == v) continue;
vis[u] = vis[v] = ;
fa[getfa(u, fa)] = getfa(v, fa);
G[u][v]++;
G[v][u]++;
}
int flag = ;
for (int i = ; i <= n; i++) {
if (getfa(i, fa) != getfa(i - , fa)) {
puts("");
return ;
}
}
if (!m) {
puts("");
return ;
}
printf("%d\n", ans);
return ;
}

1017. [JSOI2008]魔兽地图DotR

一种很妙的树上背包。

$dp[i][j][k]$ 表示处理完 $i$ 的子树,$j$ 个 $i$ 物品贡献给父亲,花费为 $k$ 的最大价值。

再用一个 $f[i][j]$ 表示做完 $i$ 个儿子,花费为 $j$ 的最大价值

然后就可以xjb转移了。

#include <bits/stdc++.h>
#define pii pair<int, int>
#define fi first
#define se second const int N = ;
const int INF = 0x3f3f3f3f;
const int M = 2e3 + ; int dp[N][][M], f[N][M], n, m, mx[N], cost[N], val[N];
int degree[N];
std::vector<std::pii> vec[N]; void dfs(int u) {
if (vec[u].empty()) {
mx[u] = std::min(mx[u], m / cost[u]);
for (int i = ; i <= mx[u]; i++)
for (int j = i; j <= mx[u]; j++)
dp[u][i][j * cost[u]] = (j - i) * val[u];
return;
}
mx[u] = INF;
for (auto p: vec[u]) {
int v = p.fi;
dfs(v);
mx[u] = std::min(mx[u], mx[v] / p.se);
cost[u] += cost[v] * p.se;
}
mx[u] = std::min(mx[u], m / cost[u]);
memset(f, 0xcf, sizeof f);
f[][] = ;
for (int c = mx[u]; ~c; c--) {
int cur = ;
for (auto p: vec[u]) {
int v = p.fi;
cur++;
for (int j = ; j <= m; j++)
for (int k = ; k <= j; k++)
f[cur][j] = std::max(f[cur][j], f[cur - ][j - k] + dp[v][c * p.se][k]);
}
for (int i = ; i <= c; i++)
for (int j = ; j <= m; j++)
dp[u][i][j] = std::max(dp[u][i][j], (c - i) * val[u] + f[cur][j]);
}
} int main() {
scanf("%d%d", &n, &m);
for (int i = ; i <= n; i++) {
scanf("%d", val + i);
char s[];
scanf("%s", s);
if (s[] == 'A') {
int cnt;
scanf("%d", &cnt);
while (cnt--) {
int who, need;
scanf("%d%d", &who, &need);
vec[i].push_back(std::pii(who, need));
degree[who]++;
}
} else {
scanf("%d%d", cost + i, mx + i);
}
}
bool flag = ;
for (int i = ; i <= n; i++)
if (degree[i])
flag = ;
int ans = ;
memset(dp, 0xcf, sizeof dp);
if (flag) {
for (int i = ; i <= n; i++)
if (!degree[i]) {
dfs(i);
for (int j = ; j <= mx[i]; j++)
for (int k = ; k <= m; k++)
ans = std::max(ans, dp[i][j][k]);
}
} else {
f[][] = ;
for (int i = ; i <= n; i++)
for (int k = ; k <= mx[i]; k++)
for (int j = k * cost[i]; j <= m; j++)
f[i][j] = std::max(f[i][j], f[i - ][j - k * cost[i]] + k * val[i]);
for (int i = ; i <= m; i++)
ans = std::max(ans, f[n][i]);
}
printf("%d\n", ans);
return ;
}

1018. [SHOI2008]堵塞的交通traffic

线段树分治。具体就是能实现删除/撤销操作。把时间看成下标,那么加入和删除就是一个区间,就像线段树一样分成 $log$ 个区间去操作。

可撤回的并查集就得按秩合并实现了。复杂度为 $O(nlog^2 n)$。

#include <bits/stdc++.h>
#define pii pair<int, int>
#define fi first
#define se second const int N = 2e5 + ;
int fa[N], sz[N], top, n, q, cnt, id[][N], tol;
std::pii st[N], query[N];
std::map<int, int> mp[N]; struct Node {
int u, v, st, ed;
Node(int u = , int v = , int st = , int ed = ): u(u), v(v), st(st), ed(ed) {}
};
std::vector<Node> E; int getfa(int x) {
while (fa[x] != x)
x = fa[x];
return x;
} inline void merge(int x, int y) {
x = getfa(x), y = getfa(y);
if (sz[x] > sz[y]) std::swap(x, y);
fa[x] = y; sz[y] += sz[x];
st[++top] = std::pii(x, y);
} void solve(int l, int r, const std::vector<Node> &E) {
std::vector<Node> L, R;
int mid = l + r >> ;
int temp = top;
for (auto p: E) {
if (p.st <= l && p.ed >= r) {
merge(p.u, p.v);
} else {
if (p.st <= mid) L.push_back(p);
if (p.ed > mid) R.push_back(p);
}
}
if (l == r) {
puts(getfa(query[l].fi) == getfa(query[l].se) ? "Y" : "N");
} else {
solve(l, mid, L);
solve(mid + , r, R);
}
while (top > temp) {
int x = st[top].fi, y = st[top].se;
fa[x] = x; sz[y] -= sz[x];
top--;
}
} int main() {
scanf("%d", &n);
for (int i = ; i <= n; i++)
id[][i] = ++tol, id[][i] = ++tol;
while () {
char s[];
scanf("%s", s);
if (s[] == 'E') break;
int x1, y1, x2, y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
if (s[] == 'O') {
mp[id[x1][y1]][id[x2][y2]] = mp[id[x2][y2]][id[x1][y1]] = E.size();
E.push_back(Node(id[x1][y1], id[x2][y2], q + , -));
} else if (s[] == 'C') {
E[mp[id[x1][y1]][id[x2][y2]]].ed = q;
} else {
query[++q] = std::pii(id[x1][y1], id[x2][y2]);
}
}
for (auto &p: E) {
if (p.ed == -)
p.ed = q;
}
for (int i = ; i <= tol; i++)
fa[i] = i, sz[i] = ;
solve(, q, E);
return ;
}

1019. [SHOI2008]汉诺塔

%这篇题解

会想到这个解法大概就是汉诺塔问题都是基于递归解决的思想吧,我要移动 $j$ 个盘子,那么就得先移动 $j-1$ 个盘子到一个柱子上,递归解决。

用 $f[j][i]$ 表示 $i$ 上有 $j$ 个盘子,其他柱子上没有盘子,将这 $j$ 个盘子移动到 $g[j][i]$ 上的最小步数

边界 $f[1][i] = 1$,$g[1][i]$ 由输入的优先级决定。

当前 $i$ 柱子上有 $j$ 个盘子,那么就先将 $j - 1$ 个盘子移动到 $a$ 上,$a=g[j - 1][i]$,剩下的柱子 $b = 3 - i - a$。再把最后一个盘子移动到 $b$ 上。

若 $g[j - 1][a] = b$,那么直接把 $a$ 上 $j - 1$ 个盘子移动到 $b$ 上就完成了。

此时 $f[j][i] = f[j - 1][i] + 1 + f[j - 1][a]$,$g[j][i] = b$。

否则 $g[j -1][a] = i$,那么先把 $a$ 上 $j-1$ 个盘子移动到 $i$ 上,再把 $b$ 上的大盘子移动到 $a$ 上,再把 $i$ 上 $j-1$ 个盘子移动到 $a$ 上。

此时 $f[j][i] = f[j - 1][i] + 1 + f[j - 1][a] + 1 + f[j - 1][i]$,$g[j][i] = a$。

答案即为 $f[n][0]$。

因为第一步符合操作优先级,那么第二步是模仿第一步来的,肯定也符合优先级,这么推下去也是符合优先级的。

然后又有一个要求是不移动刚移动的那个盘子,我们在移动完 $f[j - 1][i]$ 或者 $f[j - 1][a]$ 之后,都是移动那个大盘子,而不会再移动这 $j-1$ 个盘子(其实也就是最顶部那个盘子),所以也是符合要求的。

#include <bits/stdc++.h>
#define ll long long const int N = ;
ll f[N][];
int g[N][];
bool vis[]; int main() {
int n;
scanf("%d", &n);
for (int i = ; i <= ; i++) {
static char s[];
scanf("%s", s);
int a = s[] - 'A', b = s[] - 'A';
if (vis[a]) continue;
vis[a] = ;
f[][a] = ;
g[][a] = b;
}
for (int j = ; j <= n; j++)
for (int i = ; i < ; i++) {
int a = g[j - ][i], b = - a - i;
if (g[j - ][a] == b) f[j][i] = f[j - ][i] + + f[j - ][a], g[j][i] = b;
else f[j][i] = f[j - ][i] + + f[j - ][a] + + f[j - ][i], g[j][i] = a;
}
printf("%lld\n", f[n][]);
return ;
}

1020. [SHOI2008]安全的航线flight

可以对一条线段每间隔一个 eps 的长度的点找一下答案,但是这样会TLE。

其实得到一个答案后,很多小于这个答案的点就可以不用搜了。

可以迭代地解决问题。

先把所有航线的线段加入一个队列。

1. 取出一条线段后,左端点为 $a$,右端点为 $b$,找到 $a$ 离得最近的陆地的点 $p_1$,并用两者的距离更新答案,找到 $b$ 离的最近的陆地的点 $p_2$,并用两者的距离更新答案。

2. 找到线段 $ab$ 上的点 $p$,使得 $p$ 和 $p_1$ 之间的距离与 $p$ 和 $p_2$ 之间的距离 $d$ 大致相等,这一步可以二分得到。

3. 当 $d$ 的距离比当前 ans 小的时候,就丢掉这条线段。

4. 否则将 $(a, p)$ 和 $(p, b)$ 重新加入队列。

5. 重复上述过程直到队列为空。

第 3 步就是一个最优性剪枝,因为这条线段上的所有点到最近陆地的距离都不超过 $d$。

#include <bits/stdc++.h>

const double pi = acos(-1.0);
const int INF = 0x3f3f3f3f;
const double eps = 1e-;
const int N = ; int dcmp(double x) {
if (fabs(x) < eps) return ;
return x > ? : -;
} struct Point {
double x, y;
Point(double x = , double y = ): x(x), y(y) {}
Point operator + (const Point &p) const { return Point(x + p.x, y + p.y); }
Point operator - (const Point &p) const { return Point(x - p.x, y - p.y); }
Point operator * (const double &rhs) const { return Point(x * rhs, y * rhs); }
Point operator / (const double &rhs) const { return Point(x / rhs, y / rhs); }
bool operator == (const Point &p) const { return !dcmp(x - p.x) && !dcmp(y - p.y); }
void read() { scanf("%lf%lf", &x, &y); }
void print() { printf("%.6f %.6f\n", x, y); }
} flight[N]; typedef Point Vector; double Dot(const Vector &a, const Vector &b) { return a.x * b.x + a.y * b.y; }
double Len(const Vector &a) { return sqrt(Dot(a, a)); }
double Cross(const Vector &a, const Vector &b) { return a.x * b.y - a.y * b.x; }
bool On(const Point &p, const Point &a, const Point &b) { return !dcmp(Cross(a - p, b - p)) && dcmp(Dot(a - p, b - p)) <= ; }
Vector Normal(const Vector &a) { int l = Len(a); return Vector(-a.y / l, a.x / l); } struct Seg {
Point a, b;
Seg() {}
Seg(const Point &a, const Point &b): a(a), b(b) {}
} queue[ + ]; struct Polygon {
std::vector<Point> poly;
int n;
bool In(const Point &p) {
int wn = ;
for (int i = ; i <= n; i++) {
if (On(p, poly[i], poly[i % n + ])) return ;
int k = dcmp(Cross(poly[i % n + ] - poly[i], p - poly[i]));
int d1 = dcmp(poly[i].y - p.y);
int d2 = dcmp(poly[i % n + ].y - p.y);
if (k > && d1 <= && d2 > ) wn++;
if (k < && d2 <= && d1 > ) wn--;
}
if (wn) return ;
return ;
}
} island[N]; struct near {
Point p;
double dis;
near() {}
near(const Point &a, const double &b): p(a), dis(b) {}
}; int n, m; void init() {
scanf("%d%d", &n, &m);
for (int i = ; i <= m; i++)
flight[i].read();
for (int i = ; i <= n; i++) {
scanf("%d", &island[i].n);
island[i].poly.resize(island[i].n + );
for (int j = ; j <= island[i].n; j++)
island[i].poly[j].read();
}
} bool check(const Point &p) {
for (int i = ; i <= n; i++)
if (island[i].In(p)) return ;
return ;
} Point GetLineIntersection(const Point &P, const Vector &v, const Point &Q, const Vector &w) {
Vector u = P - Q;
double t = Cross(w, u) / Cross(v, w);
return P + v * t;
}
// 点 a 到线段 bc 的最近点
near DISPS(const Point &a, const Point &b, const Point &c) {
if (b == c) return near(b, Len(b - a));
Vector v1 = c - b, v2 = a - b, v3 = a - c;
if (dcmp(Dot(v1, v2)) <= ) return near(b, Len(v2));
if (dcmp(Dot(v1, v3)) >= ) return near(c, Len(v3));
Vector v = Normal(b - c);
Point p = GetLineIntersection(a, v, b, v1);
return near(p, Len(a - p));
} double ans; near Find(const Point &p) {
if (check(p)) return near(p, );
near ans1;
ans1.dis = 1e10;
for (int i = ; i <= n; i++)
for (int j = ; j <= island[i].n; j++) {
near cur = DISPS(p, island[i].poly[j], island[i].poly[j % island[i].n + ]);
if (dcmp(ans1.dis - cur.dis) >= ) ans1 = cur;
}
ans = std::max(ans, ans1.dis);
return ans1;
} const int M = 1e5; void solve() {
int head = , tail = ;
for (int i = ; i < m; i++)
queue[++tail] = Seg(flight[i], flight[i + ]), Find(flight[i]);
Find(flight[m]);
while (head != tail) {
Seg cur = queue[head = head % M + ];
Point p1 = Find(cur.a).p, p2 = Find(cur.b).p, l = cur.a, r = cur.b;
while (Len(r - l) > 1e-) {
Point mid = (l + r) / ;
if (Len(mid - p1) < Len(mid - p2)) l = mid;
else r = mid;
}
double nowans = std::min(Len(l - p1), Len(l - p2));
Find(l);
if (ans + 0.0005 < nowans) {
queue[tail = tail % M + ] = Seg(cur.a, l);
queue[tail = tail % M + ] = Seg(l, cur.b);
}
}
} int main() {
freopen("in.txt", "r", stdin);
init();
solve();
printf("%.2f\n", ans);
return ;
}

1021. [SHOI2008]Debt 循环的债务

自己的DP真是弱爆了...虽然其他也弱...看完别人的题解恍然大悟...

$dp[i][j][k]$ 表示前 $i$ 种钞票,第一个人有 $j$ 块钱,第二个人有 $k$ 块钱所需要交换的次数。

$dp[0][第一个人初始钱数][第二个人初始钱数] = 0$

然后就暴力枚举两个人要多少张 $i$ 钞票转移即可。

#include <bits/stdc++.h>

const int N = ;
const int INF = 0x3f3f3f3f;
const int val[] = {, , , , , , };
int dp[][N][N], sum, cnt[][], d[], e[]; inline bool chkmin(int &a, const int &b) {
return a > b ? a = b, : ;
} int main() {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
for (int i = ; i <= ; i++)
for (int j = ; j <= ; j++) {
scanf("%d", &cnt[i][j]);
sum += cnt[i][j] * val[j];
d[i] += cnt[i][j] * val[j];
e[j] += cnt[i][j];
}
int lasta = d[] - a + c, lastb = d[] - b + a, lastc = sum - lasta - lastb;
if (lasta < || lastb < || lastc < ) {
puts("impossible");
return ;
}
memset(dp, 0x3f, sizeof dp);
dp[][d[]][d[]] = ;
for (int i = ; i <= ; i++)
for (int j = ; j <= sum; j++)
for (int k = ; k + j <= sum; k++) if (dp[i - ][j][k] != INF) {
chkmin(dp[i][j][k], dp[i - ][j][k]);
for (int m = ; m <= e[i]; m++)
for (int n = ; n + m <= e[i]; n++) {
int difa = m - cnt[][i], difb = n - cnt[][i];
int jj = j + difa * val[i], kk = k + difb * val[i];
if (jj < || kk < || sum - jj - kk < ) continue;
chkmin(dp[i][jj][kk], dp[i - ][j][k] + (std::abs(difa) + std::abs(difb) + std::abs(difa + difb)) / );
}
}
if (dp[][lasta][lastb] == INF) puts("impossible");
else printf("%d\n", dp[][lasta][lastb]);
return ;
}

1022. [SHOI2008]小约翰的游戏John

Anti-Nim模板题。

先手必胜当且仅当

1. 所有石堆个数小于 $2$ 且 SG 值为 $0$

2. 存在一个石堆个数不小于 $2$ 且 SG 值不为 $0$

#include <bits/stdc++.h>

int main() {
int T;
scanf("%d", &T);
while (T--) {
int n;
scanf("%d", &n);
bool flag = ;
int sg = ;
while (n--) {
int x;
scanf("%d", &x);
sg ^= x;
if (x > ) flag = ;
}
if (flag)
puts(sg ? "John" : "Brother");
else
puts(!sg ? "John" : "Brother");
}
return ;
}

1023. [SHOI2008]cactus仙人掌图

考虑求树上的直径,$f[u]$ 表示 $u$ 到以 $u$ 为子树中的节点的距离最大值,$ans$ 表示树的直径。

$ans = max \{f[u] + f[v] + 1 \}, v \in son(u)$

$f[u] = max \{f[v] + 1 \}, v \in son(u)$

如果能将环缩成点,那么就直接做就行了。

先跑tarjan,求出 $low$、$dfn$、$dep$ 数组,如果这条边是一条树边,即满足 $dfn[u] < low[v]$,$v \in son(u)$,就直接按上面的方法更新。

否则不更新。

然后如果 $dfn[u] < dfn[v] \wedge fa[v] \neq u$,则说明 $u$ 到 $v$ 是一个环,并且 $u$ 是环的起点,$v$ 是环的终点。

就把这个环单独处理一下,并把最长链的信息整合到 $u$ 上即可。

对一个环上处理,即把环上所有顶点拿出来暴力更新答案。

把环上节点按深度依次放到数组里,$C$ 代表这个环,$L$ 代表环的节点个数。

$ans = max \{ f[u] + f[v] + dis(u, v) \}, u \in C, v \in C$

$dis(u, v) = min \{ dep[v] - dep[u], L - (dep[v] - dep[u]) \}$

在数组里按深度排好序之后就可以用下标之差来表示距离了。

$ans = max \{ f[v] + v + f[u] - u \},2 \times dis(u, v) \leq L$

可以用单调队列优化。注意得把数组复制一下,因为最优点有可能被 $u$ 和 $v$ 隔开了。

$f[u] = max \{ f[v] + dis(u, v) \}$ 把最长链合并到 $u$ 上。

#include <bits/stdc++.h>

namespace IO
{
char buf[ << ], buf2[ << ], a[], *p1 = buf, *p2 = buf, hh = '\n';
int p, p3 = -;
void read() {}
void print() {}
inline int getc() {
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, , << , stdin), p1 == p2) ? EOF : *p1++;
}
inline void flush() {
fwrite(buf2, , p3 + , stdout), p3 = -;
}
template <typename T, typename... T2>
inline void read(T &x, T2 &... oth) {
T f = ; x = ;
char ch = getc();
while (!isdigit(ch)) { if (ch == '-') f = -; ch = getc(); }
while (isdigit(ch)) { x = x * + ch - ; ch = getc(); }
x *= f;
read(oth...);
}
template <typename T, typename... T2>
inline void print(T x, T2... oth) {
if (p3 > << ) flush();
if (x < ) buf2[++p3] = , x = -x;
do {
a[++p] = x % + ;
} while (x /= );
do {
buf2[++p3] = a[p];
} while (--p);
buf2[++p3] = hh;
print(oth...);
}
}
#define read IO::read
#define print IO::print inline bool chkmin(int &x, int y) { return x > y ? x = y, : ; }
inline bool chkmax(int &x, int y) { return x < y ? x = y, : ; } const int N = 1e5 + ;
std::vector<int> vec[N];
int fa[N], dfn[N], low[N], tol, dep[N], n, m, dp[N], ans;
int que[N], a[N]; void solve(int u, int v) {
int cnt = dep[v] - dep[u] + ;
for (int i = v; i != u; i = fa[i])
a[cnt--] = dp[i];
a[] = dp[u];
cnt = dep[v] - dep[u] + ;
for (int i = ; i <= cnt; i++)
a[i + cnt] = a[i];
int l = , r = ;
que[l = r = ] = ;
for (int i = ; i <= cnt + (cnt >> ); i++) {
if (i - que[l] > (cnt >> )) l++;
chkmax(ans, a[i] + i + a[que[l]] - que[l]);
while (l <= r && a[i] - i >= a[que[r]] - que[r]) r--;
que[++r] = i;
}
for (int i = ; i <= cnt; i++)
chkmax(dp[u], a[i] + std::min(i - , cnt - i + ));
} void dfs(int u, int pre) {
fa[u] = pre; dep[u] = dep[pre] + ;
dfn[u] = low[u] = ++tol;
for (int v: vec[u]) {
if (v == pre) continue;
if (!dfn[v]) {
dfs(v, u);
chkmin(low[u], low[v]);
} else {
chkmin(low[u], dfn[v]);
}
if (dfn[u] < low[v]) chkmax(ans, dp[u] + dp[v] + ), chkmax(dp[u], dp[v] + );
}
for (int v: vec[u]) {
if (dfn[u] < dfn[v] && fa[v] != u)
solve(u, v);
}
} int main() {
read(n, m);
for (int i = ; i <= m; i++) {
int k, u;
read(k, u);
for (int j = ; j <= k; j++) {
int v;
read(v);
vec[u].push_back(v);
vec[v].push_back(u);
u = v;
}
}
dfs(, );
print(ans);
IO::flush();
return ;
}

1024. [SCOI2009]生日快乐

由于 $n$ 很小,那么可以暴力搜一波。

因为所有面积必须相同,所以考虑每一刀的时候,左右两部分必须长度符合比例。

#include <bits/stdc++.h>

double solve(double x, double y, int n) {
if (x < y) std::swap(x, y);
if (n == ) return x / y;
double ans = 1e9;
for (int i = ; i <= n / ; i++) {
ans = std::min(ans, std::max(solve(x / n * i, y, i), solve(x / n * (n - i), y, n - i)));
ans = std::min(ans, std::max(solve(x, y / n * i, i), solve(x, y / n * (n - i), n - i)));
}
return ans;
} int main() {
double x, y;
int n;
scanf("%lf%lf%d", &x, &y, &n);
printf("%.6f\n", solve(x, y, n));
return ;
}

1026. [SCOI2009]windy数

数位DP裸题啦。

#include <bits/stdc++.h>
#define int long long
using namespace std; int dp[][];
int a[]; int DP(int pos, int pre, bool limit, bool lead) {
if (pos < ) return ;
if (!limit && !lead && dp[pos][pre] != -) return dp[pos][pre];
int ans = ;
int up = limit ? a[pos] : ;
if (lead) {
ans += DP(pos - , , limit && == up, );
for (int i = ; i <= up; i++)
ans += DP(pos - , i, limit && i == up, );
} else {
for (int i = ; i <= pre - && i <= up; i++)
ans += DP(pos - , i, limit && i == up, );
for (int i = pre + ; i <= up; i++)
ans += DP(pos - , i, limit && i == up, );
}
if (!limit && !lead) dp[pos][pre] = ans;
return ans;
} int solve(int x) {
//if (x == 0) return 1;
int pos = ;
while (x) {
a[pos++] = x % ;
x /= ;
}
return DP(pos - , , , );
} signed main() {
int l, r;
scanf("%lld%lld", &l, &r);
memset(dp, -, sizeof(dp));
printf("%lld\n", solve(r) - solve(l - ));
return ;
}

1027. [JSOI2007]合金

计算几何的题怎么都这么妙啊!

首先第三个元素是没有用的,因为如果前两个符合了,第三个肯定也符合。

然后把第一个元素看成 $x$ 轴,第二个元素看成 $y$ 轴。

那么对于一些原材料能形成的合金肯定在这些原材料的凸包内。

枚举每一对原材料 $(i, j)$,若所有合金都在其逆时针方向,那么 $i$ 就向 $j$ 连一条边。

之后就是求这个图的最小环,floyd即可。

不过需要特判一种情况,当所有合金都是一个点时,且有原材料刚好就是这种合金,答案就是 $1$。

看到有的博客说,所有金属都在一条线段上要特判,其实这种情况能被后面处理掉。即 $i$ 会向 $j$ 连一条边,$j$ 会向 $i$ 连一条边,答案就是 $2$。

所以就不用特判啦。

#include <bits/stdc++.h>
#define db double const db eps = 1e-;
inline int sign(db k) { return k < -eps ? - : k > eps; }
inline int cmp(db k1, db k2) { return sign(k1 - k2); } struct P {
db x, y;
P() {}
P(db x, db y): x(x), y(y) {}
P operator + (const P &rhs) const { return P(x + rhs.x, y + rhs.y); }
P operator - (const P &rhs) const { return P(x - rhs.x, y - rhs.y); }
P operator * (const db &k) const { return P(x * k, y * k); }
P operator / (const db &k) const { return P(x / k, y / k); }
bool operator < (const P &rhs) const { int c = cmp(x, rhs.x); return c ? c == - : cmp(y, rhs.y) == -; }
bool operator == (const P &rhs) const { return !cmp(x, rhs.x) && !cmp(y, rhs.y); }
db distTo(const P &rhs) const { return (*this - rhs).abs(); }
db alpha() { return atan2(y, x); }
void read() { scanf("%lf%lf", &x, &y); }
void print() { printf("%.10f %.10f\n", x, y); }
db abs() { return sqrt(abs2()); }
db abs2() { return x * x + y * y; }
P rotate(const db &k) { return P(x * cos(k) - y * sin(k), x * sin(k) + y * cos(k)); }
P rotate90() { return P(-y, x); }
P unit() { return *this/abs(); }
P normal() { return rotate90() / abs(); }
int quad() { return sign(y) == || (sign(y) == && sign(x) >= ); }
db dot(const P &p) const { return x * p.x + y * p.y; }
db det(const P &p) const { return x * p.y - y * p.x; }
}; #define cross(p1, p2, p3) ((p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y))
#define crossOp(p1, p2, p3) sign(cross(p1, p2, p3)) // 判断 p1p2 和 q1q2 是否相交
bool chkLL(const P &p1, const P &p2, const P &q1, const P &q2) {
db a1 = cross(q1, q2, p1), a2 = -cross(q1, q2, p2);
return sign(a1 + a2) != ;
}
P isLL(const P &p1, const P &p2, const P &q1, const P &q2) {
assert(chkLL(p1, p2, q1, q2));
db a1 = cross(q1, q2, p1), a2 = -cross(q1, q2, p2);
return (p1 * a2 + p2 * a1) / (a1 + a2);
}
bool intersect(db l1, db r1, db l2, db r2) {
if (l1 > r1) std::swap(l1, r2); if (l2 > r2) std::swap(l2, r2);
return !(cmp(r1, l2) == - || cmp(r2, l1) == -);
}
bool isSS(const P &p1, const P &p2, const P &q1, const P &q2) {
return intersect(p1.x, p2.x, q1.x, q2.x) && intersect(p1.y, p2.y, q1.y, q2.y)
&& crossOp(p1, p2, q1) * crossOp(p1, p2, q2) <=
&& crossOp(q1, q2, p1) * crossOp(q1, q2, p2) <= ;
}
bool isSS_strict(const P &p1, const P &p2, const P &q1, const P &q2) {
return crossOp(p1, p2, q1) * crossOp(p1, p2, q2) <
&& crossOp(q1, q2, p1) * crossOp(q1, q2, p2) < ;
}
bool isMiddle(db a, db m, db b) {
return sign(a - m) == || sign(b - m) == || (a < m != b < m);
}
bool isMiddle(const P &a, const P &m, const P &b) {
return isMiddle(a.x, m.x, b.x) && isMiddle(a.y, m.y, b.y);
}
bool onSeg(const P &p1, const P &p2, const P &q) {
return crossOp(p1, p2, q) == && isMiddle(p1, q, p2);
}
bool onSeg_strict(const P &p1, const P &p2, const P &q) {
return crossOp(p1, p2, q) == && sign((q - p1).dot(p1 - p2)) * sign((q - p2).dot(p1 - p2)) < ;
}
P proj(const P &p1, const P &p2, const P &q) {
P dir = p2 - p1;
return p1 + dir * (dir.dot(q - p1) / dir.abs2());
}
P reflect(const P &p1, const P &p2, const P &q) {
return proj(p1, p2, q) * - q;
}
db nearest(const P &p1, const P &p2, const P &q) {
P h = proj(p1, p2, q);
if (isMiddle(p1, h, p2)) return q.distTo(h);
return std::min(p1.distTo(q), p2.distTo(q));
}
db disSS(const P &p1, const P &p2, const P &q1, const P &q2) {
if (isSS(p1, p2, q1, q2)) return ;
return std::min(std::min(nearest(p1, p2, q1), nearest(p1, p2, q2)), std::min(nearest(q1, q2, p1), nearest(q1, q2, p2)));
}
db rad(const P &p1, const P &p2) {
return atan2l(p1.det(p2), p1.dot(p2));
} const int N = ;
int n, m;
P material[N], alloy[N]; bool spj() {
for (int i = ; i < n; i++)
if (!(alloy[i] == alloy[i - ]))
return ;
for (int i = ; i < m; i++)
if (alloy[] == material[i])
return ;
return ;
} const int INF = 0x3f3f3f3f;
int dis[N][N]; inline bool chkmin(int &a, int b) { return a > b ? a = b, : ; } int floyd() {
for (int k = ; k < m; k++)
for (int i = ; i < m; i++)
if (dis[i][k] < INF)
for (int j = ; j < m; j++)
chkmin(dis[i][j], dis[i][k] + dis[k][j]);
int ans = INF;
for (int i = ; i < m; i++)
chkmin(ans, dis[i][i]);
return ans;
} void addedge(int p, int q) {
for (int i = ; i < n; i++) {
int k = crossOp(material[p], material[q], alloy[i]);
if (k > ) continue;
if (k < ) return;
if (!onSeg(material[p], material[q], alloy[i])) return;
}
dis[p][q] = ;
} int main() {
scanf("%d%d", &m, &n);
for (int i = ; i < m; i++) {
material[i].read();
db x;
scanf("%lf", &x);
}
for (int i = ; i < n; i++) {
alloy[i].read();
db x;
scanf("%lf", &x);
}
if (spj()) {
puts("");
return ;
}
memset(dis, 0x3f, sizeof dis);
for (int i = ; i < m; i++)
for (int j = ; j < m; j++)
if (i != j)
addedge(i, j);
int ans = floyd();
printf("%d\n", ans == INF ? - : ans);
return ;
}

1028. [JSOI2007]麻将

暴力枚举要补哪张牌,再枚举谁是对子,最后 check 一下剩下能否组成顺子或刻子,优先 check 刻子。

#include <cstdio>
#include <vector>
#include <algorithm> const int N = ;
int a[N], cnt[N], n, m, temp[N]; bool solve() {
for (int i = ; i <= n; i++) {
if (cnt[i] < ) continue;
bool flag = ;
cnt[i] -= ;
for (int j = ; j <= n + ; j++)
temp[j] = cnt[j];
for (int j = ; j <= n + ; j++) {
if (temp[j] < ) { flag = ; break; }
temp[j] %= ;
temp[j + ] -= temp[j];
temp[j + ] -= temp[j];
}
cnt[i] += ;
if (flag) return ;
}
return ;
} int main() {
scanf("%d%d", &n, &m);
for (int i = ; i <= * m + ; i++) {
int x;
scanf("%d", &x);
cnt[x]++;
}
std::vector<int> ans;
for (int i = ; i <= n; i++) {
cnt[i]++;
if (solve())
ans.push_back(i);
cnt[i]--;
}
if (ans.size() == ) puts("NO");
else for (int x: ans)
printf("%d ", x);
puts("");
return ;
}

1029. [JSOI2007]建筑抢修

ddl 靠前的先做,如果做不了,看看之前有没有耗时比它多的,有的话换掉,用堆维护。

#include <bits/stdc++.h>
#define ll long long const int N = 2e5 + ;
struct P {
ll x, y;
bool operator < (const P &pp) const { return y < pp.y; }
} p[N]; int main() {
int n;
scanf("%d", &n);
for (int i = ; i <= n; i++)
scanf("%lld%lld", &p[i].x, &p[i].y);
ll cur = ;
std::sort(p + , p + + n);
std::priority_queue<int> que;
que.push();
int ans = ;
for (int i = ; i <= n; i++) {
if (cur + p[i].x <= p[i].y) {
cur += p[i].x;
que.push(p[i].x);
ans++;
} else if (p[i].x < que.top()) {
cur += p[i].x - que.top();
que.pop();
que.push(p[i].x);
}
}
printf("%d\n", ans);
return ;
}

1030. [JSOI2007]文本生成器

建出AC自动机后DP,注意第一维要枚举长度,因为第一维枚举节点的话一些节点会往回跳。

#include <bits/stdc++.h>

const int N = ;
const int sz = ;
const int MOD = ; void M(int &a) { if (a >= MOD) a -= MOD; } int n, m; struct Aho {
int trie[N][sz], tol, fail[N], last[N];
bool flag[N];
int dp[N][];
void init() {
tol = ;
memset(dp, , sizeof(dp));
newnode();
}
int newnode() {
for (int i = ; i < sz; i++)
trie[tol][i] = ;
fail[tol] = flag[tol] = last[tol] = ;
return tol++;
}
void insert(char *s) {
int n = strlen(s), cur = ;;
for (int i = ; i < n; i++) {
int id = s[i] - 'A';
if (!trie[cur][id]) trie[cur][id] = newnode();
cur = trie[cur][id];
}
flag[cur] = ;
}
int build() {
std::queue<int> que;
for (int i = ; i < sz; i++)
if (trie[][i]) que.push(trie[][i]);
while (!que.empty()) {
int u = que.front(); que.pop();
flag[u] |= flag[fail[u]];
for (int i = ; i < sz; i++) {
int &v = trie[u][i];
if (v) {
fail[v] = trie[fail[u]][i];
que.push(v);
last[v] = flag[fail[v]] ? fail[v] : last[fail[v]];
} else {
v = trie[fail[u]][i];
}
}
}
dp[][] = ;
for (int j = ; j < m; j++)
for (int i = ; i < tol; i++) if (dp[i][j] && !flag[i])
for (int k = ; k < sz; k++) {
M(dp[trie[i][k]][j + ] += dp[i][j]);
}
int ans = ;
for (int i = ; i < tol; i++)
if (!flag[i])
M(ans += dp[i][m]);
return ans;
}
} ac; char s[N]; int qp(int a, int b) {
int ans = ;
while (b) {
if (b & ) ans = ans * a % MOD;
a = a * a % MOD;
b >>= ;
}
return ans;
} int main() {
while (~scanf("%d%d", &n, &m)) {
ac.init();
for (int i = ; i < n; i++)
scanf("%s", s), ac.insert(s);
printf("%d\n", (qp(, m) - ac.build() + MOD) % MOD);
}
return ;
}

1031. [JSOI2007]字符加密Cipher

把字符串复制一下,求个后缀数组,再按sa枚举后缀,判断长度是否大于等于原字符串长度,注意坑就是第 $1$ 和 第 $len + 1$ 个后缀可能会重复。

#include <bits/stdc++.h>

const int N = 2e5 + ;
char s[N]; namespace SA {
int sa[N], rk[N], fir[N], sec[N], c[N], height[N];
void build(int len, int num = ) {
register int i, j, k;
for (i = ; i <= num; i++) c[i] = ;
for (i = ; i <= len; i++) ++c[fir[i] = s[i]];
for (i = ; i <= num; i++) c[i] += c[i - ];
for (i = len; i >= ; i--) sa[c[fir[i]]--] = i;
for (k = ; k <= len; k <<= ) {
int cnt = ;
for (i = len - k + ; i <= len; i++) sec[++cnt] = i;
for (i = ; i <= len; i++) if (sa[i] > k) sec[++cnt] = sa[i] - k;
for (i = ; i <= num; i++) c[i] = ;
for (i = ; i <= len; i++) ++c[fir[i]];
for (i = ; i <= num; i++) c[i] += c[i - ];
for (i = len; i >= ; i--) sa[c[fir[sec[i]]]--] = sec[i], sec[i] = ;
std::swap(fir, sec);
fir[sa[]] = ; cnt = ;
for (i = ; i <= len; i++)
fir[sa[i]] = (sec[sa[i]] == sec[sa[i - ]] && sec[sa[i] + k] == sec[sa[i - ] + k]) ? cnt : ++cnt;
if (cnt == len) break;
num = cnt;
}
k = ;
for (i = ; i <= len; i++) rk[sa[i]] = i;
for (i = ; i <= len; i++) {
if (rk[i] == ) continue;
if (k) k--;
j = sa[rk[i] - ];
while (j + k <= len && i + k <= len && s[i + k] == s[j + k]) k++;
height[rk[i]] = k;
}
}
} using namespace SA; char ans[N];
int cnt;
bool vis[N]; int id(int x, int len) {
if (x > len) x -= len;
return x;
} int main() {
scanf("%s", s + );
int len = strlen(s + );
for (int i = ; i <= len; i++)
s[i + len] = s[i];
build(len * );
for (int i = ; i <= * len; i++) {
if (sa[i] <= len + && !vis[id(sa[i], len)]) {
ans[++cnt] = s[sa[i] + len - ];
vis[id(sa[i], len)] = ;
if (cnt == len) {
puts(ans + );
return ;
}
}
}
return ;
}

1032. [JSOI2007]祖码Zuma

区间DP,把同颜色的段缩成一个点,用 $num$ 数组表示个数。

$f[l][r]$ 表示消除 $l$ 到 $r$ 区间所有点的最小花费。

当 $num[i] > 1$,$f[i][i] = 1$,否则等于 $2$。

然后区间DP,若左右端点相同时 $f[l][r] = f[l + 1][r - 1] + c$,其中当 $num[l] + num[r] > 2$ 时,$c = 0$,否则 $c = 1$。

然后枚举中断点即可。

#include <bits/stdc++.h>

const int INF = 0x3f3f3f3f;
const int N = ;
int f[N][N], num[N], color[N], cnt, n;
int a[N]; int main() {
memset(f, 0x3f, sizeof f);
scanf("%d", &n);
for (int i = ; i <= n; i++)
scanf("%d", a + i);
int cur = ;
for (int i = ; i <= n; i++) {
if (a[i] != a[i - ]) {
color[++cnt] = a[i - ];
num[cnt] = cur;
cur = ;
} else {
cur++;
}
}
color[++cnt] = a[n];
num[cnt] = cur;
for (int i = ; i <= cnt; i++)
f[i][i] = num[i] > ? : ;
for (int len = ; len <= cnt; len++)
for (int l = ; l <= cnt; l++) {
int r = l + len - ;
if (r > cnt) break;
if (color[l] == color[r])
f[l][r] = f[l + ][r - ] + ((num[l] + num[r]) > ? : );
for (int k = l; k < r; k++)
f[l][r] = std::min(f[l][r], f[l][k] + f[k + ][r]);
}
printf("%d\n", f[][cnt]);
return ;
}

1034. [ZJOI2008]泡泡堂BNB

感觉我双指针不行啊...写了个multiset过的。看了别人代码改了发双指针。

写几个样例大概就知道跟田忌赛马一样了。

#include <bits/stdc++.h>

const int N = 1e5 + ;
int a[N], b[N];
std::multiset<int> st1, st2;
bool vis[N]; int solve(int A[], int B[], int n) {
int ans = ;
int l1 = , l2 = , r1 = n, r2 = n;
while (l1 <= r1 && l2 <= r2) {
if (A[l1] > B[l2]) ans += , l1++, l2++;
else if (A[r1] > B[r2]) ans += , r1--, r2--;
else ans += (A[l1] == B[r2]) ? : , l1++, r2--;
}
return ans;
} int main() {
int n;
scanf("%d", &n);
for (int i = ; i <= n; i++)
scanf("%d", a + i);
for (int i = ; i <= n; i++)
scanf("%d", b + i);
std::sort(a + , a + + n);
std::sort(b + , b + + n);
printf("%d %d\n", solve(a, b, n), * n - solve(b, a, n));
}

1036. [ZJOI2008]树的统计Count

树剖板子题。

#include <bits/stdc++.h>
using namespace std; template<typename T>
inline void read(T &x) {
x = ; T f = ; char ch = getchar();
while (ch < '' || ch > '') { if (ch == '-') f = -; ch = getchar(); }
while (ch >= '' && ch <= '') { x = x * + ch - ; ch = getchar(); }
x *= f;
} const int INF = 0x3f3f3f3f;
const int N = ;
int w[N], wt[N], n, q;
int sz[N], son[N], fa[N], dep[N];
int dfn[N], tol, top[N];
struct E { int v, ne; } e[N * ];
int head[N], cnt; struct Seg {
#define lp p << 1
#define rp p << 1 | 1
int sum[N << ], mx[N << ];
inline void pushup(int p) {
sum[p] = sum[lp] + sum[rp];
mx[p] = max(mx[lp], mx[rp]);
}
void build(int p, int l, int r) {
if (l == r) {
sum[p] = mx[p] = wt[l];
return;
}
int mid = l + r >> ;
build(lp, l, mid);
build(rp, mid + , r);
pushup(p);
}
void update(int p, int l, int r, int pos, int val) {
if (l == r) {
sum[p] = mx[p] = val;
return;
}
int mid = l + r >> ;
if (pos <= mid) update(lp, l, mid, pos, val);
else update(rp, mid + , r, pos, val);
pushup(p);
}
int query1(int p, int l, int r, int x, int y) {
if (x <= l && y >= r) return sum[p];
int ans = ;
int mid = l + r >> ;
if (x <= mid) ans += query1(lp, l, mid, x, y);
if (y > mid) ans += query1(rp, mid + , r, x, y);
return ans;
}
int query2(int p, int l, int r, int x, int y) {
if (x <= l && y >= r) return mx[p];
int ans = -INF;
int mid = l + r >> ;
if (x <= mid) ans = max(ans, query2(lp, l, mid, x, y));
if (y > mid) ans = max(ans, query2(rp, mid + , r, x, y));
return ans;
}
} seg; inline void add(int u, int v) {
e[++cnt].v = v; e[cnt].ne = head[u]; head[u] = cnt;
e[++cnt].v = u; e[cnt].ne = head[v]; head[v] = cnt;
} void dfs1(int u, int pre, int d) {
dep[u] = d;
fa[u] = pre;
sz[u] = ;
for (int i = head[u]; i; i = e[i].ne) {
int v = e[i].v;
if (v == pre) continue;
dfs1(v, u, d + );
sz[u] += sz[v];
if (sz[v] > sz[son[u]]) son[u] = v;
}
} void dfs2(int u, int tp) {
top[u] = tp;
dfn[u] = ++tol;
wt[tol] = w[u];
if (!son[u]) return;
dfs2(son[u], tp);
for (int i = head[u]; i; i = e[i].ne) {
int v = e[i].v;
if (v != fa[u] && v != son[u])
dfs2(v, v);
}
} int solve1(int u, int v) {
int ans = ;
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) swap(u, v);
ans += seg.query1(, , n, dfn[top[u]], dfn[u]);
u = fa[top[u]];
}
if (dep[u] > dep[v]) swap(u, v);
ans += seg.query1(, , n, dfn[u], dfn[v]);
return ans;
} int solve2(int u, int v) {
int ans = -INF;
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) swap(u, v);
ans = max(ans, seg.query2(, , n, dfn[top[u]], dfn[u]));
u = fa[top[u]];
}
if (dep[u] > dep[v]) swap(u, v);
ans = max(ans, seg.query2(, , n, dfn[u], dfn[v]));
return ans;
} char s[]; int main() {
read(n);
for (int i = ; i < n; i++) {
int u, v;
read(u), read(v);
add(u, v);
}
for (int i = ; i <= n; i++) read(w[i]);
dfs1(, , );
dfs2(, );
seg.build(, , n);
read(q);
while (q--) {
scanf("%s", s);
int u, v;
read(u), read(v);
if (s[] == 'M') {
printf("%d\n", solve2(u, v));
} else if (s[] == 'S') {
printf("%d\n", solve1(u, v));
} else {
seg.update(, , n, dfn[u], v);
}
}
return ;
}

1037. [ZJOI2008]生日聚会Party

$dp[i][j][k][l]$ 表示 $i$ 个男生,$j$ 个女生,结尾段男生最多比女生多 $k$ 个,女生最多比男生多 $l$ 个。

$dp[i + 1][j][k + 1][\max\{l - 1, 0\}]$ $+=$ $dp[i][j][k][l]$

$dp[i][j+1][\max\{k - 1, 0\}][l+1]$ $+=$ $dp[i][j][k][l]$

#include <bits/stdc++.h>

const int MOD = ;
const int N = ;
int dp[N][N][][]; void M(int &x) {
if (x >= MOD) x -= MOD;
if (x < ) x += MOD;
} int main() {
int n, m, k;
scanf("%d%d%d", &n, &m, &k);
dp[][][][] = ;
for (int i = ; i <= n; i++)
for (int j = ; j <= m; j++)
for (int o = ; o <= k; o++)
for (int p = ; p <= k; p++) if (dp[i][j][o][p]) {
if (i < n && o < k)
M(dp[i + ][j][o + ][std::max(p - , )] += dp[i][j][o][p]);
if (j < m && p < k)
M(dp[i][j + ][std::max(o - , )][p + ] += dp[i][j][o][p]);
}
int ans = ;
for (int i = ; i <= k; i++)
for (int j = ; j <= k; j++)
M(ans += dp[n][m][i][j]);
printf("%d\n", ans);
return ;
}

1038. [ZJOI2008]瞭望塔

能看到其他所有点的区域就是轮廓线的半平面交。
然后最小高度就是半平面交与轮廓线这两个一次分段函数的差,极值肯定出现在分段点上,分别求一下即可。

#include <bits/stdc++.h>

#define db double

const db eps = 1e-;
inline int sign(db k) { return k < -eps ? - : k > eps; }
inline int cmp(db k1, db k2) { return sign(k1 - k2); } struct P {
db x, y;
P() {}
P(db x, db y): x(x), y(y) {}
P operator + (const P &rhs) const { return P(x + rhs.x, y + rhs.y); }
P operator - (const P &rhs) const { return P(x - rhs.x, y - rhs.y); }
P operator * (const db &k) const { return P(x * k, y * k); }
P operator / (const db &k) const { return P(x / k, y / k); }
bool operator < (const P &rhs) const { int c = cmp(x, rhs.x); return c ? c == - : cmp(y, rhs.y) == -; }
bool operator == (const P &rhs) const { return !cmp(x, rhs.x) && !cmp(y, rhs.y); }
db distTo(const P &rhs) const { return (*this - rhs).abs(); }
db alpha() { return atan2(y, x); }
void read() { scanf("%lf%lf", &x, &y); }
void print() { printf("%.10f %.10f\n", x, y); }
db abs() { return sqrt(abs2()); }
db abs2() { return x * x + y * y; }
P rot(const db &k) { return P(x * cos(k) - y * sin(k), x * sin(k) + y * cos(k)); }
P rot90() { return P(-y, x); }
P unit() { return *this / abs(); }
P normal() { return rot90() / abs(); }
int quad() { return sign(y) == || (sign(y) == && sign(x) >= ); }
db dot(const P &p) const { return x * p.x + y * p.y; }
db det(const P &p) const { return x * p.y - y * p.x; }
}; struct L { // ps[0] -> ps[1]
P ps[];
L() {}
L(const P &p0, const P &p1) {
ps[] = p0; ps[] = p1;
}
P &operator[](int i) { return ps[i]; }
P dir() { return ps[] - ps[]; }
bool include(const P &p) { return sign((ps[] - ps[]).det(p - ps[])) > ; }
L push() { // push eps outawrd
const db Eps = 1e-;
P delta = (ps[] - ps[]).normal() * Eps;
return {ps[] - delta, ps[] - delta};
}
}; #define cross(p1, p2, p3) ((p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y))
#define crossOp(p1, p2, p3) sign(cross(p1, p2, p3)) // 判断 p1p2 和 q1q2 是否相交
bool chkLL(const P &p1, const P &p2, const P &q1, const P &q2) {
db a1 = cross(q1, q2, p1), a2 = -cross(q1, q2, p2);
return sign(a1 + a2) != ;
}
// 直线交点
P isLL(const P &p1, const P &p2, const P &q1, const P &q2) {
assert(chkLL(p1, p2, q1, q2));
db a1 = cross(q1, q2, p1), a2 = -cross(q1, q2, p2);
return (p1 * a2 + p2 * a1) / (a1 + a2);
}
P isLL(L l1, L l2) {
return isLL(l1[], l1[], l2[], l2[]);
}
/***** 线段相交 *****/
bool intersect(db l1, db r1, db l2, db r2) {
if (l1 > r1) std::swap(l1, r2); if (l2 > r2) std::swap(l2, r2);
return !(cmp(r1, l2) == - || cmp(r2, l1) == -);
}
bool isSS(const P &p1, const P &p2, const P &q1, const P &q2) {
return intersect(p1.x, p2.x, q1.x, q2.x) && intersect(p1.y, p2.y, q1.y, q2.y)
&& crossOp(p1, p2, q1) * crossOp(p1, p2, q2) <=
&& crossOp(q1, q2, p1) * crossOp(q1, q2, p2) <= ;
}
bool isSS_strict(const P &p1, const P &p2, const P &q1, const P &q2) {
return crossOp(p1, p2, q1) * crossOp(p1, p2, q2) <
&& crossOp(q1, q2, p1) * crossOp(q1, q2, p2) < ;
}
/***************/
/***** 点在线段上判定 *****/
bool isMiddle(db a, db m, db b) {
return sign(a - m) == || sign(b - m) == || (a < m != b < m);
}
bool isMiddle(const P &a, const P &m, const P &b) {
return isMiddle(a.x, m.x, b.x) && isMiddle(a.y, m.y, b.y);
}
bool onSeg(const P &p1, const P &p2, const P &q) {
return crossOp(p1, p2, q) == && isMiddle(p1, q, p2);
}
bool onSeg_strict(const P &p1, const P &p2, const P &q) {
return crossOp(p1, p2, q) == && sign((q - p1).dot(p1 - p2)) * sign((q - p2).dot(p1 - p2)) < ;
}
/*******************/
// 投影
P proj(const P &p1, const P &p2, const P &q) {
P dir = p2 - p1;
return p1 + dir * (dir.dot(q - p1) / dir.abs2());
}
// 反射
P reflect(const P &p1, const P &p2, const P &q) {
return proj(p1, p2, q) * - q;
}
// 最近点
db nearest(const P &p1, const P &p2, const P &q) {
P h = proj(p1, p2, q);
if (isMiddle(p1, h, p2)) return q.distTo(h);
return std::min(p1.distTo(q), p2.distTo(q));
}
// 线段距离
db disSS(const P &p1, const P &p2, const P &q1, const P &q2) {
if (isSS(p1, p2, q1, q2)) return ;
return std::min(std::min(nearest(p1, p2, q1), nearest(p1, p2, q2)), std::min(nearest(q1, q2, p1), nearest(q1, q2, p2)));
}
// 夹角
db rad(const P &p1, const P &p2) {
return atan2l(p1.det(p2), p1.dot(p2));
}
// 多边形面积
db area(const std::vector<P> &ps) {
db ans = ;
for (int i = , n = ps.size(); i < n; i++)
ans += ps[i].det(ps[(i + ) % n]);
return ans;
}
// 点包含 2: inside 1: onSeg 0: outside
int contain(const std::vector<P> &ps, const P &p) {
int n = ps.size(), ret = ;
for (int i = ; i < n; i++) {
P u = ps[i], v = ps[(i + ) % n];
if (onSeg(u, v, p)) return ;
if (cmp(u.y, v.y) <= ) std::swap(u, v);
if (cmp(p.y, u.y) > || cmp(p.y, v.y) <= ) continue;
ret ^= crossOp(p, u, v) > ;
}
return ret * ;
}
// 凸包
std::vector<P> convexHull(std::vector<P> ps) {
int n = ps.size(); if (n <= ) return ps;
std::sort(ps.begin(), ps.end());
std::vector<P> qs(n * ); int k = ;
for (int i = ; i < n; qs[k++] = ps[i++])
while (k > && crossOp(qs[k - ], qs[k - ], ps[i]) <= ) --k;
for (int i = n - , t = k; i >= ; qs[k++] = ps[i--])
while (k > t && crossOp(qs[k - ], qs[k - ], ps[i]) <= ) --k;
qs.resize(k - );
return qs;
}
std::vector<P> convexHullNonStrict(std::vector<P> ps) {
int n = ps.size(); if (n <= ) return ps;
std::sort(ps.begin(), ps.end());
std::vector<P> qs(n * ); int k = ;
for (int i = ; i < n; qs[k++] = ps[i++])
while (k > && crossOp(qs[k - ], qs[k - ], ps[i]) < ) --k;
for (int i = n - , t = k; i >= ; qs[k++] = ps[i--])
while (k > t && crossOp(qs[k - ], qs[k - ], ps[i]) < ) --k;
qs.resize(k - );
return qs;
}
// 点集直径
db convexDiameter(const std::vector<P> &ps) {
int n = ps.size(); if (n <= ) return ;
int is = , js = ;
for (int k = ; k < n; k++)
is = ps[k] < ps[is] ? k : is, js = ps[js] < ps[k] ? k : js;
int i = is, j = js;
db ret = ps[i].distTo(ps[j]);
do {
if ((ps[(i + ) % n] - ps[i]).det(ps[(j + ) % n] - ps[j]) >= )
(++j) %= n;
else
(++i) %= n;
ret = std::max(ret, ps[i].distTo(ps[j]));
} while (i != is || j != js);
return ret;
}
// convecCut
std::vector<P> convexCut(const std::vector<P> &ps, const P &q1, const P &q2) {
std::vector<P> qs;
int n = ps.size();
for (int i = ; i < n; i++) {
P p1 = ps[i], p2 = ps[(i + ) % n];
int d1 = crossOp(q1, q2, p1), d2 = crossOp(q1, q2, p2);
if (d1 >= ) qs.push_back(p1);
if (d1 * d2 < ) qs.push_back(isLL(p1, p2, q1, q2));
}
return qs;
}
// min_dis
db min_dis(const std::vector<P> &ps, int l, int r) {
if (r - l <= ) {
db ret = 1e100;
for (int i = l; i < r; i++)
for (int j = l; j < i; j++)
ret = std::min(ret, ps[i].distTo(ps[j]));
return ret;
}
int mid = l + r >> ;
db ret = std::min(min_dis(ps, l, mid), min_dis(ps, mid, r));
std::vector<P> qs;
for (int i = l; i < r; i++)
if (cmp(fabs(ps[i].x - ps[mid].x), ret) <= ) qs.push_back(ps[i]);
std::sort(qs.begin(), qs.end(), [](const P & a, const P & b) -> bool { return cmp(a.y, b.y) < ; });
for (int i = ; i < qs.size(); i++)
for (int j = i - ; j >= && cmp(qs[j].y, qs[i].y - ret) >= ; j--)
ret = std::min(ret, qs[j].distTo(qs[i]));
return ret;
}
// 圆的关系
int type(const P &o1, db r1, const P &o2, db r2) {
db d = o1.distTo(o2);
if (cmp(d, r1 + r2) == ) return ; // 相离
if (cmp(d, r1 + r2) == ) return ; // 外切
if (cmp(d, fabs(r1 - r2)) == ) return ; // 相交
if (cmp(d, fabs(r1 - r2)) == ) return ; // 内切
return ;
} bool parallel(L l0, L l1) {
return sign(l0.dir().det(l1.dir())) == ;
} bool sameDir(L l0, L l1) {
return parallel(l0, l1) && sign(l0.dir().dot(l1.dir())) == ;
} bool cmp(P a, P b) {
if (a.quad() != b.quad()) {
return a.quad() < b.quad();
} else {
return sign(a.det(b)) > ;
}
} bool operator < (L l0, L l1) {
if (sameDir(l0, l1)) {
return l1.include(l0[]);
} else {
return cmp(l0.dir(), l1.dir());
}
} bool check(L u, L v, L w) {
return w.include(isLL(u, v));
} const int N = 1e3 + ;
L que[N]; std::vector<L> halfPlaneIS(std::vector<L> &l) {
std::sort(l.begin(), l.end());
int head = , tail = ;
for (int i = ; i < l.size(); i++) {
if (i && sameDir(l[i], l[i - ])) continue;
while (tail - head > && !check(que[tail - ], que[tail - ], l[i])) tail--;
while (tail - head > && !check(que[head + ], que[head], l[i])) head++;
que[tail++] = l[i];
}
while (tail - head > && !check(que[tail - ], que[tail - ], que[])) tail--;
while (tail - head > && !check(que[], que[], que[tail - ])) head++;
std::vector<L> ans;
for (int i = head; i < tail; i++)
ans.push_back(que[i]);
return ans;
} db gety(P p, std::vector<P> point, std::vector<L> line) {
int n = point.size();
if (sign(p.x - point[].x) <= ) return isLL(line[], L(p, P(p.x, p.y + ))).y;
if (sign(p.x - point[n - ].x) >= ) return isLL(line[n], L(p, P(p.x, p.y + ))).y;
for (int i = ; i < n - ; i++) {
if (isMiddle(point[i].x, p.x, point[i + ].x))
return isLL(line[i + ], L(p, P(p.x, p.y + ))).y;
}
return 1e11;
} db getyy(P p, std::vector<P> point) {
for (int i = , sz = point.size(); i < sz - ; i++) {
if (isMiddle(point[i].x, p.x, point[i + ].x))
return isLL(point[i], point[i + ], p, P(p.x, p.y + )).y;
}
return 1e11;
} int main() {
int n;
scanf("%d", &n);
if (n <= ) {
puts("");
return ;
}
std::vector<P> p(n);
for (int i = ; i < n; i++)
scanf("%lf", &p[i].x);
for (int i = ; i < n; i++)
scanf("%lf", &p[i].y);
std::vector<L> l;
for (int i = ; i < n - ; i++)
l.push_back(L(p[i], p[i + ]));
std::vector<L> half = halfPlaneIS(l);
db ans = 1e10;
std::vector<P> ss;
for (int i = , sz = half.size(); i < sz - ; i++)
ss.push_back(isLL(half[i], half[i + ]));
for (int i = ; i < n; i++) {
ans = std::min(ans, std::fabs(gety(p[i], ss, half) - p[i].y));
}
for (int i = , sz = half.size(); i < sz - ; i++) {
P pp = ss[i];
ans = std::min(ans, std::fabs(getyy(pp, p) - pp.y));
}
printf("%.3f\n", ans);
return ;
}

1039. [ZJOI2008]无序运动Movement

平移、旋转、放缩对两个相似三角形没有影响,那么一个长度为 $n$ 的轨迹就可以描述为 $n-2$ 个三角形,每个三角形就用相邻两边长来描述,还得加上第二条线段在第一条线段的逆时针还是顺时针方向,因为如果不加这个就会出现翻不翻转带来的影响,然后就变成了字符串匹配了。不过由于字符集很大,得用 map 来存边。然后翻转一下再做一遍。
如果一个轨迹共线的话,翻转后他会被重新算一遍,所以要除以 $2$。
如果一个轨迹长度小于 $3$ 的话, 他就能匹配上所有长度相等的子串。

#include <bits/stdc++.h>

namespace IO {
char buf[ << ], buf2[ << ], a[], *p1 = buf, *p2 = buf, hh = '\n';
int p, p3 = -;
void read() {}
void print() {}
inline int getc() {
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, , << , stdin), p1 == p2) ? EOF : *p1++;
}
inline void flush() {
fwrite(buf2, , p3 + , stdout), p3 = -;
}
template <typename T, typename... T2>
inline void read(T &x, T2 &... oth) {
T f = ; x = ;
char ch = getc();
while (!isdigit(ch)) { if (ch == '-') f = -; ch = getc(); }
while (isdigit(ch)) { x = x * + ch - ; ch = getc(); }
x *= f;
read(oth...);
}
template <typename T, typename... T2>
inline void print(T x, T2... oth) {
if (p3 > << ) flush();
if (x < ) buf2[++p3] = , x = -x;
do {
a[++p] = x % + ;
} while (x /= );
do {
buf2[++p3] = a[p];
} while (--p);
buf2[++p3] = hh;
print(oth...);
}
} struct P {
int x, y;
void read() { IO::read(x, y); }
void print() { printf("%d %d\n", x, y); }
P() {}
P(int x, int y): x(x), y(y) {}
P operator + (const P &p) const { return P(x + p.x, y + p.y); }
P operator - (const P &p) const { return P(x - p.x, y - p.y); }
int det(const P &p) const { return x * p.y - y * p.x; }
int abs2() { return x * x + y * y; }
}; struct Node {
int a, b, c, dir;
bool operator < (const Node &p) const {
if (a != p.a) return a < p.a;
if (b != p.b) return b < p.b;
if (c != p.c) return c < p.c;
return dir < p.dir;
}
bool operator == (const Node &p) const {
return !(*this < p) && !(p < *this);
}
}; const int N = 2e5 + ;
int n, m, par[N], tol, ans[N];
std::vector<P> point[N], all;
std::vector<int> flag[N];
std::map<Node, int> mp[N]; int gcd(int a, int b) {
while (b) {
a %= b;
std::swap(a, b);
}
return a;
} Node getnode(const P &A, const P &B, const P &C) {
int lena = (B - A).abs2(), lenb = (B - C).abs2(), lenc = (A - C).abs2();
int g = gcd(lena, gcd(lenb, lenc));
lena /= g, lenb /= g, lenc /= g;
int crs = ;
if ((B - A).det(C - B) > ) crs = ;
else if ((B - A).det(C - B) < ) crs = -;
return {lena, lenb, lenc, crs};
} void done(std::vector<P> vec, int id) {
if (vec.size() < ) {
ans[id] = n - vec.size() + ;
return;
}
par[id] = ;
int rt = ;
for (int i = ; i < vec.size() - ; i++) {
Node o = getnode(vec[i], vec[i + ], vec[i + ]);
if (o.dir) par[id] = ;
auto it = mp[rt].find(o);
if (it == mp[rt].end()) {
mp[rt][o] = ++tol;
rt = tol;
} else {
rt = it->second;
}
}
flag[rt].push_back(id);
} int fail[N], last[N], cnt[N]; void build() {
std::queue<int> que;
for (auto it: mp[])
que.push(it.second);
while (!que.empty()) {
int u = que.front(); que.pop();
for (auto it: mp[u]) {
Node cur_node = it.first;
int f = fail[u], v = it.second;
for (; f && mp[f].find(cur_node) == mp[f].end(); f = fail[f]);
if (mp[f].find(cur_node) != mp[f].end())
f = mp[f][cur_node];
fail[v] = f;
last[v] = flag[fail[v]].empty() ? last[fail[v]] : fail[v];
que.push(v);
}
}
} void solve(const std::vector<P> &vec) {
int rt = ;
for (int i = ; i < n - ; i++) {
Node node = getnode(vec[i], vec[i + ], vec[i + ]);
for (; rt && mp[rt].find(node) == mp[rt].end(); rt = fail[rt]);
if (mp[rt].find(node) != mp[rt].end())
rt = mp[rt][node];
for (int j = rt; j; j = last[j])
++cnt[j];
}
} int main() {
IO::read(n, m);
for (int i = ; i <= m; i++) {
int k;
IO::read(k);
point[i].resize(k);
for (int j = ; j < k; j++)
point[i][j].read();
done(point[i], i);
}
build();
all.resize(n);
for (int i = ; i < n; i++)
all[i].read();
solve(all);
for (int i = ; i < n; i++)
all[i].y *= -;
solve(all);
for (int i = ; i <= tol; i++)
for (int u: flag[i])
ans[u] += cnt[i] / (par[u] + );
for (int i = ; i <= m; i++)
IO::print(ans[i]);
IO::flush();
return ;
}

1040. [ZJOI2008]骑士

基环森林上DP。
我刚开始想的就是找到环,然后把环上每个点及它的子树缩成一个点,就变成一个环上的DP了。然后就是强制第一个不取和强制最后一个不取。
看了别人的题解发现可以不用那么麻烦,只要找到环上任意相邻的两点,强制把这条边断开,然后还是DP两次就行了。
DP方程就比较naive了。

#include <bits/stdc++.h>

namespace IO {
char buf[ << ], buf2[ << ], a[], *p1 = buf, *p2 = buf, hh = '\n';
int p, p3 = -;
void read() {}
void print() {}
inline int getc() {
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, , << , stdin), p1 == p2) ? EOF : *p1++;
}
inline void flush() {
fwrite(buf2, , p3 + , stdout), p3 = -;
}
template <typename T, typename... T2>
inline void read(T &x, T2 &... oth) {
T f = ; x = ;
char ch = getc();
while (!isdigit(ch)) { if (ch == '-') f = -; ch = getc(); }
while (isdigit(ch)) { x = x * + ch - ; ch = getc(); }
x *= f;
read(oth...);
}
template <typename T, typename... T2>
inline void print(T x, T2... oth) {
if (p3 > << ) flush();
if (x < ) buf2[++p3] = , x = -x;
do {
a[++p] = x % + ;
} while (x /= );
do {
buf2[++p3] = a[p];
} while (--p);
buf2[++p3] = hh;
print(oth...);
}
} #define ll long long
const int N = 1e6 + ;
int n, fa[N], root, _root;
ll dp[N][], val[N];
bool vis[N];
int head[N];
struct E {
int v, ne;
} e[N << ];
int cnt = , ban; void add(int u, int v) {
e[++cnt].v = v; e[cnt].ne = head[u]; head[u] = cnt;
} void dfs(int u, int f) {
vis[u] = ;
for (int i = head[u]; i; i = e[i].ne) {
int v = e[i].v;
if (v == f) continue;
if (vis[v]) {
root = u, _root = v;
ban = i;
} else {
dfs(v, u);
}
}
} void DP(int u, int f) {
dp[u][] = ; dp[u][] = val[u];
for (int i = head[u]; i; i = e[i].ne) {
int v = e[i].v;
if (v == f || i == ban || i == (ban ^ )) continue;
DP(v, u);
dp[u][] += std::max(dp[v][], dp[v][]);
dp[u][] += dp[v][];
}
} int main() {
IO::read(n);
for (int i = , u; i <= n; i++)
IO::read(val[i], u), add(u, i), add(i, u);
ll ans = ;
for (int i = ; i <= n; i++) if (!vis[i]) {
dfs(i, -);
DP(root, -);
ll temp = dp[root][];
DP(_root, -);
temp = std::max(temp, dp[_root][]);
ans += temp;
}
printf("%lld\n", ans);
return ;
}

1041. [HAOI2008]圆上的整点

$x^2+y^2=r^2$
$x^2=(r-y)(r+y)$
$(\frac{x}{g})^2=\frac{r-y}{g}\frac{r+y}{g}$
其中 $g = \gcd(r-y,r+y)$
那么 $\frac{r-y}{g}$ 和 $\frac{r+y}{g}$ 互质
并且左边 $(\frac{x}{g})^2$ 是完全平方数,那么右边 $\frac{r-y}{g}$ 和 $\frac{r+y}{g}$ 也必须都是完全平方数
设 $a^2=\frac{r-y}{g}$,$b^2=\frac{r+y}{g}$
$a^2+b^2=\frac{2r}{g}$
枚举 $g$ 之后再枚举 $a$,可以做到复杂度 $O(n^{\frac{3}{4}})$

#include <bits/stdc++.h>

#define ll long long

template<class T>
T gcd(T a, T b) {
while (b) {
a %= b;
std::swap(a, b);
}
return a;
} int main() {
ll r;
scanf("%lld", &r);
int ans = ;
for (ll g = ; g * g <= * r; g++) {
if (( * r) % g) continue;
for (ll a = ; a * a < g / ; a++) {
ll b = sqrt(g - a * a + 0.5);
if (a * a + b * b == g && gcd(a, b) == ) ans++;
}
for (ll a = ; a * a < r / g; a++) {
ll b = sqrt( * r / g - a * a + 0.5);
if (a * a + b * b == * r / g && gcd(a, b) == ) ans++;
}
}
printf("%d\n", ans * + );
return ;
}

1042. [HAOI2008]硬币购物

涨芝士了!
可以先做一次完全背包,然后对每次询问容斥得到答案。
限制用 $d_i$ 个 $c_i$ 硬币,就相当于减去至少用了 $d_i+1$ 个 $c_i$ 硬币,那么就是假设我强制花了 $(d_i+1)*c_i$ 块钱,然后方案就是 $f[s-(d_i+1)*c_i]$。容斥就暴力枚举 $2^4$ 钟情况即可。

#include <bits/stdc++.h>
#define ll long long const int N = 1e5 + ;
ll dp[N];
int w[], T, d[]; int get(int x) {
int cnt = ;
while (x) {
cnt++;
x &= (x - );
}
return cnt;
} int main() {
for (int i = ; i < ; i++)
scanf("%d", w + i);
scanf("%d", &T);
dp[] = ;
for (int i = ; i < ; i++)
for (int j = w[i]; j <= N - ; j++)
dp[j] += dp[j - w[i]];
while (T--) {
for (int i = ; i < ; i++)
scanf("%d", d + i);
int s;
scanf("%d", &s);
ll ans = ;
for (int i = ; i < ; i++) {
int sz = get(i), sum = s;
int c = sz & ? - : ;
for (int j = ; j < ; j++)
if (i >> j & )
sum -= w[j] * (d[j] + );
if (sum >= )
ans += c * dp[sum];
}
printf("%lld\n", ans);
}
return ;
}

1044. [HAOI2008]木棍分割

第一问二分傻逼题。

第二问前缀和优化一下DP

#include <bits/stdc++.h>

const int N = 5e4 + ;
const int MOD = ;
int n, m, a[N], dp[N][], sum[N];
int dp_sum[N][]; void M(int &x) {
if (x >= MOD) x -= MOD;
if (x < ) x += MOD;
} inline bool chkmax(int &a, int b) {
return a < b ? a = b, : ;
} inline bool chkmin(int &a, int b) {
return a > b ? a = b, : ;
} bool check(int mid) {
int cnt = , sum = ;
for (int i = ; i <= n; i++) {
if (sum + a[i] > mid) {
cnt++;
sum = a[i];
} else {
sum += a[i];
}
}
return cnt <= m;
} int que[N]; int main() {
scanf("%d%d", &n, &m);
m++;
int l = , r = ;
for (int i = ; i <= n; i++)
scanf("%d", a + i), chkmax(l, a[i]), r += a[i], sum[i] = sum[i - ] + a[i];
int ans = ;
while (l <= r) {
int mid = l + r >> ;
if (check(mid)) ans = mid, r = mid - ;
else l = mid + ;
}
for (int i = ; i <= n; i++)
dp[i][] = (sum[i] <= ans ? : ), M(dp_sum[i][] = dp_sum[i - ][] + dp[i][]);
int ans2 = dp[n][];
for (int j = ; j <= m; j++) {
int cur = j & ;
int last = ;
for (int i = ; i <= n; i++) {
dp[i][cur] = ;
while (i > last && sum[i] - sum[last] > ans) last++;
M(dp[i][cur] += dp_sum[i - ][cur ^ ] - dp_sum[std::max(last - , )][cur ^ ]);
M(dp_sum[i][cur] = dp_sum[i - ][cur] + dp[i][cur]);
} M(ans2 += dp[n][cur]);
}
printf("%d %d\n", ans, ans2);
return ;
}

1045. [HAOI2008] 糖果传递

设 $x_i$ 表示第 $i$ 个人向第 $i+1$ 个人传的糖果数。
那么
$$\begin{cases}
x_n - x_1 = \overline{a}-a_1\\
x_1 - x_2 = \overline{a}-a_2\\
x_2 - x_3 = \overline{a}-a_3\\
\dots
\end{cases}
$$
再做一下前缀和,能得到
$x_n-x_i=\sum_{j=1}^i(\overline{a}-a_i)$
即 $x_i = x_n - \sum_{j=1}^i(\overline{a}-a_i)$
答案就是 $\sum_{i=1}^n |x_i|$
就是数轴上有 $n$ 个点,要找一个点到他们的距离之和最小,大概就是中位数吧,因为中位数往左往右移增加的距离会比少的距离多一部分,举个例子就知道了。

#include <bits/stdc++.h>
typedef long long ll; const int N = 1e6 + ;
ll a[N]; int main() {
int n;
scanf("%d", &n);
ll sum = ;
for (int i = ; i <= n; i++)
scanf("%lld", a + i), sum += a[i];
sum /= n;
for (int i = ; i <= n; i++)
a[i] = a[i - ] + sum - a[i];
std::sort(a + , a + + n);
sum = a[(n + ) / ];
ll ans = ;
for (int i = ; i <= n; i++)
ans += std::abs(a[i] - sum);
printf("%lld\n", ans);
return ;
}

1046. [HAOI2007]上升序列

先求出 $dp[i]$ 表示以 $i$ 开头的最长下降子序列。

然后因为是 $x$ 序列字典序最小,那就从左往右填就行了。

#include <bits/stdc++.h>

const int N = 1e4 + ;

int cnt, a[N], v[N], n;
int dp[N]; struct Bit {
int tree[N];
inline int lowbit(int x) {
return x & -x;
}
void add(int x, int v) {
for (int i = x; i <= n; i += lowbit(i))
tree[i] = std::max(tree[i], v);
}
int query(int x) {
int ans = ;
for (int i = x; i; i -= lowbit(i))
ans = std::max(ans, tree[i]);
return ans;
}
} bit; int main() {
scanf("%d", &n);
for (int i = ; i <= n; i++)
scanf("%d", a + i), v[i] = a[i];
std::sort(v + , v + + n);
cnt = std::unique(v + , v + + n) - v - ;
int ans = ;
for (int i = n; i >= ; i--) {
a[i] = std::lower_bound(v + , v + + cnt, a[i]) - v;
a[i] = cnt - a[i] + ;
dp[i] = bit.query(a[i] - ) + ;
bit.add(a[i], dp[i]);
ans = std::max(ans, dp[i]);
a[i] = cnt - a[i] + ;
}
int m;
scanf("%d", &m);
for (int i = ; i <= m; i++) {
int len;
scanf("%d", &len);
if (len > ans) {
puts("Impossible");
continue;
}
int last = ;
for (int i = ; i <= n; i++) {
if (!len) break;
if (dp[i] >= len && a[i] > last) {
printf("%d%c", v[a[i]], " \n"[len == ]);
len--;
last = a[i];
}
}
}
return ;
}

1047. [HAOI2007]理想的正方形

先对每一行做一遍单调队列,然后再对每一列做一遍单调队列即可。

#include <bits/stdc++.h>

namespace IO {
char buf[ << ], buf2[ << ], a[], *p1 = buf, *p2 = buf, hh = '\n';
int p, p3 = -;
void read() {}
void print() {}
inline int getc() {
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, , << , stdin), p1 == p2) ? EOF : *p1++;
}
inline void flush() {
fwrite(buf2, , p3 + , stdout), p3 = -;
}
template <typename T, typename... T2>
inline void read(T &x, T2 &... oth) {
T f = ; x = ;
char ch = getc();
while (!isdigit(ch)) { if (ch == '-') f = -; ch = getc(); }
while (isdigit(ch)) { x = x * + ch - ; ch = getc(); }
x *= f;
read(oth...);
}
template <typename T, typename... T2>
inline void print(T x, T2... oth) {
if (p3 > << ) flush();
if (x < ) buf2[++p3] = , x = -x;
do {
a[++p] = x % + ;
} while (x /= );
do {
buf2[++p3] = a[p];
} while (--p);
buf2[++p3] = hh;
print(oth...);
}
} const int N = ;
int A[N][N], a, b, n, mx[N][N], mn[N][N]; int main() {
IO::read(a, b, n);
for (int i = ; i <= a; i++)
for (int j = ; j <= b; j++)
IO::read(A[i][j]);
static int que1[N], que2[N];
for (int i = ; i <= a; i++) {
int l1 = , r1 = , l2 = , r2 = ;
for (int j = ; j <= b; j++) {
{
while (l1 < r1 && que1[l1] <= j - n) l1++;
while (l1 < r1 && A[i][que1[r1 - ]] >= A[i][j]) r1--;
que1[r1++] = j;
if (j >= n) mn[i][j] = A[i][que1[l1]];
}
{
while (l2 < r2 && que2[l2] <= j - n) l2++;
while (l2 < r2 && A[i][que2[r2 - ]] <= A[i][j]) r2--;
que2[r2++] = j;
if (j >= n) mx[i][j] = A[i][que2[l2]];
}
}
}
int ans = 2e9;
for (int j = n; j <= b; j++) {
int l1 = , r1 = , l2 = , r2 = ;
for (int i = ; i <= a; i++) {
{
while (l1 < r1 && que1[l1] <= i - n) l1++;
while (l1 < r1 && mn[que1[r1 - ]][j] >= mn[i][j]) r1--;
que1[r1++] = i;
}
{
while (l2 < r2 && que2[l2] <= i - n) l2++;
while (l2 < r2 && mx[que2[r2 - ]][j] <= mx[i][j]) r2--;
que2[r2++] = i;
}
if (i >= n) ans = std::min(ans, mx[que2[l2]][j] - mn[que1[l1]][j]);
}
}
printf("%d\n", ans);
return ;
}

1048. [HAOI2007]分割矩阵

记忆化着搜就行了,$f[x_1][y_1][x_2][y_2][c]$ 表示矩阵 $(x_1,y_1)$,$(x_2,y_2)$ 分成 $c$ 份的最小方差和。

#include <bits/stdc++.h>

typedef double db;

const db eps = 1e-;
const int N = ;
db f[N][N][N][N][N];
int sum[N][N];
db ave;
int A[N][N]; int sign(db x) {
if (fabs(x) < eps) return ;
return x > ? : -;
} db sqr(db x) {
return x * x;
} db Sum(int x1, int x2, int y1, int y2) {
return sum[x2][y2] + sum[x1 - ][y1 - ] - sum[x1 - ][y2] - sum[x2][y1 - ];
} db solve(int x1, int x2, int y1, int y2, int c) {
db &ans = f[x1][x2][y1][y2][c];
if (ans != -) return ans;
if (c == ) return ans = sqr(Sum(x1, x2, y1, y2) - ave);
ans = 1e9;
for (int i = x1; i < x2; i++)
for (int cc = ; cc < c; cc++)
ans = std::min(ans, solve(x1, i, y1, y2, cc) + solve(i + , x2, y1, y2, c - cc));
for (int i = y1; i < y2; i++)
for (int cc = ; cc < c; cc++)
ans = std::min(ans, solve(x1, x2, y1, i, cc) + solve(x1, x2, i + , y2, c - cc));
return ans;
} int n, m, c; int main() {
scanf("%d%d%d", &n, &m, &c);
for (int i = ; i <= n; i++)
for (int j = ; j <= n; j++)
for (int k = ; k <= m; k++)
for (int l = ; l <= m; l++)
for (int o = ; o <= c; o++)
f[i][j][k][l][o] = -;
for (int i = ; i <= n; i++)
for (int j = ; j <= m; j++)
scanf("%d", &A[i][j]), sum[i][j] = sum[i - ][j] + sum[i][j - ] - sum[i - ][j - ] + A[i][j];
ave = 1.0 * sum[n][m] / c;
printf("%.2f\n", sqrt(solve(, n, , m, c) / c));
return ;
}

1049. [HAOI2006]数字序列

第一问套路,每一个数字减去它的下标就是求最长不下降子序列了。

第二问,$g[i]$ 表示 $1$ 到 $i$ 都已经符合条件的最小花费

那么可以列出方程 $g[i]=\min \{g[j]+cost(j+1,i)\},dp[j]+1=dp[i]$

cost 有个结论就是存在一个分割点 $k$,左边的值都变成 $b[j]$,右边的值都变成 $b[i]$ 最优,然后暴力求就行了,因为数据随机

#include <bits/stdc++.h>

typedef long long ll;
const int N = 4e4 + ;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int inf = 0x3f3f3f3f; template<class T>
inline bool chkmax(T &a, T b) {
return a < b ? a = b, : ;
} template<class T>
inline bool chkmin(T &a, T b) {
return a > b ? a = b, : ;
} int n, a[N], dp[N], v[N], cnt, b[N];
std::vector<int> vec[N];
ll g[N], s1[N], s2[N]; struct BIT {
int tree[N];
inline int lowbit(int x) {
return x & -x;
}
void add(int x, int v) {
for (int i = x; i <= cnt; i += lowbit(i))
chkmax(tree[i], v);
}
int query(int x) {
int ans = ;
for (int i = x; i; i -= lowbit(i))
chkmax(ans, tree[i]);
return ans;
}
} bit; int main() {
scanf("%d", &n);
for (int i = ; i <= n; i++)
scanf("%d", a + i), b[i] = a[i] - i, v[i] = b[i];
a[++n] = inf, b[n] = a[n] - n, v[n] = b[n];
std::sort(v + , v + + n);
cnt = std::unique(v + , v + + n) - v - ;
int ans = ;
for (int i = ; i <= n; i++) {
b[i] = std::lower_bound(v + , v + + cnt, b[i]) - v;
dp[i] = bit.query(b[i]) + ;
bit.add(b[i], dp[i]);
chkmax(ans, dp[i]);
}
ans = n - ans;
printf("%d\n", ans);
for (int i = ; i <= n; i++) {
vec[dp[i]].push_back(i);
b[i] = v[b[i]];
g[i] = INF;
}
g[] = ; b[] = -inf;
for (int i = ; i <= n; i++)
for (int j = , sz = vec[dp[i] - ].size(); j < sz; j++) {
int pos = vec[dp[i] - ][j];
if (pos > i) break;
if (b[pos] > b[i]) continue;
for (int k = pos; k <= i; k++)
s1[k] = std::abs(b[k] - b[pos]), s2[k] = std::abs(b[i] - b[k]);
for (int k = pos + ; k <= i; k++)
s1[k] += s1[k - ], s2[k] += s2[k - ];
for (int k = pos; k <= i; k++)
chkmin(g[i], g[pos] + s1[k] - s1[pos] + s2[i] - s2[k]);
}
printf("%lld\n", g[n]);
return ;
}

1188. [HNOI2007]分裂游戏

每次操作相当于在一个位置取一个豆子,放到后面的两个位置上,即 $i$ -> $(j, k)$。那么每个豆子只跟位置有关,而与它所在的那一堆有多少豆子无关。游戏的和就是所有豆子的 SG 函数的异或和。

那么一个位置有奇数个豆子时对游戏的和有贡献,理解起来就是如果一个位置有两个豆子的话,第二个人可以模仿第一个人的动作。

SG 函数可以倒着求出来,然后枚举第一步即可。

这里不是一堆石子是单个游戏,而是每个石子都是单个游戏。

#include <bits/stdc++.h>

const int N = ;
int a[N], SG[N];
bool vis[N]; int main() {
int T;
scanf("%d", &T);
while (T--) {
int n;
scanf("%d", &n);
for (int i = ; i <= n; i++)
scanf("%d", a + i), SG[i] = ;
for (int i = n - ; i; i--) {
memset(vis, , sizeof(vis));
for (int j = i + ; j <= n; j++)
for (int k = j; k <= n; k++)
vis[SG[j] ^ SG[k]] = ;
for (int j = ; ; j++)
if (!vis[j]) {
SG[i] = j;
break;
}
}
int A = , B = , C = , cnt = ;
int ans = ;
for (int i = ; i <= n; i++)
if (a[i] & )
ans ^= SG[i];
for (int i = ; i <= n; i++) if (a[i])
for (int j = i + ; j <= n; j++)
for (int k = j; k <= n; k++)
if (!(ans ^ SG[i] ^ SG[j] ^ SG[k])) {
if (!cnt) A = i, B = j, C = k;
cnt++;
}
printf("%d %d %d\n%d\n", A - , B - , C - , cnt);
}
return ;
}

1791. [Ioi2008]Island 岛屿

求基环森林的直径之和。
这个刚好是个基环内向树,那么可以拓扑排序做,之后再拆环,单调队列优化一下DP。

#include <bits/stdc++.h>
#define ll long long namespace IO {
void read() {}
template<class T, class ... T2>
inline void read(T &x, T2 &... oth) {
x = ; T f = ; char ch = getchar();
while (!isdigit(ch)) { if (ch == '-') f = -; ch = getchar(); }
while (isdigit(ch)) x = x * + ch - '', ch = getchar();
x *= f;
read(oth...);
}
} const int N = 1e6 + ;
int to[N], w[N], n, in[N];
ll f[N], dp[N], d[N];
bool vis[N]; struct Deque {
static const int LEN = 1e6;
int l, r, que[LEN];
Deque() { l = r = ; }
void clear() { l = r = ; }
bool empty() { return !(l ^ r); }
void push_back(int v) { que[r++] = v; r %= LEN; }
void push_front(int v) { que[l = (l - + LEN) % LEN] = v; }
void pop_front() { ++l; l %= LEN; }
void pop_back() { r = (r - + LEN) % LEN; }
int front() { return que[l]; }
int back() { return que[r - ]; }
} que; template<class T>
inline bool chkmax(T &a, T b) {
return a < b ? a = b, : ;
} int cur[N * ];
ll s[N * ]; int main() {
IO::read(n);
for (int i = ; i <= n; i++) {
IO::read(to[i], w[i]);
in[to[i]]++;
}
for (int i = ; i <= n; i++)
if (!in[i]) que.push_back(i);
while (!que.empty()) {
int u = que.front(); que.pop_front();
int v = to[u];
if (!--in[v]) que.push_back(v);
// 经过 v 的最长路径
chkmax(f[v], d[u] + d[v] + w[u]);
// 从 v 出发的最长路径
chkmax(d[v], d[u] + w[u]);
// v 的子树中最长路径
chkmax(dp[v], std::max(dp[u], f[v]));
}
ll ans = ;
for (int i = ; i <= n; i++) if (in[i] && !vis[i]) {
int cnt = ;
ll temp = ;
for (int j = i; !vis[j]; j = to[j]) {
cur[++cnt] = j;
vis[j] = ;
chkmax(temp, dp[j]);
}
int dcnt = cnt << ;
for (int i = cnt + ; i <= dcnt; i++)
cur[i] = cur[i - cnt];
que.clear();
for (int j = ; j <= dcnt; j++) {
s[j] = s[j - ] + w[cur[j - ]];
while (!que.empty() && j - que.front() >= cnt) que.pop_front();
if (!que.empty())
chkmax(temp, d[cur[j]] + d[cur[que.front()]] + s[j] - s[que.front()]);
while (!que.empty() && d[cur[j]] - s[j] > d[cur[que.back()]] - s[que.back()])
que.pop_back();
que.push_back(j);
}
ans += temp;
}
printf("%lld\n", ans);
return ;
}

2038. [2009国家集训队]小Z的袜子(hose)

莫队板子题。

#include <bits/stdc++.h>

const int N = 5e4 + ;

int n, m, B, ans[N][], fz, fm;
int cnt[N], len, col[N]; struct Q {
int l, r, id;
bool operator < (const Q &p) const {
return l / B == p.l / B ? r < p.r : l < p.l;
}
} q[N]; void add(int x) {
fz += cnt[x];
cnt[x]++;
fm += len;
len++;
} void del(int x) {
cnt[x]--;
fz -= cnt[x];
len--;
fm -= len;
} int main() {
scanf("%d%d", &n, &m);
for (int i = ; i <= n; i++)
scanf("%d", col + i);
for (int i = ; i <= m; i++)
scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
B = sqrt(n);
std::sort(q + , q + + m);
int l = , r = ;
for (int i = ; i <= m; i++) {
while (l > q[i].l) add(col[--l]);
while (l < q[i].l) del(col[l++]);
while (r > q[i].r) del(col[r--]);
while (r < q[i].r) add(col[++r]);
if (fm == ) {
ans[q[i].id][] = ans[q[i].id][] = ;
continue;
}
int g = std::__gcd(fz, fm);
ans[q[i].id][] = fz / g;
ans[q[i].id][] = fm / g;
}
for (int i = ; i <= m; i++)
printf("%d/%d\n", ans[i][], ans[i][]);
return ;
}

2120. 数颜色

带修莫队板子题。

#include <bits/stdc++.h>

const int N = 1e4 + ;
int cnt[N * ], ans, B, a[N];
int n, m; struct Q {
int l, r, t, id;
bool operator < (const Q &p) const {
if (l / B != p.l / B) return l < p.l;
if (r / B != p.r / B) return r < p.r;
return t < p.t;
}
} q[N]; struct C {
int p, col;
} change[N]; void add(int x) {
++cnt[x];
if (cnt[x] == )
ans++;
} void del(int x) {
--cnt[x];
if (cnt[x] == )
ans--;
} void update(int cur_q, int cur_c) {
if (change[cur_c].p >= q[cur_q].l && change[cur_c].p <= q[cur_q].r)
del(a[change[cur_c].p]), add(change[cur_c].col);
std::swap(a[change[cur_c].p], change[cur_c].col);
} int res[N]; int main() {
char s[];
scanf("%d%d", &n, &m);
B = n / sqrt(m * / );
for (int i = ; i <= n; i++)
scanf("%d", a + i);
int qcnt = , ccnt = ;
for (int i = ; i <= m; i++) {
scanf("%s", s);
if (s[] == 'Q') {
++qcnt;
scanf("%d%d", &q[qcnt].l, &q[qcnt].r);
q[qcnt].t = ccnt;
q[qcnt].id = qcnt;
} else {
++ccnt;
scanf("%d%d", &change[ccnt].p, &change[ccnt].col);
}
}
int l = , r = , cur = ;
std::sort(q + , q + + qcnt);
for (int i = ; i <= qcnt; i++) {
while (l < q[i].l) del(a[l++]);
while (l > q[i].l) add(a[--l]);
while (r < q[i].r) add(a[++r]);
while (r > q[i].r) del(a[r--]);
while (cur < q[i].t) update(i, ++cur);
while (cur > q[i].t) update(i, cur--);
res[q[i].id] = ans;
}
for (int i = ; i <= qcnt; i++)
printf("%d\n", res[i]);
}

2154. Crash的数字表格

$$\sum_{i = 1}^n \sum_{j= 1}^m lcm(i, j)=\sum_{i=1}^n\sum_{j=1}^m \dfrac{i*j}{(i,j)}$$
$$=\sum_{i=1}^n\sum_{j=1}^m \sum_d \dfrac{ij}{d}[(\frac{i}{d}, \frac{j}{d})==1]=\sum_{d}d\sum_{i=1}^{\lfloor \frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor \frac{m}{d}\rfloor}ij[(i,j)==1]$$
$$=\sum_d d\sum_{i=1}^{\lfloor \frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor \frac{m}{d}\rfloor}ij \sum_{p|(i,j)}\mu(p)=\sum_d d\sum_p p^2\mu(p)\sum_{i=1}^{\lfloor \frac{n}{dp}\rfloor}i\sum_{j=1}^{\lfloor \frac{m}{dp}\rfloor}j$$
$$=\sum_dd\sum_pp^2\mu(p)(\dfrac{(\lfloor \frac{n}{dp} \rfloor + 1)\lfloor \frac{n}{dp} \rfloor}{2})(\dfrac{(\lfloor \frac{m}{dp} \rfloor + 1)\lfloor \frac{m}{dp} \rfloor}{2})$$
$$=\sum_T(\dfrac{(\lfloor \frac{n}{T} \rfloor + 1)\lfloor \frac{n}{T} \rfloor}{2})(\dfrac{(\lfloor \frac{m}{T} \rfloor + 1)\lfloor \frac{m}{T} \rfloor}{2})\sum_{p|T}\dfrac{T}{p}p^2\mu(p)$$
$$=\sum_T(\dfrac{(\lfloor \frac{n}{T} \rfloor + 1)\lfloor \frac{n}{T} \rfloor}{2})(\dfrac{(\lfloor \frac{m}{T} \rfloor + 1)\lfloor \frac{m}{T} \rfloor}{2})\sum_{p|T}Tp\mu(p)$$
需要预处理 $\sum_{d|n}nd\mu(d)$,只需要预处理 $f(n)=\sum_{d|n}d\mu(d)$,$f(n)$ 为积性函数,且 $f(p^t) = 1 - p$。
所以这道题可以搞成多组询问的。
复杂度为 $O(n+q\sqrt n)$

#include <bits/stdc++.h>

const int MOD = ;
const int N = 1e7 + ;
int prime[N], prin;
int f[N];
bool vis[N]; void M(int &x) {
if (x >= MOD) x -= MOD;
if (x < ) x += MOD;
} void init(int N) {
f[] = ;
for (int i = ; i < N; i++) {
if (!vis[i]) {
prime[++prin] = i;
M(f[i] = - i);
}
for (int j = ; j <= prin && i * prime[j] < N; j++) {
vis[i * prime[j]] = ;
if (i % prime[j] == ) {
int temp = i;
while (temp % prime[j] == ) temp /= prime[j];
f[i * prime[j]] = 1LL * ( - prime[j] + MOD) * f[temp] % MOD;
break;
}
f[i * prime[j]] = 1LL * f[i] * f[prime[j]] % MOD;
}
}
for (int i = ; i < N; i++)
M(f[i] = (f[i - ] + 1LL * f[i] * i % MOD));
} int cal(int n, int m) {
int x = 1LL * n * (n + ) / % MOD;
int y = 1LL * m * (m + ) / % MOD;
return 1LL * x * y % MOD;
} int solve(int n, int m) {
if (n > m) std::swap(n, m);
int ans = ;
for (int i = , j; i <= n; i = j + ) {
j = std::min(n / (n / i), m / (m / i));
M(ans += 1LL * cal(n / i, m / i) * (f[j] - f[i - ] + MOD) % MOD);
}
return ans;
} signed main() {
int n, m;
scanf("%d%d", &n, &m);
init(std::min(n, m) + );
printf("%d\n", solve(n, m));
return ;
}

2179. FFT快速傅立叶

FFT板子

#include <bits/stdc++.h>

struct Complex {
double r, i;
Complex(double r = 0.0, double i = 0.0): r(r), i(i) {}
Complex operator + (const Complex &p) const { return Complex(r + p.r, i + p.i); }
Complex operator - (const Complex &p) const { return Complex(r - p.r, i - p.i); }
Complex operator * (const Complex &p) const { return Complex(r * p.r - i * p.i, r * p.i + i * p.r); }
}; const double pi = acos(-1.0);
const int N = 2e5 + ;
int n, limit, r[N], l;
int v[N], A[N], B[N], C[N];
Complex a[N], b[N], c[N]; void FFT(Complex *a, int pd) {
for (int i = ; i < limit; i++)
if (i < r[i])
std::swap(a[i], a[r[i]]);
for (int mid = ; mid < limit; mid <<= ) {
Complex wn = Complex(cos(pi / mid), pd * sin(pi / mid));
for (int l = mid << , j = ; j < limit; j += l) {
Complex w = Complex(1.0, 0.0);
for (int k = ; k < mid; k++, w = w * wn) {
Complex u = a[k + j], v = w * a[k + j + mid];
a[k + j] = u + v;
a[k + j + mid] = u - v;
}
}
}
if (pd == -)
for (int i = ; i < limit; i++)
a[i] = Complex(a[i].r / limit, a[i].i / limit);
} int main() {
scanf("%d", &n);
for (int i = ; i < n; i++) {
int x;
scanf("%d", &x);
x += ;
A[x]++;
B[x * ]++;
C[x * ]++;
}
for (int i = ; i <= ; i++)
a[i] = Complex((double)A[i], 0.0);
for (int i = ; i <= ; i++)
b[i] = Complex((double)B[i], 0.0);
limit = ;
while (limit <= + )
limit <<= , l++;
for (int i = ; i < limit; i++)
r[i] = r[i >> ] >> | ((i & ) << (l - ));
FFT(a, );
FFT(b, );
for (int i = ; i < limit; i++)
b[i] = b[i] * a[i];
for (int i = ; i < limit; i++)
a[i] = a[i] * a[i] * a[i];
FFT(a, -);
FFT(b, -);
for (int i = ; i <= ; i++) {
long long ans = (long long)((a[i].r - 3.0 * b[i].r + 2.0 * C[i]) / 6.0 + 0.5);
if (ans > )
printf("%d : %lld\n", i - , ans);
}
return ;
}

2238. Mst

先求出 Mst,如果要删的边不在 Mst 上,那么对答案不影响。

如果删的边 $(u, v)$ 在 Mst 上,相当于把树分成以 $u$ 为根和以 $v$ 为根两棵树,现在就要在两棵树里挑出一条权值最小的非树边。

预处理每一条非树边,它们之间的路径的边的值取个min,那么就树剖一下,建线段树,区间里面比这个大的数都变成这个数。

我以为要吉司机线段树,但是查询是单点查询,标记永久化即可。

#include <bits/stdc++.h>

const int N = 1e5 + ;
inline bool chkmax(int &a, int b) { return a < b ? a = b, : ; }
inline bool chkmin(int &a, int b) { return a > b ? a = b, : ; } struct Edge {
int u, v, w, id;
bool operator < (const Edge &p) const { return this->w < p.w; }
} edge[N], sort_edge[N]; int n, m, sz[N], son[N], fa[N], top[N];
int dep[N], dfn[N], tol;
bool vis[N];
std::vector<int> vec[N]; void dfs1(int u, int f) {
fa[u] = f;
sz[u] = ;
dep[u] = dep[f] + ;
for (int v: vec[u]) {
if (v == f) continue;
dfs1(v, u);
sz[u] += sz[v];
if (sz[son[u]] < sz[v]) son[u] = v;
}
} void dfs2(int u, int tp) {
top[u] = tp;
dfn[u] = ++tol;
if (!son[u]) return;
dfs2(son[u], tp);
for (int v: vec[u])
if (v != fa[u] && v != son[u])
dfs2(v, v);
} const int INF = 0x3f3f3f3f; struct Seg {
#define lp p << 1
#define rp p << 1 | 1
int tree[N << ];
void build(int p, int l, int r) {
tree[p] = INF;
if (l == r) return;
int mid = l + r >> ;
build(lp, l, mid);
build(rp, mid + , r);
}
void update(int p, int l, int r, int x, int y, int z) {
if (x <= l && y >= r) {
chkmin(tree[p], z);
return;
}
int mid = l + r >> ;
if (x <= mid) update(lp, l, mid, x, y, z);
if (y > mid) update(rp, mid + , r, x, y, z);
}
int query(int p, int l, int r, int pos) {
if (l == r) return tree[p];
int mid = l + r >> ;
if (pos <= mid) return std::min(tree[p], query(lp, l, mid, pos));
return std::min(tree[p], query(rp, mid + , r, pos));
}
} seg; void solve(int u, int v, int w) {
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) std::swap(u, v);
seg.update(, , n, dfn[top[u]], dfn[u], w);
u = fa[top[u]];
}
if (dep[u] > dep[v]) std::swap(u, v);
seg.update(, , n, dfn[u] + , dfn[v], w);
} struct DSU {
int fa[N];
void init(int n) { for (int i = ; i <= n; i++) fa[i] = i; }
int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
bool merge(int u, int v) {
u = find(u), v = find(v);
if (u == v) return ;
fa[u] = v;
return ;
}
} dsu; int main() {
scanf("%d%d", &n, &m);
for (int i = ; i <= m; i++) {
scanf("%d%d%d", &edge[i].u, &edge[i].v, &edge[i].w);
edge[i].id = i;
sort_edge[i] = edge[i];
}
dsu.init(n);
std::sort(sort_edge + , sort_edge + + m);
int ed = , ans = ;
for (int i = ; i <= m; i++) {
int u = sort_edge[i].u, v = sort_edge[i].v;
if (!dsu.merge(u, v)) continue;
vec[u].push_back(v); vec[v].push_back(u);
ed++;
ans += sort_edge[i].w;
vis[sort_edge[i].id] = ;
}
int q;
scanf("%d", &q);
if (ed < n - ) {
while (q--) {
int x;
scanf("%d", &x);
puts("Not connected");
}
return ;
}
dfs1(, );
dfs2(, );
seg.build(, , n);
assert(n == tol);
for (int i = ; i <= m; i++) {
if (vis[i]) continue;
solve(edge[i].u, edge[i].v, edge[i].w);
}
while (q--) {
int i;
scanf("%d", &i);
if (!vis[i]) {
printf("%d\n", ans);
continue;
}
int u = dep[edge[i].u] < dep[edge[i].v] ? edge[i].v : edge[i].u;
int temp = seg.query(, , n, dfn[u]);
if (temp == INF) puts("Not connected");
else printf("%d\n", ans - edge[i].w + temp);
}
return ;
}

2301. [HAOI2011]Problem b

询问拆成四个,就像矩阵数点一样。
每一个询问的形式为 $\sum_{i=1}^n\sum_{j=1}^m[(i,j)==k]$。
$$\sum_{i=1}^n\sum_{j=1}^m[(i,j)==k]=\sum_{i=1}^{\lfloor \frac{n}{k} \rfloor}\sum_{j=1}^{\lfloor \frac{m}{k} \rfloor}[(i,j)==1]$$
把 $\lfloor \dfrac{n}{k} \rfloor$ 换成 $n$,把 $\lfloor \dfrac{m}{k} \rfloor$ 换成 $m$
$$\sum_{i=1}^{n}\sum_{j=1}^{m}\sum_{d|(i,j)}\mu(d)=\sum_d \mu(d)\sum_{i=1}^{n}[d|i]\sum_{j=1}^{m}[d|j]$$
$$=\sum_d\mu(d)\lfloor \dfrac{n}{d}\rfloor\lfloor \dfrac{m}{d}\rfloor$$
求个 $\mu$ 的前缀和加数论分块。

#include <bits/stdc++.h>

const int N = 5e4 + ;
int prime[N], prin, mu[N];
bool vis[N]; void init() {
mu[] = ;
for (int i = ; i < N; i++) {
if (!vis[i]) prime[++prin] = i, mu[i] = -;
for (int j = ; j <= prin && i * prime[j] < N; j++) {
vis[i * prime[j]] = ;
if (i % prime[j] == ) {
mu[i * prime[j]] = ;
break;
}
mu[i * prime[j]] = -mu[i];
}
}
for (int i = ; i < N; i++)
mu[i] += mu[i - ];
} int solve(int n, int m) {
int res = ;
for (int i = , j; i <= std::min(n, m); i = j + ) {
j = std::min(n / (n / i), m / (m / i));
res += (mu[j] - mu[i - ]) * (n / i) * (m / i);
}
return res;
} int main() {
init();
int n;
scanf("%d", &n);
while (n--) {
int a, b, c, d, k;
scanf("%d%d%d%d%d", &a, &b, &c, &d, &k);
printf("%d\n", solve(b / k, d / k) - solve(b / k, (c - ) / k) - solve((a - ) / k, d / k) + solve((a - ) / k, (c - ) / k));
}
return ;
}

2693. jzptab

$$\sum_{i=1}^n\sum_{j=1}^m \text{lcm}(i, j)$$
$$=\sum_{i=1}^n\sum_{j=1}^m \frac{ij}{\text{gcd}(i,j)}$$
$$=\sum_{d}d\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}ij[(i,j)=1]$$
$$=\sum_{d}d\sum_{d'}\mu(d')d'^2 s(\lfloor\frac{n}{dd'}\rfloor)s(\lfloor\frac{m}{dd'}\rfloor)$$
$$=\sum_{T} s(\lfloor\frac{n}{T}\rfloor)s(\lfloor\frac{m}{T}\rfloor)\sum_{d|T}\mu(d)d^2\frac{T}{d}$$$$=\sum_{T} s(\lfloor\frac{n}{T}\rfloor)s(\lfloor\frac{m}{T}\rfloor)T\sum_{d|T}\mu(d)d$$
其中 $s(n) = \frac{n\times (n+1)}{2}$,设 $f(n)=\sum_{d|n}d\mu(d)$,$g(n)=nf(n)$,$f(n)$ 是积性函数,可以直接筛,问题解决。

#include <bits/stdc++.h>

const int MOD = ;
const int N = 1e7 + ;
int prime[N], prin;
int f[N];
bool vis[N]; void M(int &x) {
if (x >= MOD) x -= MOD;
if (x < ) x += MOD;
} void init(int N) {
f[] = ;
for (int i = ; i < N; i++) {
if (!vis[i]) {
prime[++prin] = i;
M(f[i] = - i + MOD);
}
for (int j = ; j <= prin && i * prime[j] < N; j++) {
vis[i * prime[j]] = ;
if (i % prime[j] == ) {
int temp = i;
while (temp % prime[j] == ) temp /= prime[j];
f[i * prime[j]] = 1LL * ( - prime[j] + MOD) * f[temp] % MOD;
break;
}
f[i * prime[j]] = 1LL * f[i] * f[prime[j]] % MOD;
}
}
for (int i = ; i < N; i++)
f[i] = (1LL * f[i - ] + 1LL * f[i] * i % MOD) % MOD;
} int cal(int n, int m) {
int x = 1LL * n * (n + ) / % MOD;
int y = 1LL * m * (m + ) / % MOD;
return 1LL * x * y % MOD;
} int solve(int n, int m) {
if (n > m) std::swap(n, m);
int ans = ;
for (int i = , j; i <= n; i = j + ) {
j = std::min(n / (n / i), m / (m / i));
(ans += 1LL * cal(n / i, m / i) * (f[j] - f[i - ] + MOD) % MOD) %= MOD;
}
return ans;
} signed main() {
int T;
scanf("%d", &T);
init(N - );
while (T--) {
int n, m;
scanf("%d%d", &n, &m);
printf("%d\n", solve(n, m));
}
return ;
}

2820. YY的GCD

$$\sum_{i=1}^{n}\sum_{i=1}^{m}\left[\left(i,j\right)\in P\right]$$
$$=\sum_{p\in P}\sum_{i=1}^{n}\sum_{i=1}^{m}\left[\left(i,j\right)=p\right]$$$$=\sum_{p\in P}\sum_{i=1}^{\lfloor \frac{n}{p} \rfloor}\sum_{i=1}^{\lfloor \frac{m}{p} \rfloor}\left[\left(i,j\right)=1\right]$$
$$=\sum_{p\in P}\sum_{d=1}^{\min\{\lfloor \frac{n}{p} \rfloor,\lfloor \frac{m}{p} \rfloor\}}\mu(d)\lfloor\frac{n}{pd}\rfloor\lfloor\frac{m}{pd}\rfloor$$
$$=\sum_{T=1}^{\min\{n, m\}}\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor\sum_{p|T \wedge p\in P}\mu(\frac{T}{p})$$

$f(n) = \sum_{p|n \wedge p \in P}\mu(\frac{n}{p})$ 不是积性函数,但是可以线性筛预处理出来。
对于质数 $p$,$f(p)=1$
当 $p < M(i)$ 时,$f(i \times p)=-f(i)+\mu(i)$
当 $p=M(i)$ 时,$i \times p$ 里含有 $p^2$,所以 $\forall p' \neq p$,$\mu(\frac{i\times p}{p'})=0$,所以 $f(i\times p)=\mu(i)$

#include <bits/stdc++.h>
#define ll long long const int N = 1e7 + ;
int prime[N], prin, mu[N];
bool vis[N];
ll f[N]; void init() {
mu[] = ;
for (int i = ; i < N; i++) {
if (!vis[i]) {
prime[++prin] = i;
mu[i] = -;
f[i] = ;
}
for (int j = ; j <= prin && i * prime[j] < N; j++) {
vis[i * prime[j]] = ;
if (i % prime[j] == ) {
mu[i * prime[j]] = ;
f[i * prime[j]] = mu[i];
break;
}
mu[i * prime[j]] = -mu[i];
f[i * prime[j]] = -f[i] + mu[i];
}
}
for (int i = ; i < N; i++)
f[i] += f[i - ];
} ll solve(int n, int m) {
ll ans = ;
for (int i = , j; i <= std::min(n, m); i = j + ) {
j = std::min(n / (n / i), m / (m / i));
ans += (f[j] - f[i - ]) * (n / i) * (m / i);
}
return ans;
} int main() {
init();
int T;
scanf("%d", &T);
while (T--) {
int n, m;
scanf("%d%d", &n, &m);
printf("%lld\n", solve(n, m));
}
return ;
}

2940. [Poi2000]条纹

就是枚举放一种条纹下去会变成啥样,要么变成两段要么变成一段,求下 SG 函数就好了。

复杂度 $O(n ^ 2)$

#include <bits/stdc++.h>

const int N = ;
int SG[N];
bool vis[N]; int main() {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
for (int i = std::min(a, std::min(b, c)); i <= ; i++) {
memset(vis, , sizeof(vis));
for (int j = ; j <= i - a; j++)
vis[SG[j] ^ SG[i - a - j]] = ;
for (int j = ; j <= i - b; j++)
vis[SG[j] ^ SG[i - b - j]] = ;
for (int j = ; j <= i - c; j++)
vis[SG[j] ^ SG[i - c - j]] = ;
for (int j = ; ; j++)
if (!vis[j]) {
SG[i] = j;
break;
}
}
int T;
scanf("%d", &T);
while (T--) {
int n;
scanf("%d", &n);
puts(SG[n] ? "" : "");
}
return ;
}

3529. [Sdoi2014]数表 

$$\sum_{i=1}^{n}\sum_{j=1}^{m}\sigma_{1}(\gcd(i, j))[\sigma_1(\gcd(i,j))\leq a]$$
首先忽略 $\sigma_1(\gcd(i,j))\leq a$ 的限制
即求$$\sum_{i=1}^{n}\sum_{j=1}^{m}\sigma_{1}(\gcd(i, j))$$
$$=\sum_{d=1}^{\min\{n,m\}}\sigma_{1}(d)\sum_{i=1}^{n}\sum_{j=1}^{m}[(i,j)=d]$$
$$=\sum_{d=1}^{\min\{n,m\}}\sigma_{1}(d)\sum_{d'=1}^{\min\{\lfloor\frac{n}{d}\rfloor,\lfloor\frac{m}{d}\rfloor\}}\mu(d')\lfloor\frac{n}{dd'}\rfloor\lfloor\frac{m}{dd'}\rfloor$$
$$=\sum_{T}\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor\sum_{d|T}\sigma_1(d)\mu(\frac{T}{d})$$
设 $f(n)=\sum_{d|n}\sigma_1(d)\mu(\frac{n}{d})$,要维护这个的前缀和,会受 $\sigma_1(\gcd(i,j))\leq a$ 的就只有这里的 $\sigma_1(d)$,离线一下询问,用树状数组维护这个前缀和,然后就像扫描线一样去修改这个前缀和,总共需要修改 $n\log n$ 次,所以复杂度为 $O(n\log ^2 n)$

#include <bits/stdc++.h>

namespace IO {
char buf[ << ], buf2[ << ], a[], *p1 = buf, *p2 = buf, hh = '\n';
int p, p3 = -;
void read() {}
void print() {}
inline int getc() {
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, , << , stdin), p1 == p2) ? EOF : *p1++;
}
inline void flush() {
fwrite(buf2, , p3 + , stdout), p3 = -;
}
template <typename T, typename... T2>
inline void read(T &x, T2 &... oth) {
T f = ; x = ;
char ch = getc();
while (!isdigit(ch)) { if (ch == '-') f = -; ch = getc(); }
while (isdigit(ch)) { x = x * + ch - ; ch = getc(); }
x *= f;
read(oth...);
}
template <typename T, typename... T2>
inline void print(T x, T2... oth) {
if (p3 > << ) flush();
if (x < ) buf2[++p3] = , x = -x;
do {
a[++p] = x % + ;
} while (x /= );
do {
buf2[++p3] = a[p];
} while (--p);
buf2[++p3] = hh;
print(oth...);
}
} // using namespace IO const int N = 1e5;
int prime[N + ], prin, mu[N + ], n, d[N + ];
bool vis[N + ];
std::pair<int, int> p[N + ]; void init() {
mu[] = ;
for (int i = ; i <= N; i++) {
if (!vis[i]) {
prime[++prin] = i;
mu[i] = -;
}
for (int j = ; j <= prin && i * prime[j] <= N; j++) {
vis[i * prime[j]] = ;
if (i % prime[j] == ) break;
mu[i * prime[j]] = -mu[i];
}
}
for (int i = ; i <= N; i++)
for (int j = ; 1LL * i * j <= N; j++)
d[i * j] += i;
for (int i = ; i <= N; i++) {
p[i].first = d[i], p[i].second = i;
}
std::sort(p + , p + + N);
} struct Bit {
unsigned tree[N];
int lowbit(int x) {
return x & -x;
}
void add(int x, unsigned v) {
for (int i = x; i <= N; i += lowbit(i))
tree[i] += v;
}
unsigned query(int x) {
unsigned ans = ;
for (int i = x; i; i -= lowbit(i))
ans += tree[i];
return ans;
}
} bit; struct QUERY{
int n, m, a, id;
bool operator < (const QUERY &p) const {
return a < p.a;
}
} que[N]; unsigned ans[N]; unsigned solve(int n, int m) {
unsigned ans = ;
for (int i = , j; i <= std::min(n, m); i = j + ) {
j = std::min(n / (n / i), m / (m / i));
ans += 1u * (n / i) * (m / i) * (bit.query(j) - bit.query(i - ));
}
return ans;
} int main() {
init();
int q;
IO::read(q);
for (int i = ; i <= q; i++) {
IO::read(que[i].n, que[i].m, que[i].a);
que[i].id = i;
}
std::sort(que + , que + + q);
int cur = ;
for (int i = ; i <= q; i++) {
while (cur <= N && p[cur].first <= que[i].a) {
for (int j = ; j * p[cur].second <= N; j++)
bit.add(j * p[cur].second, p[cur].first * mu[j]);
cur++;
}
ans[que[i].id] = solve(que[i].n, que[i].m);
}
for (int i = ; i <= q; i++)
IO::print(ans[i] & ((1u << ) - ));
IO::flush();
return ;
}

3576. [Hnoi2014]江南乐

有一个很显然的 $O(n^2)$ 的方法。枚举要分成 $i$ 份,然后大堆是 $n % i$,小堆有 $n - n \% i$ 的奇偶性,就可以求出 SG 函数。

但其实很多情况是冗余的,小堆的大小只有 $\sqrt n$ 种,即 $\lfloor \dfrac {n}{i}\rfloor$。

然后因为在同一段中 $n \% i$ 和 $n \% (i + 2)$ 的奇偶性相同,所以只需要求一下 $n \% i$ 和 $n \% (i + 1)$ 的情况即可。

#include <bits/stdc++.h>

const int N = 1e5 + ;
int SG[N], vis[N], T, F;
std::vector<int> a[]; int get(int n) {
for (int i = , j, s; i <= n; i = j + ) {
j = n / (n / i);
s = n / i;
int y = n % i, x = i - y;
vis[((x & ) * SG[s]) ^ ((y & ) * SG[s + ])] = n;
if (i < j) {
y = n % (i + );
x = i + - y;
vis[((x & ) * SG[s]) ^ ((y & ) * SG[s + ])] = n;
}
}
for (int i = ; ; i++)
if (vis[i] != n)
return i;
} void init(int n) {
for (int i = F; i <= n; i++)
SG[i] = get(i);
} int main() {
scanf("%d%d", &T, &F);
int mx = ;
for (int i = ; i <= T; i++) {
int n;
scanf("%d", &n);
a[i].resize(n);
for (int j = ; j < n; j++)
scanf("%d", &a[i][j]), mx = std::max(mx, a[i][j]);
}
init(mx);
for (int i = ; i <= T; i++) {
int n = a[i].size();
int ans = ;
for (int j = ; j < n; j++) {
int x = a[i][j];
ans ^= SG[x];
}
printf("%d%c", ans ? : , " \n"[i == T]);
}
return ;
}

3722. 精神污染

查询每一条路径覆盖了多少条路径。

一条路径 $(u, v)$,如果另一条路径 $(u_0, v_0)$ 被其覆盖,那么 $u_0$ 和 $v_0$ 一定在这条路径上。

那么可以以dfs序建主席树,每个节点代表一个版本的线段树,一条路径就将 $v$ 加到 $u$ 版本的线段树上。

在 $in[v]$ 处 +1, $out[v]$ 处 -1。

查询的时候就是差分查询,在 $root[u], root[v], root[lca], root[fa_lca]$ 查询 $(lca, u)$ + $(lca, v)$ - $(lca, lca)$ - $1$

毒瘤卡空间,得用树剖求LCA

#include <bits/stdc++.h>
#define pii pair<int, int>
#define fi first
#define se second
#define ll long long const int N = 1e5 + ;
int n, m, in[N], out[N], fa[N], sz[N], son[N], dep[N], root[N], cnt, top[N];
std::vector<int> vec[N], query[N];
std::pii edge[N]; ll gcd(ll a, ll b) {
while (b) {
a %= b;
std::swap(a, b);
}
return a;
} struct Seg {
struct Node {
int lp, rp, sum;
} tree[N * ];
int tol;
void update(int &p, int q, int l, int r, int pos, int v) {
tree[p = ++tol] = tree[q];
tree[p].sum += v;
if (l == r) return;
int mid = l + r >> ;
if (pos <= mid) update(tree[p].lp, tree[q].lp, l, mid, pos, v);
else update(tree[p].rp, tree[q].rp, mid + , r, pos, v);
}
int query(int p, int q, int f, int ff, int l, int r, int x, int y) {
if (x <= l && y >= r)
return tree[p].sum + tree[q].sum - tree[f].sum - tree[ff].sum;
int mid = l + r >> ;
ll ans = ;
if (x <= mid) ans += query(tree[p].lp, tree[q].lp, tree[f].lp, tree[ff].lp, l, mid, x, y);
if (y > mid) ans += query(tree[p].rp, tree[q].rp, tree[f].rp, tree[ff].rp, mid + , r, x, y);
return ans;
}
} seg; void dfs1(int u, int f = ) {
fa[u] = f;
dep[u] = dep[f] + ;
sz[u] = ;
in[u] = ++cnt;
for (int v: vec[u])
if (v != f) {
dfs1(v, u);
sz[u] += sz[v];
if (sz[v] > sz[son[u]]) son[u] = v;
}
out[u] = ++cnt;
} void dfs2(int u, int tp) {
top[u] = tp;
if (!son[u]) return;
dfs2(son[u], tp);
for (int v: vec[u])
if (v != fa[u] && v != son[u])
dfs2(v, v);
} void dfs(int u, int f = ) {
root[u] = root[f];
for (int v: query[u]) {
seg.update(root[u], root[u], , cnt, in[v], );
seg.update(root[u], root[u], , cnt, out[v], -);
}
for (int v: vec[u])
if (v != f) dfs(v, u);
} int Lca(int u, int v) {
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) std::swap(u, v);
u = fa[top[u]];
}
if (dep[u] > dep[v]) std::swap(u, v);
return u;
} int main() {
freopen("in.txt", "r", stdin);
//freopen("out1.txt", "w", stdout);
scanf("%d%d", &n, &m);
for (int i = ; i < n; i++) {
int u, v;
scanf("%d%d", &u, &v);
vec[u].push_back(v);
vec[v].push_back(u);
}
for (int i = ; i <= m; i++) {
scanf("%d%d", &edge[i].fi, &edge[i].se);
query[edge[i].fi].push_back(edge[i].se);
}
std::sort(edge + , edge + + m);
dfs1();
dfs2(, );
dfs();
ll ans = ;
for (int i = ; i <= m; i++) {
int u = edge[i].fi, v = edge[i].se, f = Lca(u, v), ff = fa[f];
//printf("%d %d %d\n", u, v, f);
ans += seg.query(root[u], root[v], root[f], root[ff], , cnt, in[f], in[u]);
ans += seg.query(root[u], root[v], root[f], root[ff], , cnt, in[f], in[v]);
ans -= seg.query(root[u], root[v], root[f], root[ff], , cnt, in[f], in[f]);
ans--;
}
//printf("%lld\n", ans);
long long all = 1LL * m * (m - ) / ;
long long g = gcd(all, ans);
printf("%lld/%lld\n", ans / g, all / g);
return ;
}

3781. 小B的询问

莫队。

#include <bits/stdc++.h>
#define ll long long const int N = 5e4 + ;
int cnt[N], a[N];
ll res, ans[N];
int B; struct Q {
int l, r, id;
bool operator < (const Q &p) const {
return l / B == p.l / B ? r < p.r : l < p.l;
}
} q[N]; void add(int x) {
res -= 1LL * cnt[x] * cnt[x];
cnt[x]++;
res += 1LL * cnt[x] * cnt[x];
} void del(int x) {
res -= 1LL * cnt[x] * cnt[x];
cnt[x]--;
res += 1LL * cnt[x] * cnt[x];
} int main() {
int n, m, k;
scanf("%d%d%d", &n, &m, &k);
B = n / sqrt(m);
for (int i = ; i <= n; i++)
scanf("%d", a + i);
for (int i = ; i <= m; i++)
scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
std::sort(q + , q + + m);
int l = , r = ;
for (int i = ; i <= m; i++) {
while (l < q[i].l) del(a[l++]);
while (l > q[i].l) add(a[--l]);
while (r > q[i].r) del(a[r--]);
while (r < q[i].r) add(a[++r]);
ans[q[i].id] = res;
}
for (int i = ; i <= m; i++)
printf("%lld\n", ans[i]);
return ;
}

3994. [SDOI2015]约数个数和

求 $$\sum_{i=1}^n\sum_{j=1}^md(ij)$$
$$d(ij)=\sum_{x|i}\sum_{y|j}[(i,j)==1]=\sum_{x|i}\sum_{y|j}\sum_{p|(x,y)}\mu(p)$$
$$=\sum_p \mu(p)\sum_{x|i}[d|x]\sum_{y|j}[d|y]=\sum_{p|i,p|j}\mu(p)d(\frac{i}{p})d(\frac{j}{p})$$
代回原式得
$$\sum_{i=1}^n\sum_{j=1}^m\sum_{p|i,p|j}\mu(p)d(\frac{i}{p})d(\frac{j}{p})=\sum_{p}\mu(p)\sum_{p|i}d(\frac{i}{p})\sum_{p|j}d(\frac{j}{p})$$
$$=\sum_{p}\mu(p)\sum_{i=1}^{\lfloor \frac{n}{p} \rfloor}d(i)\sum_{j=1}^{\lfloor \frac{m}{p} \rfloor}d(j)$$
令 $s(n)=\sum_{i=1}^n d(i)$
原式为$$\sum_{p}\mu(p)s(\lfloor \frac{n}{p} \rfloor)s(\lfloor \frac{m}{p} \rfloor)$$

#include <bits/stdc++.h>

const int N = 5e4 + ;
int mu[N], prime[N], prin, d[N], t[N];
bool vis[N]; void init(int n) {
mu[] = d[] = t[] = ;
for (int i = ; i <= n; i++) {
if (!vis[i]) {
prime[++prin] = i;
d[i] = ;
t[i] = ;
mu[i] = -;
}
for (int j = ; j <= prin && i * prime[j] < N; j++) {
vis[i * prime[j]] = ;
if (i % prime[j] == ) {
t[i * prime[j]] = t[i] + ;
mu[i * prime[j]] = ;
d[i * prime[j]] = d[i] / (t[i] + ) * (t[i] + );
break;
}
d[i * prime[j]] = d[i] * ;
t[i * prime[j]] = ;
mu[i * prime[j]] = -mu[i];
}
}
for (int i = ; i <= n; i++)
mu[i] += mu[i - ], d[i] += d[i - ];
} #define ll long long ll solve(int n, int m) {
ll ans = ;
if (n > m) std::swap(n, m);
for (int i = , j; i <= n; i = j + ) {
j = std::min(n / (n / i), m / (m / i));
ans += 1LL * (mu[j] - mu[i - ]) * 1LL * d[n / i] * d[m / i];
}
return ans;
} int main() {
init(N - );
int T;
scanf("%d", &T);
while (T--) {
int n, m;
scanf("%d%d", &n, &m);
printf("%lld\n", solve(n, m));
}
return ;
}

4025. 二分图

线段树分治+按秩合并的并查集解决加边删边的问题。
一个图是二分图当且仅当点数大于等于二并且不存在奇环。
那么可以用带权并查集维护路径长度,会出现环就是当加入一条边是产生环并且原路径长度为偶数。

#include <bits/stdc++.h>

namespace IO {
void read() {}
template<class T, class... T2>
void read(T &x, T2 &... oth) {
x = ; T f = ; char ch = getchar();
while (!isdigit(ch)) { if (ch == '-') f = -; ch = getchar(); }
while (isdigit(ch)) x = x * + ch - '', ch = getchar();
x *= f;
read(oth...);
}
} const int N = 2e5 + ; int st[N], top; struct DSU {
int fa[N], d[N], dep[N];
void init(int n) {
for (int i = ; i <= n; i++)
fa[i] = i, d[i] = dep[i] = ;
}
int getfa(int x) {
while (x != fa[x])
x = fa[x];
return x;
}
int getdis(int x) {
int dis = ;
while (x != fa[x])
dis ^= d[x], x = fa[x];
return dis;
}
bool merge(int x, int y) {
int D = getdis(x) ^ getdis(y) ^ ;
x = getfa(x), y = getfa(y);
if (x == y) return D == ;
if (dep[x] < dep[y])
std::swap(x, y);
if (dep[x] == dep[y])
dep[x]++, st[++top] = -x;
fa[y] = x; d[y] = D; st[++top] = y;
return ;
}
void del(int tp) {
while (top > tp) {
int x = st[top--];
if (x < )
dep[-x]--;
else
fa[x] = x, d[x] = ;
}
}
} dsu; int n, m, T;
struct P {
int u, v, x, y;
} p[N];
int ans[N]; void solve(int l, int r, const std::vector<int> &vec) {
int tp = top;
int mid = l + r >> ;
std::vector<int> L, R;
for (int i = , sz = vec.size(); i < sz; i++) {
int id = vec[i];
if (p[id].x <= l && r <= p[id].y) {
if (!dsu.merge(p[id].u, p[id].v)) {
for (int j = l; j <= r; j++)
ans[j] = ;
dsu.del(tp);
return;
}
} else {
if (p[id].x <= mid)
L.push_back(id);
if (p[id].y > mid)
R.push_back(id);
}
}
if (l == r) {
dsu.del(tp);
return;
}
solve(l, mid, L);
solve(mid + , r, R);
dsu.del(tp);
} int main() {
IO::read(n, m, T);
dsu.init(n);
std::vector<int> vec;
for (int i = ; i <= m; i++)
IO::read(p[i].u, p[i].v, p[i].x, p[i].y), p[i].x++, vec.push_back(i);
solve(, T, vec);
for (int i = ; i <= T; i++)
puts(ans[i] ? "No" : "Yes");
return ;
}

4299. Codechef FRBSUM

来补徐州的锅...其实在暑假补过这题...但是当时是把它拿来练主席树板子的...没懂原理...

若 $[1,x]$ 都能组成,那么 $[1, x + 1]$ 的区间和肯定也都能组成。证明就是数学归纳法...或者现在区间和为 $s$,那么就是倒着把 $1$、$2$、$3$、都取走就能得到其他的值了。

这样每次区间长度至少翻倍。再用主席树维护就行了...

复杂度 $O(nlog^2 n)$,徐州加了个单点修改,三 $log$ 可过...


4424. Cf19E Fairy

一个图为二分图的充要条件就是不存在奇环。
先求出一个dfs树,然后考虑非树边对dfs树的影响。
有几种情况需要考虑。
一、不存在自环及奇环
都可以删。
二、自环
如果存在两个自环及以上,就不可能了,因为它只能删除一条边。
有一个自环,当不存在奇环的时候就只能删除这个自环,否则也没边可删了。
三、存在一个奇环
那么这个奇环上的树边及非树边都可以删。也只有这种情况能删非树边。
四、存在多个奇环
那么能删除的边就是这些奇环的树边的交集。同时,这个交集的边不能出现在偶环上,否则奇环+偶环还是会得到奇环。
那么树上差分一下得到每条边在多少个奇环上,如果在偶环上就把路径减一下,就能处理出不能在偶环上的情况。最后就判断一下每一条边的值是否为奇环的个数。

#include <bits/stdc++.h>

namespace IO {
void read() {}
template<class T, class... T2>
void read(T &x, T2 &... oth) {
x = ; T f = ; char ch = getchar();
while (!isdigit(ch)) { if (ch == '-') f = -; ch = getchar(); }
while (isdigit(ch)) x = x * + ch - '', ch = getchar();
x *= f;
read(oth...);
}
} const int N = 1e6 + ;
struct E {
int v, ne, id;
} e[N << ];
int head[N], tag[N], dep[N], cnt = ;
bool vis[N];
int self, n, m; void add(int u, int v, int id) {
e[++cnt].v = v; e[cnt].ne = head[u]; e[cnt].id = id; head[u] = cnt;
e[++cnt].v = u; e[cnt].ne = head[v]; e[cnt].id = id; head[v] = cnt;
} int tol, fei; void dfs(int u, int f) {
for (int i = head[u]; i; i = e[i].ne) {
if (i == (f ^ )) continue;
int v = e[i].v;
if (dep[v]) {
if (dep[v] > dep[u]) continue;
if ((dep[u] - dep[v] + ) & ) {
tol++;
fei = e[i].id;
tag[u]++; tag[v]--;
} else {
tag[u]--; tag[v]++;
}
} else {
dep[v] = dep[u] + ;
dfs(v, i);
tag[u] += tag[v];
}
}
} std::vector<int> vec; void dfs(int u) {
vis[u] = ;
for (int i = head[u]; i; i = e[i].ne) {
int v = e[i].v;
if (vis[v]) continue;
if (tag[v] == tol)
vec.push_back(e[i].id);
dfs(v);
}
} int main() {
IO::read(n, m);
for (int i = ; i <= m; i++) {
int u, v;
IO::read(u, v);
if (u == v && !self) {
self = i;
continue;
}
if (u == v) {
self = -;
continue;
}
add(u, v, i);
}
if (self == -) {
puts("");
return ;
}
for (int i = ; i <= n; i++) {
if (!dep[i])
dep[i] = , dfs(i, );
}
if (tol == ) {
if (self) {
printf("1\n%d\n", self);
} else {
printf("%d\n", m);
for (int i = ; i <= m; i++)
printf("%d%c", i, " \n"[i == m]);
}
return ;
}
if (self) {
puts("");
return ;
}
for (int i = ; i <= n; i++)
if (!vis[i])
dfs(i);
if (tol == )
vec.push_back(fei);
printf("%d\n", (int)vec.size());
std::sort(vec.begin(), vec.end());
for (int i = ; i < vec.size(); i++)
printf("%d%c", vec[i], " \n"[i + == vec.size()]);
return ;
}

4652. [Noi2016]循环之美

首先,一个分数 $\frac{p}{q}$ 在 $k$ 进制下是纯循环小数,就是 $\exists t$,$p \equiv p \times k^t \pmod q$,其中 $p \bot q$,那么即为 $k^t \equiv 1 \pmod q$,要存在解,就得 $k \bot q$。
所以答案就是 $\sum_{i=1}^n\sum_{j=1}^m [i\bot j][k\bot q]$
$$\sum_{i=1}^n\sum_{j=1}^m [i\bot j][k\bot j]$$
$$=\sum_{i=1}^n\sum_{j=1}^m[k\bot j]\sum_{d|(i,j)}\mu(d)$$
$$=\sum_{d=1}^{\min\{n,m\}}[d\bot k]\mu(d)\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}[j\bot k]$$
$$=\sum_{d=1}^{\min\{n,m\}}[d\bot k]\mu(d)\lfloor\frac{n}{d}\rfloor s(\lfloor\frac{m}{d}\rfloor)$$
其中 $s(n)=\sum_{i=1}^{n}[i\bot k]=\lfloor\frac{n}{k}\rfloor s(k) + s(n$ $\text{mod}$ $k)$,这个暴力预处理即可。
要求 $f(n)=\mu(n)[n\bot k]$ 的前缀和,设 $g(n)=[n\bot k]$。
$$(f\circ g)(n)$$
$$=\sum_{d\mid n}\mu(d)[d\bot k][\frac{n}{d}\bot k]$$
$$=[n\bot k]\sum_{d\mid n}\mu(d)$$
$$=[n \bot k][n=1]$$
所以 $\sum_{i=1}^n (f\circ g)(i)=1$
所以 $f$ 的前缀和 $S(n)=1-\sum_{i=2}^{n}[i\bot k]S(\lfloor\frac{n}{i}\rfloor)$
至此解决。

#include <bits/stdc++.h>

const int N = 1e6, XN = N + ;
int prin, prime[XN], mu[XN];
int fs[XN], gs[XN]; int gcd(int a, int b) {
while (b) {
a %= b;
std::swap(a, b);
}
return a;
} void init(int k) {
static bool vis[XN];
mu[] = ;
for (int i = ; i <= N; i++) {
if (!vis[i]) {
prime[++prin] = i;
mu[i] = -;
}
for (int j = ; j <= prin && i * prime[j] <= N; j++) {
vis[i * prime[j]] = ;
if (i % prime[j] == ) break;
mu[i * prime[j]] = -mu[i];
}
}
for (int i = ; i <= N; i++) {
fs[i] = fs[i - ] + mu[i] * (gcd(i, k) == );
gs[i] = gs[i - ] + (gcd(i, k) == );
}
} int k, n, m; int GS(int n) {
return (n / k) * gs[k] + gs[n % k];
}
#define ll long long
std::unordered_map<int, ll> mp; ll solve(int n) {
if (n <= N) return fs[n];
if (mp.count(n)) return mp[n];
ll ans = ;
for (int i = , j; i <= n; i = j + ) {
j = n / (n / i);
ans -= 1LL * (GS(j) - GS(i - )) * solve(n / i);
}
return mp[n] = ans;
} signed main() {
scanf("%d%d%d", &n, &m, &k);
init(k);
ll ans = ;
for (int i = , j; i <= std::min(n, m); i = j + ) {
j = std::min(n / (n / i), m / (m / i));
ans += 1LL * (solve(j) - solve(i - )) * (n / i) * GS(m / i);
}
printf("%lld\n", ans);
return ;
}

4742. [Usaco2016 Dec]Team Building

考虑将所有牛的分数从大到小排序,当分数相同时第二队的牛在前面。

这样把第一队的牛看成左括号,第二队的牛看成右括号,那就相当于要选出一个合法的括号序列。

$dp[i][j][k]$ 表示前 $i$ 头牛选了 $j$ 个第一队的,选了 $k$ 个第二队的方案数。

转移过程中保证 $j$ 大于 $k$ 即可。

4816. [Sdoi2017]数字表格

$$\prod_{i=1}^{n}\prod_{j=1}^{m}f(\gcd(i,j))$$
$$=\prod_{d=1}^{\min\{n,m\}}f(d)^{\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}[(i,j)=1]}$$
$$=\prod_{d=1}^{\min\{n,m\}}f(d)^{\sum_{d'}\mu(d')\lfloor\frac{n}{dd'}\rfloor\lfloor\frac{m}{dd'}\rfloor}$$
$$=\prod_{T}(\prod_{d|T}f(d)^{\mu(\frac{T}{d})})^{\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor}$$
求出 $g(n) =\prod_{d|n}f(d)^{\mu(\frac{n}{d})}$ 的前缀积即可。

#include <bits/stdc++.h>

const int MOD = 1e9 + ;

int qp(int a, int b = MOD - ) {
int ans = ;
while (b) {
if (b & ) ans = 1LL * ans * a % MOD;
b >>= ;
a = 1LL * a * a % MOD;
}
return ans;
} const int N = 1e6;
int prime[N + ], prin, mu[N + ], g[N + ], f[N + ], fi[N + ];
bool vis[N + ]; inline int add(int x, int y) {
return x + y >= MOD ? x + y - MOD : x + y;
} void init() {
mu[] = ;
f[] = ; f[] = ;
fi[] = ;
g[] = g[] = ;
for (int i = ; i <= N; i++) {
g[i] = ;
f[i] = add(f[i - ], f[i - ]);
fi[i] = qp(f[i]);
if (!vis[i]) {
prime[++prin] = i;
mu[i] = -;
}
for (int j = ; j <= prin && i * prime[j] <= N; j++) {
vis[i * prime[j]] = ;
if (i % prime[j] == ) break;
mu[i * prime[j]] = -mu[i];
}
}
for (int i = ; i <= N; i++) if (mu[i])
for (int j = ; 1LL * i * j <= N; j++)
g[i * j] = 1LL * g[i * j] * (mu[i] == ? f[j] : fi[j]) % MOD;
for (int i = ; i <= N; i++)
g[i] = 1LL * g[i] * g[i - ] % MOD;
} int main() {
init();
int T;
scanf("%d", &T);
while (T--) {
int n, m;
scanf("%d%d", &n, &m);
if (n > m) std::swap(n, m);
int ans = ;
for (int i = , j; i <= n; i = j + ) {
j = std::min(n / (n / i), m / (m / i));
ans = 1LL * ans * qp(1LL * g[j] * qp(g[i - ]) % MOD, 1LL * (n / i) * (m / i) % (MOD - )) % MOD;
}
printf("%d\n", ans);
}
return ;
}

BZOJ练习记的更多相关文章

  1. BZOJ开荒记

    2019/4/16 1:04 使用Yinku2017提交了第一发,当然是A+B Problem. 看一下排行榜,算一下区域赛还有180多天吧?先用30天过50道题(含A+B Problem)怎么样?

  2. BZOJ.4738.[清华集训2016]汽水(点分治 分数规划)

    BZOJ UOJ 记\(val_i\)是每条边的边权,\(s\)是边权和,\(t\)是经过边数,\(k\)是给定的\(k\). 在点分治的时候二分答案\(x\),设\(|\frac st-k|=x\) ...

  3. 【BZOJ】【3157】&【BZOJ】【3516】国王奇遇记

    数论 题解:http://www.cnblogs.com/zhuohan123/p/3726933.html copy一下推导过程: 令$$S_i=\sum_{k=1}^{n}k^im^k$$ 我们有 ...

  4. [BZOJ 3157] 国王奇遇记

    Link: BZOJ 3157 传送门 Solution: 题意:求解$\sum_{i=1}^n m^i \cdot {i^m}$ $O(m^2)$做法: 定义一个函数$f[i]$,$f[i]=\su ...

  5. 3157: 国王奇遇记 & 3516: 国王奇遇记加强版 - BZOJ

    果然我数学不行啊,题解君: http://www.cnblogs.com/zhuohan123/p/3726933.html const h=; var fac,facinv,powm,s:..]of ...

  6. BZOJ 3157: 国王奇遇记 (数学)

    题面:BZOJ3157 一句话题意: 求: \[ \sum_{i=1}^ni^m\ \times m^i\ (mod\ 1e9+7)\ \ (n \leq 1e9,m\leq200)\] 题解 令 \ ...

  7. BZOJ 3516 国王奇遇记加强版(乱推)

    题意 求\(\sum_{k=1}^{n}k^mm^k (n\leq1e9,m\leq1e3)\) 思路 在<>中有一个方法用来求和,称为摄动法. 我们考虑用摄动法来求这个和式,看能不能得到 ...

  8. bzoj 3157 && bzoj 3516 国王奇遇记——推式子

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3157 https://www.lydsy.com/JudgeOnline/problem.p ...

  9. bzoj 3157 & bzoj 3516 国王奇遇记 —— 推式子

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3157 https://www.lydsy.com/JudgeOnline/problem.p ...

随机推荐

  1. day_0

    Day01 - 初识Python - Python简介 - Python的历史 / Python的优缺点 / Python的应用领域 - 搭建编程环境 - Windows环境 / Linux环境 / ...

  2. Paper | Feedback Networks

    目录 读后总结 动机 故事 ConvLSTM图像分类网络 损失函数 与Episodic Curriculum Learning的结合 实验方法 发表在2017年CVPR. 读后总结 这篇论文旨在说明: ...

  3. 【Java并发专题之二】Java线程基础

    使用线程更好的提高资源利用率,但也会带来上下文切换的消耗,频繁的内核态和用户态的切换消耗,如果代码设计不好,可能弊大于利. 一.线程 进程是分配资源的最小单位,线程是程序执行的最小单位:线程是依附于进 ...

  4. TiDB 压力测试报告

    (转载自公众号DBATech) 一.测试环境 1.tidb 集群架构: 测试使用最基本的TiDB架构.即 3个tidb-server节点+ 3个tikv节点 + 3个pd节点. 2.tidb集群的部署 ...

  5. 如何让 C# 在运行时自动选择合适的重载方法?

    如题:假设我们有一段代码: static void Main(string[] args) { ; // 假设这里的 obj 的值来自于外部方法 PrintType(obj); } public st ...

  6. (译)Kubernetes中的多容器Pod和Pod内容器间通信

    原文:https://www.mirantis.com/blog/multi-container-pods-and-container-communication-in-kubernetes/Pave ...

  7. 【题解】Norma [COCI2014] [SP22343]

    [题解]Norma [COCI2014] [SP22343] 传送门:\(\text{Norma [COCI2014]}\) \(\text{[SP22343]}\) [题目描述] 给定一个整数 \( ...

  8. Vue动态修改网页标题

    业务需求,进入页面的时候,网页有个默认标题,加载的网页内容不同时,标题需要变更. 例:功能授权,功能授权(张三). Vue下有很多的方式去修改网页标题,这里总结下解决此问题的几种方案: 一.最笨方案 ...

  9. 【EasyExcel】使用easyExcel过程中,项目报错的解决集合

    报错:Can not close IO [ERROR] 2019-11-02 13:51:21.210 [ProExportSkuDataJob-1455-TaskThread-1] [com.dma ...

  10. Python - 元组 - 第九天

    Python 元组 Python 的元组与列表类似,不同之处在于元组的元素不能修改. 元组使用小括号,列表使用方括号. 元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可.例如: >&g ...