BZOJ练习记

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

1001. [BeiJing2006]狼抓兔子

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

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


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 
using namespace std;

struct Bigi {
    int a[600], len;
    Bigi() {
        memset(a, 0, sizeof(a));
        len = 1;
    }
    friend Bigi operator * (int x, Bigi b) {
        Bigi res;
        res.len = b.len + 10;
        for (int i = 1; i <= res.len; i++) {
            res.a[i] += b.a[i] * x;
            res.a[i + 1] += res.a[i] / 10;
            res.a[i] %= 10;
        }
        while (res.len > 1 && res.a[res.len] == 0) res.len--;
        return res;
    }
    friend Bigi operator - (Bigi x, Bigi y) {
        Bigi res;
        res.len = x.len;
        for (int i = 1; i <= res.len; i++) {
            res.a[i] = x.a[i] - y.a[i];
            while (res.a[i] < 0) {
                res.a[i] += 10;
                x.a[i + 1]--;
            }
        }
        while (res.len > 1 && res.a[res.len] == 0) res.len--;
        return res;
    }
    friend Bigi operator + (Bigi x, int y) {
        Bigi res = x;
        res.a[1] += y;
        for (int i = 1; i <= res.len; i++) {
            if (res.a[i] >= 10) {
                res.a[i] -= 10;
                res.a[i + 1]++;
            } else {
                break;
            }
        }
        while (res.a[res.len + 1]) res.len++;
        return res;
    }
    void print() {
        for (int i = len; i; i--)
            printf("%d", a[i]);
        puts("");
    }
} big[150], ans;

int main() {
    int n;
    scanf("%d", &n);
    big[1] = big[1] + 3;
    big[2] = big[2] + 8;
    if (n <= 2) {
        big[n].print();
        return 0;
    }
    for (int i = 3; i <= n; i++) 
        big[i] = 3 * big[i - 1] - big[i - 2];
    ans = 3 * big[n - 1] - 2 * big[n - 2];
    ans = ans + (-2);
    ans.print();
    return 0;
}
View Code


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 
#define pii pair
#define fi first
#define se second
using namespace std;

const int INF = 10000000;
const int N = 110;
int cost[N][N], dp[N], n, m, k, e;
vector 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 = 1; i <= m; i++) {
        unable[i] = 0;
        done[i] = 0;
        dis[i] = INF;
        for (int j = l; j <= r; j++)
            unable[i] |= ban[j][i];
    }
    if (unable[1] || unable[m]) return INF;
    priority_queue, greater > que;
    dis[1] = 0;
    que.push(pii(0, 1));
    while (!que.empty()) {
        auto pp = que.top(); que.pop();
        int u = pp.se;
        if (done[u]) continue;
        done[u] = 1;
        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] = 1;
    }
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            cost[i][j] = dijkstra(i, j);
    for (int i = 1; i <= n; i++) {
        dp[i] = cost[1][i] * i;
        for (int j = 1; j < i; j++)
            checkmin(dp[i], dp[j] + cost[j + 1][i] * (i - j) + k);
    }
    printf("%d\n", dp[n]);
    return 0;
}
View Code

 

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 

const int N = 1007;
const int MOD = 10000;
int num[N];
int prime[N], tol, d[N], c[N];
bool vis[N];

void init() {
    for (int i = 2; i < N; i++) {
        if (!vis[i]) prime[++tol] = i;
        for (int j = 1; j <= tol && i * prime[j] < N; j++) {
            vis[i * prime[j]] = 1;
            if (i % prime[j] == 0) break;
        }
    }
}

void add(int x, int o) {
    for (int i = 1; i <= tol; i++) {
        while (x % prime[i] == 0)
            c[i] += o, x /= prime[i];
    }
}

int main() {
    init();
    int n;
    scanf("%d", &n);
    bool flag = 0;
    int sum = 0, cnt = 0;
    for (int i = 1; i <= n; i++) {
        scanf("%d", d + i);
        if (!d[i] || d[i] > n - 1) flag = 1;
        if (d[i] != -1) sum += d[i] - 1, cnt++;
    }
    if (n == 1) {
        if (!d[1]) puts("1");
        else puts("0");
        return 0;
    }
    if (sum > n - 2 || flag) {
        puts("0");
        return 0;
    }
    for (int i = n - 2 - sum + 1; i <= n - 2; i++)
        add(i, 1);
    for (int i = 1; i <= n; i++) {
        if (d[i] > -1) {
            for (int j = 2; j < d[i]; j++)
                add(j, -1);
        }
    }
    int len = 0;
    num[++len] = 1;
    for (int i = 1; i <= n - 2 - sum; i++) {
        for (int j = 1; j <= len; j++) num[j] *= n - cnt;
        for (int j = 1; j <= len; j++) {
            if (num[j] >= MOD) {
                num[j + 1] += num[j] / MOD;
                num[j] %= MOD;
            }
        }
        while (num[len + 1]) {
            num[len + 1] += num[len] / MOD;
            num[len] %= MOD;
            len++;
        }
    }
    for (int i = 1; i <= tol; i++) {
        while (c[i]) {
            for (int j = 1; j <= len; j++) num[j] *= prime[i];
            for (int j = 1; j <= len; j++) {
                if (num[j] >= MOD) {
                    num[j + 1] += num[j] / MOD;
                    num[j] %= MOD;
                }
            }
            while (num[len + 1]) {
                num[len + 1] += num[len] / MOD;
                num[len] %= MOD;
                len++;
            }
            c[i]--;
        }
    }
    printf("%d", num[len]);
    for (int i = len - 1; i; i--) printf("%04d", num[i]);
    puts("");
    return 0;
}
View Code


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

#include 

const int N = 5e4 + 7;
const double eps = 1e-7;

inline int dcmp(double x) {
    if (fabs(x) < eps) return 0;
    return x < 0 ? -1 : 1;
}

struct P {
    double x, y;
    int id;
    inline bool operator < (const P &rhs) const {
        if (dcmp(x - rhs.x) == 0) 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) == 0) top--;
        else if (top > 1 && dcmp(crossx(p[st[top]], cur) - crossx(p[st[top]], p[st[top - 1]])) <= 0) top--;
        else break;
    }
    st[++top] = id;
}

int ans[N];

int main() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%lf%lf", &p[i].x, &p[i].y);
        p[i].id = i;
    }
    std::sort(p + 1, p + 1 + n);
    for (int i = 1; i <= n; i++)
        ins(i);
    for (int i = 1; i <= top; i++)
        ans[p[st[i]].id] = 1;
    for (int i = 1; i <= n; i++)
        if (ans[i])
            printf("%d ", i);
    return 0;
}
View Code

 

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

#include 
#define ll long long

const int MOD = 100003;

int qp(int a, ll b) {
    a %= MOD;
    int ans = 1;
    while (b) {
        if (b & 1) ans = 1LL * ans * a % MOD;
        a = 1LL * a * a % MOD;
        b >>= 1;
    }
    return ans;
}

int main() {
    int m;
    ll n;
    scanf("%d%lld", &m, &n);
    int ans = qp(m, n);
    ans = (ans - 1LL * m * qp(m - 1, n - 1) % MOD) % MOD;
    ans = (ans + MOD) % MOD;
    printf("%d\n", ans);
    return 0;
}
View Code

 

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 

const int N = 22;
int MOD, n, m;
char s[N];
int ne[N];

struct Mat {
    int mat[25][25];
    Mat() {
        memset(mat, 0, sizeof mat);
    }
    Mat operator * (const Mat &rhs) const {
        Mat c;
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
                for (int k = 0; 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 & 1) ans = ans * a;
        a = a * a;
        b >>= 1;
    }
    return ans;
}

void kmp() {
    int i = 0, j = ne[0] = -1;
    while (i < n) {
        while (j != -1 && 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 = 0; i < n; i++) {
        for (int j = 0; j < 10; j++) {
            int k = i;
            while (k != -1 && s[k] != j + '0') k = ne[k];
            k++;
            if (k < n) b.mat[i][k]++;
        }
    }
    Mat a;
    a.mat[0][0] = 1;
    a = qp(a, b, m);
    int ans = 0;
    for (int i = 0; i < n; i++)
        ans += a.mat[0][i];
    ans %= MOD;
    printf("%d\n", ans);
    return 0;
}
View Code

 

1010. [HNOI2008]玩具装箱toy

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

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

斜率优化一下即可。

#include 
#define ll long long
using namespace std;
 
const int N = 5e4 + 7;
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]) + 2 * 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 = 1; i <= n; i++)
        scanf("%lld", &c[i]), sum[i] = sum[i - 1] + c[i];
    for (int i = 1; i <= n; i++)
        sum[i] += i;
    int head = 1, tail = 1;
    l++;
    for (int i = 1; i <= n; i++) {
        while (head < tail && K(que[head], que[head + 1]) < 2 * sum[i]) head++;
        int j = que[head];
        dp[i] = dp[j] + sqr(sum[i] - sum[j] - l);
        while (head < tail && K(que[tail - 1], que[tail]) > K(que[tail], i)) tail--;
        que[++tail] = i;
    }
    printf("%lld\n", dp[n]);
    return 0;
}
View Code

 

1011. [HNOI2008]遥远的行星

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

 

1012. [JSOI2008]最大数maxnumber

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

线段树维护即可。

#include 
#define ll long long

const int N = 2e5 + 7;

struct Seg {
    #define lp p << 1
    #define rp p << 1 | 1
    ll tree[N << 2];
    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 >> 1;
        if (pos <= mid) update(lp, l, mid, pos, v);
        else update(rp, mid + 1, 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 >> 1;
        ll ans = 0;
        if (x <= mid) ans = std::max(ans, query(lp, l, mid, x, y));
        if (y > mid) ans = std::max(ans, query(rp, mid + 1, r, x, y));
        return ans;
    }
} seg;

int main() {
    int n;
    ll D;
    scanf("%d%lld", &n, &D);
    ll ans = 0;
    int cnt = 0;
    for (int i = 1; i <= n; i++) {
        char s[5];
        ll num;
        scanf("%s%lld", s, &num);
        if (s[0] == 'A') {
            num += ans;
            num %= D;
            seg.update(1, 1, n, ++cnt, num);
        } else {
            printf("%lld\n", ans = seg.query(1, 1, n, cnt - num + 1, cnt));
        }
    }
    return 0;
}
View Code

 

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

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

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

#include 

const int N = 20;
const double eps = 1e-10;
int n;
double a[N][N];
struct Node {
    double d[N];
} p[N];

void gauss() {
    for (int i = 1; i <= n; i++) {
        int r = i;
        for (int j = i + 1; 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 = 1; j <= n; j++) {
            if (j != i) {
                double t = a[j][i] / a[i][i];
                for (int k = i; k <= n + 1; k++)
                    a[j][k] -= a[i][k] * t;
            }
        }
    }
    for (int i = 1; i <= n; i++)
        a[i][n + 1] /= a[i][i];
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n + 1; i++)
        for (int j = 1; j <= n; j++)
            scanf("%lf", &p[i].d[j]);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            a[i][j] = 2 * p[i].d[j] - 2 * p[i + 1].d[j];
            a[i][n + 1] += p[i].d[j] * p[i].d[j] - p[i + 1].d[j] * p[i + 1].d[j];
        }
    }
    gauss();
    for (int i = 1; i <= n; i++)
        printf("%.3f%c", a[i][n + 1], " \n"[i == n]);
    return 0;
}
View Code

 

1014. [JSOI2008]火星人prefix

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

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

#include 
#define ull unsigned long long

const int N = 2e5 + 7;
ull base[N];
const ull BASE = 19260817;
int tol, root, m;
char s[N];

struct Splay {
    int ch[N][2], fa[N], sz[N];
    ull val[N], ha[N];
    inline bool chk(int x) {
        return ch[fa[x]][1] == x;
    }
    inline void pushup(int x) {
        sz[x] = sz[ch[x][0]] + sz[ch[x][1]] + 1;
        ha[x] = ha[ch[x][0]] + val[x] * base[sz[ch[x][0]]] + ha[ch[x][1]] * base[(sz[ch[x][0]] + 1)];
    }
    void rotate(int x) {
        int y = fa[x], z = fa[y], k = chk(x), w = ch[x][k ^ 1];
        ch[y][k] = w; fa[w] = y;
        ch[z][chk(y)] = x; fa[x] = z;
        ch[x][k ^ 1] = y; fa[y] = x;
        pushup(y); pushup(x);
    }
    void splay(int x, int goal = 0) {
        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 (1) {
            if (ch[cur][0] && k <= sz[ch[cur][0]])
                cur = ch[cur][0];
            else if (k > 1 + sz[ch[cur][0]])
                k -= 1 + sz[ch[cur][0]], cur = ch[cur][1];
            else 
                break;
        }
        return cur;
    }
    ull gethash(int l, int len) {
        int x = kth(l), y = kth(l + len + 1);
        splay(x), splay(y, x);
        return ha[ch[y][0]];
    }
    void insert(int x, int v) {
        int l = kth(x + 1), r = kth(x + 2);
        splay(l); splay(r, l);
        val[++tol] = v; fa[tol] = r; ch[r][0] = tol;
        splay(tol);
    }
    void update(int x, int v) {
        int l = kth(x), r = kth(x + 2);
        splay(l); splay(r, l);
        val[ch[r][0]] = v;
        splay(ch[r][0]);
    }
    int query(int x, int y) {
        int l = 0, r = tol - std::max(x, y) - 1;
        int ans = 0;
        while (l <= r) {
            int mid = l + r >> 1;
            if (gethash(x, mid) == gethash(y, mid)) ans = mid, l = mid + 1;
            else r = mid - 1;
        }
        return ans;
    }
    void init() {
        ch[1][1] = 2;
        root = 1, tol = 2;
        fa[2] = 1;
        pushup(2);
        pushup(1);
    }
} splay;

int main() {
    for (int i = base[0] = 1; i < N; i++)
        base[i] = base[i - 1] * BASE;
    scanf("%s%d", s + 1, &m);
    int n = strlen(s + 1);
    splay.init();
    for (int i = 1; i <= n; i++)
        splay.insert(i - 1, s[i]);
    for (int x, y; m--; ) {
        char s[3];
        scanf("%s", s);
        if (s[0] == 'Q') {
            scanf("%d%d", &x, &y);
            printf("%d\n", splay.query(x, y));
        } else if (s[0] == 'R') {
            char ss[3];
            scanf("%d%s", &x, s);
            splay.update(x, s[0]);
        } else {
            char ss[3];
            scanf("%d%s", &x, s);
            splay.insert(x, s[0]);
        }
    }
    return 0;
}
View Code

 

1015. [JSOI2008]星球大战starwar

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

#include 

const int N = 4e5 + 7;

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 = 0; i < n; i++)
        fa[i] = i;
    for (int i = 0; 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 = 1; i <= k; i++) 
        scanf("%d", a + i), vis[a[i]] = 1;
    res = n - k;
    for (int i = 0; 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] = 0;
        res++;
        for (int v: vec[u])
            if (!vis[v]) 
                merge(u, v);
    }
    printf("%d\n", res);
    for (int i = 1; i <= k; i++)
        printf("%d\n", ans[i]);
    return 0;
}
View Code

 

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

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

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

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

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

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

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

#include 
using namespace std;

const int N = 110;
const int MOD = 31011;

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 * 10];

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 = 1;
    for (int i = 1; i <= n; i++) {
        for (int k = i + 1; 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 = 0; i < m; i++) 
        edge[i].read();
    sort(edge, edge + m);
    for (int i = 1; i <= n; i++) pre[i] = i;
    int last = -1;
    int ans = 1;
    for (int k = 0; k <= m; k++) {
        if (last != edge[k].cost || k == m) {
            for (int i = 1; i <= n; i++) {
                if (vis[i]) {
                    int u = getfa(i, fa);
                    vec[u].push_back(i);
                    vis[i] = 0;
                }
            }
            for (int i = 1; i <= n; i++) {
                if (vec[i].size() > 1) {
                    memset(A, 0, sizeof(A));
                    int len = vec[i].size();
                    for (int a = 0; a < len; a++)
                        for (int b = a + 1; 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 - 1) % MOD;
                    for (int a = 0; a < len; a++)
                        pre[vec[i][a]] = i;
                }
            }
            for (int i = 1; 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] = 1;
        fa[getfa(u, fa)] = getfa(v, fa);
        G[u][v]++;
        G[v][u]++;
    }
    int flag = 0;
    for (int i = 2; i <= n; i++) {
        if (getfa(i, fa) != getfa(i - 1, fa)) {
            puts("0");
            return 0;
        }
    }
    if (!m) {
        puts("0");
        return 0;
    }
    printf("%d\n", ans);
    return 0;
}
View Code

 

1017. [JSOI2008]魔兽地图DotR

一种很妙的树上背包。

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

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

然后就可以xjb转移了。

#include 
#define pii pair
#define fi first
#define se second

const int N = 55;
const int INF = 0x3f3f3f3f;
const int M = 2e3 + 7;

int dp[N][110][M], f[N][M], n, m, mx[N], cost[N], val[N];
int degree[N];
std::vector vec[N];

void dfs(int u) {
    if (vec[u].empty()) {
        mx[u] = std::min(mx[u], m / cost[u]);
        for (int i = 0; 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[0][0] = 0;
    for (int c = mx[u]; ~c; c--) {
        int cur = 0;
        for (auto p: vec[u]) {
            int v = p.fi;
            cur++;
            for (int j = 0; j <= m; j++)
                for (int k = 0; k <= j; k++)
                    f[cur][j] = std::max(f[cur][j], f[cur - 1][j - k] + dp[v][c * p.se][k]);
        }
        for (int i = 0; i <= c; i++)
            for (int j = 0; 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 = 1; i <= n; i++) {
        scanf("%d", val + i);
        char s[3];
        scanf("%s", s);
        if (s[0] == '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 = 0;
    for (int i = 1; i <= n; i++)
        if (degree[i])
            flag = 1;
    int ans = 0;
    memset(dp, 0xcf, sizeof dp);
    if (flag) {
        for (int i = 1; i <= n; i++)
            if (!degree[i]) {
                dfs(i);
                for (int j = 0; j <= mx[i]; j++)
                    for (int k = 0; k <= m; k++)
                        ans = std::max(ans, dp[i][j][k]);
            }
    } else {
        f[0][0] = 0;
        for (int i = 1; i <= n; i++)
            for (int k = 0; k <= mx[i]; k++) 
                for (int j = k * cost[i]; j <= m; j++)
                    f[i][j] = std::max(f[i][j], f[i - 1][j - k * cost[i]] + k * val[i]);
        for (int i = 0; i <= m; i++)
            ans = std::max(ans, f[n][i]);
    }
    printf("%d\n", ans);
    return 0;
}
View Code

 

1018. [SHOI2008]堵塞的交通traffic

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

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

#include 
#define pii pair
#define fi first
#define se second

const int N = 2e5 + 7;
int fa[N], sz[N], top, n, q, cnt, id[3][N], tol;
std::pii st[N], query[N];
std::map<int, int> mp[N];

struct Node {
    int u, v, st, ed;
    Node(int u = 0, int v = 0, int st = 0, int ed = 0): u(u), v(v), st(st), ed(ed) {}
};
std::vector 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 &E) {
    std::vector L, R;
    int mid = l + r >> 1;
    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 + 1, 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 = 1; i <= n; i++)
        id[1][i] = ++tol, id[2][i] = ++tol;
    while (1) {
        char s[10];
        scanf("%s", s);
        if (s[0] == 'E') break;
        int x1, y1, x2, y2;
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        if (s[0] == '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 + 1, -1));
        } else if (s[0] == '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 == -1)
            p.ed = q;
    }
    for (int i = 1; i <= tol; i++)
        fa[i] = i, sz[i] = 1;
    solve(1, q, E);
    return 0;
}
View Code

 

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 
#define ll long long

const int N = 33;
ll f[N][3];
int g[N][3];
bool vis[3];

int main() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= 6; i++) {
        static char s[10];
        scanf("%s", s);
        int a = s[0] - 'A', b = s[1] - 'A';
        if (vis[a]) continue;
        vis[a] = 1;
        f[1][a] = 1;
        g[1][a] = b;
    }
    for (int j = 2; j <= n; j++) 
        for (int i = 0; i < 3; i++) {
            int a = g[j - 1][i], b = 3 - a - i;
            if (g[j - 1][a] == b) f[j][i] = f[j - 1][i] + 1 + f[j - 1][a], g[j][i] = b;
            else f[j][i] = f[j - 1][i] + 1 + f[j - 1][a] + 1 + f[j - 1][i], g[j][i] = a;
        }
    printf("%lld\n", f[n][0]);
    return 0;
}
View Code

 

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 

const double pi = acos(-1.0);
const int INF = 0x3f3f3f3f;
const double eps = 1e-9;
const int N = 55;

int dcmp(double x) {
    if (fabs(x) < eps) return 0;
    return x > 0 ? 1 : -1;
}

struct Point {
    double x, y;
    Point(double x = 0, double y = 0): 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)) <= 0; }
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[1000000 + 5];

struct Polygon {
    std::vector poly;
    int n;
    bool In(const Point &p) {
        int wn = 0;
        for (int i = 1; i <= n; i++) {
            if (On(p, poly[i], poly[i % n + 1])) return 1;
            int k = dcmp(Cross(poly[i % n + 1] - poly[i], p - poly[i]));
            int d1 = dcmp(poly[i].y - p.y);
            int d2 = dcmp(poly[i % n + 1].y - p.y);
            if (k > 0 && d1 <= 0 && d2 > 0) wn++;
            if (k < 0 && d2 <= 0 && d1 > 0) wn--;
        }
        if (wn) return 1;
        return 0;
    }
} 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 = 1; i <= m; i++)
        flight[i].read();
    for (int i = 1; i <= n; i++) {
        scanf("%d", &island[i].n);
        island[i].poly.resize(island[i].n + 1);
        for (int j = 1; j <= island[i].n; j++) 
            island[i].poly[j].read();
    }
}

bool check(const Point &p) {
    for (int i = 1; i <= n; i++)
        if (island[i].In(p)) return 1;
    return 0;
}

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)) <= 0) return near(b, Len(v2));
    if (dcmp(Dot(v1, v3)) >= 0) 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, 0);
    near ans1;
    ans1.dis = 1e10;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= island[i].n; j++) {
            near cur = DISPS(p, island[i].poly[j], island[i].poly[j % island[i].n + 1]);
            if (dcmp(ans1.dis - cur.dis) >= 0) ans1 = cur;
        }
    ans = std::max(ans, ans1.dis);
    return ans1;
}

const int M = 1e5;

void solve() {
    int head = 0, tail = 0;
    for (int i = 1; i < m; i++)
        queue[++tail] = Seg(flight[i], flight[i + 1]), Find(flight[i]);
    Find(flight[m]);
    while (head != tail) {
        Seg cur = queue[head = head % M + 1];
        Point p1 = Find(cur.a).p, p2 = Find(cur.b).p, l = cur.a, r = cur.b;
        while (Len(r - l) > 1e-4) {
            Point mid = (l + r) / 2;
            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 + 1] = Seg(cur.a, l);
            queue[tail = tail % M + 1] = Seg(l, cur.b);
        }
    }
}

int main() {
    freopen("in.txt", "r", stdin);
    init();
    solve();
    printf("%.2f\n", ans);
    return 0;
}
View Code

 

1021. [SHOI2008]Debt 循环的债务

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

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

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

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

#include 

const int N = 1100;
const int INF = 0x3f3f3f3f;
const int val[7] = {0, 100, 50, 20, 10, 5, 1};
int dp[7][N][N], sum, cnt[5][10], d[10], e[10];

inline bool chkmin(int &a, const int &b) {
    return a > b ? a = b, 1: 0;
}

int main() {
    int a, b, c;
    scanf("%d%d%d", &a, &b, &c);
    for (int i = 1; i <= 3; i++) 
        for (int j = 1; j <= 6; 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[1] - a + c, lastb = d[2] - b + a, lastc = sum - lasta - lastb;
    if (lasta < 0 || lastb < 0 || lastc < 0) {
        puts("impossible");
        return 0;
    }
    memset(dp, 0x3f, sizeof dp);
    dp[0][d[1]][d[2]] = 0;
    for (int i = 1; i <= 6; i++)
        for (int j = 0; j <= sum; j++)
            for (int k = 0; k + j <= sum; k++) if (dp[i - 1][j][k] != INF) {
                chkmin(dp[i][j][k], dp[i - 1][j][k]);
                for (int m = 0; m <= e[i]; m++)
                    for (int n = 0; n + m <= e[i]; n++) {
                        int difa = m - cnt[1][i], difb = n - cnt[2][i];
                        int jj = j + difa * val[i], kk = k + difb * val[i];
                        if (jj < 0 || kk < 0 || sum - jj - kk < 0) continue;
                        chkmin(dp[i][jj][kk], dp[i - 1][j][k] + (std::abs(difa) + std::abs(difb) + std::abs(difa + difb)) / 2);
                    }
            }
    if (dp[6][lasta][lastb] == INF) puts("impossible");
    else printf("%d\n", dp[6][lasta][lastb]);
    return 0;
}
View Code

 

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

Anti-Nim模板题。

先手必胜当且仅当

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

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

#include 

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

 

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 

namespace IO
{
    char buf[1 << 21], buf2[1 << 21], a[20], *p1 = buf, *p2 = buf, hh = '\n';
    int p, p3 = -1;
    void read() {}
    void print() {}
    inline int getc() {
        return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++;
    }
    inline void flush() {
        fwrite(buf2, 1, p3 + 1, stdout), p3 = -1;
    }
    template 
    inline void read(T &x, T2 &... oth) {
        T f = 1; x = 0;
        char ch = getc();
        while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getc(); }
        while (isdigit(ch)) { x = x * 10 + ch - 48; ch = getc(); }
        x *= f;
        read(oth...);
    }
    template 
    inline void print(T x, T2... oth) {
        if (p3 > 1 << 20) flush();
        if (x < 0) buf2[++p3] = 45, x = -x;
        do {
            a[++p] = x % 10 + 48;
        } while (x /= 10);
        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, 1 : 0; }
inline bool chkmax(int &x, int y) { return x < y ? x = y, 1 : 0; }

const int N = 1e5 + 7;
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] + 1;
    for (int i = v; i != u; i = fa[i])
        a[cnt--] = dp[i];
    a[1] = dp[u];
    cnt = dep[v] - dep[u] + 1;
    for (int i = 1; i <= cnt; i++)
        a[i + cnt] = a[i];
    int l = 0, r = 0;
    que[l = r = 1] = 1;
    for (int i = 2; i <= cnt + (cnt >> 1); i++) {
        if (i - que[l] > (cnt >> 1)) 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 = 2; i <= cnt; i++)
        chkmax(dp[u], a[i] + std::min(i - 1, cnt - i + 1));
}

void dfs(int u, int pre) {
    fa[u] = pre; dep[u] = dep[pre] + 1;
    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] + 1), chkmax(dp[u], dp[v] + 1);
    }
    for (int v: vec[u]) {
        if (dfn[u] < dfn[v] && fa[v] != u) 
            solve(u, v);
    }
}

int main() {
    read(n, m);
    for (int i = 1; i <= m; i++) {
        int k, u;
        read(k, u);
        for (int j = 2; j <= k; j++) {
            int v;
            read(v);
            vec[u].push_back(v);
            vec[v].push_back(u);
            u = v;
        }
    }
    dfs(1, 0);
    print(ans);
    IO::flush();
    return 0;
}
View Code

 

1024. [SCOI2009]生日快乐

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

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

#include 

double solve(double x, double y, int n) {
    if (x < y) std::swap(x, y);
    if (n == 1) return x / y;
    double ans = 1e9;
    for (int i = 1; i <= n / 2; 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 0;
}
View Code

 

1026. [SCOI2009]windy数

数位DP裸题啦。

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

 

1027. [JSOI2007]合金

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

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

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

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

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

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

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

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

所以就不用特判啦。

#include 
#define db double

const db eps = 1e-9;
inline int sign(db k) { return k < -eps ? -1 : 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 == -1 : cmp(y, rhs.y) == -1; }
    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) == 1 || (sign(y) == 0 && sign(x) >= 0); }
    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) != 0;
}
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) == -1 || cmp(r2, l1) == -1);
}
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) <= 0 
        && crossOp(q1, q2, p1) * crossOp(q1, q2, p2) <= 0;
}
bool isSS_strict(const P &p1, const P &p2, const P &q1, const P &q2) {
    return crossOp(p1, p2, q1) * crossOp(p1, p2, q2) < 0
        && crossOp(q1, q2, p1) * crossOp(q1, q2, p2) < 0;
}
bool isMiddle(db a, db m, db b) {
    return sign(a - m) == 0 || sign(b - m) == 0 || (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) == 0 && isMiddle(p1, q, p2);
}
bool onSeg_strict(const P &p1, const P &p2, const P &q) {
    return crossOp(p1, p2, q) == 0 && sign((q - p1).dot(p1 - p2)) * sign((q - p2).dot(p1 - p2)) < 0;
}
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) * 2 - 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 0;
    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 = 550;
int n, m;
P material[N], alloy[N];

bool spj() {
    for (int i = 1; i < n; i++) 
        if (!(alloy[i] == alloy[i - 1])) 
            return 0;
    for (int i = 0; i < m; i++)
        if (alloy[0] == material[i]) 
            return 1;
    return 0;
}

const int INF = 0x3f3f3f3f;
int dis[N][N];

inline bool chkmin(int &a, int b) { return a > b ? a = b, 1 : 0; }

int floyd() {
    for (int k = 0; k < m; k++)
        for (int i = 0; i < m; i++)
            if (dis[i][k] < INF) 
                for (int j = 0; j < m; j++)
                    chkmin(dis[i][j], dis[i][k] + dis[k][j]);
    int ans = INF;
    for (int i = 0; i < m; i++)
        chkmin(ans, dis[i][i]);
    return ans;
}

void addedge(int p, int q) {
    for (int i = 0; i < n; i++) {
        int k = crossOp(material[p], material[q], alloy[i]);
        if (k > 0) continue;
        if (k < 0) return;
        if (!onSeg(material[p], material[q], alloy[i])) return;
    }
    dis[p][q] = 1;
}

int main() {
    scanf("%d%d", &m, &n);
    for (int i = 0; i < m; i++) {
        material[i].read();
        db x;
        scanf("%lf", &x);
    }
    for (int i = 0; i < n; i++) {
        alloy[i].read();
        db x;
        scanf("%lf", &x);
    }
    if (spj()) {
        puts("1");
        return 0;
    }
    memset(dis, 0x3f, sizeof dis);
    for (int i = 0; i < m; i++)
        for (int j = 0; j < m; j++) 
            if (i != j)
                addedge(i, j);
    int ans = floyd();
    printf("%d\n", ans == INF ? -1 : ans);
    return 0;
}
View Code

 

1028. [JSOI2007]麻将

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

#include 
#include 
#include 

const int N = 407;
int a[N], cnt[N], n, m, temp[N];

bool solve() {
    for (int i = 1; i <= n; i++) {
        if (cnt[i] < 2) continue;
        bool flag = 1;
        cnt[i] -= 2;
        for (int j = 1; j <= n + 2; j++)
            temp[j] = cnt[j];
        for (int j = 1; j <= n + 2; j++) {
            if (temp[j] < 0) { flag = 0; break; }
            temp[j] %= 3;
            temp[j + 1] -= temp[j];
            temp[j + 2] -= temp[j];
        }
        cnt[i] += 2;
        if (flag) return 1;
    }
    return 0;
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= 3 * m + 1; i++) {
        int x;
        scanf("%d", &x);
        cnt[x]++;
    }
    std::vector<int> ans;
    for (int i = 1; i <= n; i++) {
        cnt[i]++;
        if (solve())
            ans.push_back(i);
        cnt[i]--;
    }
    if (ans.size() == 0) puts("NO");
    else for (int x: ans)
        printf("%d ", x);
    puts("");
    return 0;
}
View Code

 

1029. [JSOI2007]建筑抢修

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

#include 
#define ll long long

const int N = 2e5 + 7;
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 = 1; i <= n; i++)
        scanf("%lld%lld", &p[i].x, &p[i].y);
    ll cur = 0;
    std::sort(p + 1, p + 1 + n);
    std::priority_queue<int> que;
    que.push(0);
    int ans = 0;
    for (int i = 1; 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 0;
}
View Code

 

1030. [JSOI2007]文本生成器

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

#include 

const int N = 5000;
const int sz = 26;
const int MOD = 10007;

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][110];
    void init() {
        tol = 0;
        memset(dp, 0, sizeof(dp));
        newnode();
    }
    int newnode() {
        for (int i = 0; i < sz; i++)
            trie[tol][i] = 0;
        fail[tol] = flag[tol] = last[tol] = 0;
        return tol++;
    }
    void insert(char *s) {
        int n = strlen(s), cur = 0;;
        for (int i = 0; i < n; i++) {
            int id = s[i] - 'A';
            if (!trie[cur][id]) trie[cur][id] = newnode();
            cur = trie[cur][id];
        }
        flag[cur] = 1;
    }
    int build() {
        std::queue<int> que;
        for (int i = 0; i < sz; i++)
            if (trie[0][i]) que.push(trie[0][i]);
        while (!que.empty()) {
            int u = que.front(); que.pop();
            flag[u] |= flag[fail[u]];
            for (int i = 0; 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[0][0] = 1;
        for (int j = 0; j < m; j++) 
            for (int i = 0; i < tol; i++) if (dp[i][j] && !flag[i])
                for (int k = 0; k < sz; k++) {
                        M(dp[trie[i][k]][j + 1] += dp[i][j]);
              }
        int ans = 0;
        for (int i = 0; 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 = 1;
    while (b) {
        if (b & 1) ans = ans * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return ans;
}

int main() {
    while (~scanf("%d%d", &n, &m)) {
        ac.init();
        for (int i = 0; i < n; i++)
            scanf("%s", s), ac.insert(s);
        printf("%d\n", (qp(26, m) - ac.build() + MOD) % MOD);
    }
    return 0;
}
View Code

 

1031. [JSOI2007]字符加密Cipher

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

#include 

const int N = 2e5 + 7;
char s[N];

namespace SA {
    int sa[N], rk[N], fir[N], sec[N], c[N], height[N];
    void build(int len, int num = 130) {
        register int i, j, k;
        for (i = 1; i <= num; i++) c[i] = 0;
        for (i = 1; i <= len; i++) ++c[fir[i] = s[i]];
        for (i = 1; i <= num; i++) c[i] += c[i - 1];
        for (i = len; i >= 1; i--) sa[c[fir[i]]--] = i;
        for (k = 1; k <= len; k <<= 1) {
            int cnt = 0;
            for (i = len - k + 1; i <= len; i++) sec[++cnt] = i;
            for (i = 1; i <= len; i++) if (sa[i] > k) sec[++cnt] = sa[i] - k;
            for (i = 1; i <= num; i++) c[i] = 0;
            for (i = 1; i <= len; i++) ++c[fir[i]];
            for (i = 1; i <= num; i++) c[i] += c[i - 1];
            for (i = len; i >= 1; i--) sa[c[fir[sec[i]]]--] = sec[i], sec[i] = 0;
            std::swap(fir, sec);
            fir[sa[1]] = 1; cnt = 1;
            for (i = 2; i <= len; i++)
                fir[sa[i]] = (sec[sa[i]] == sec[sa[i - 1]] && sec[sa[i] + k] == sec[sa[i - 1] + k]) ? cnt : ++cnt;
            if (cnt == len) break;
            num = cnt;
        }
        k = 0;
        for (i = 1; i <= len; i++) rk[sa[i]] = i;
        for (i = 1; i <= len; i++) {
            if (rk[i] == 1) continue;
            if (k) k--;
            j = sa[rk[i] - 1];
            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 + 1);
    int len = strlen(s + 1);
    for (int i = 1; i <= len; i++)
        s[i + len] = s[i];
    build(len * 2);
    for (int i = 1; i <= 2 * len; i++) {
        if (sa[i] <= len + 1 && !vis[id(sa[i], len)]) {
            ans[++cnt] = s[sa[i] + len - 1];
            vis[id(sa[i], len)] = 1;
            if (cnt == len) {
                puts(ans + 1);
                return 0;
            }
        }
    }
    return 0;
}
View Code

 

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 

const int INF = 0x3f3f3f3f;
const int N = 550;
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 = 1; i <= n; i++)
        scanf("%d", a + i);
    int cur = 1;
    for (int i = 2; i <= n; i++) {
        if (a[i] != a[i - 1]) {
            color[++cnt] = a[i - 1];
            num[cnt] = cur;
            cur = 1;
        } else {
            cur++;
        }
    }
    color[++cnt] = a[n];
    num[cnt] = cur;
    for (int i = 1; i <= cnt; i++)
        f[i][i] = num[i] > 1 ? 1 : 2;
    for (int len = 2; len <= cnt; len++) 
        for (int l = 1; l <= cnt; l++) {
            int r = l + len - 1;
            if (r > cnt) break;
            if (color[l] == color[r])
                f[l][r] = f[l + 1][r - 1] + ((num[l] + num[r]) > 2 ? 0 : 1);
            for (int k = l; k < r; k++)
                f[l][r] = std::min(f[l][r], f[l][k] + f[k + 1][r]);
        }
    printf("%d\n", f[1][cnt]);
    return 0;
}
View Code

 

1034. [ZJOI2008]泡泡堂BNB

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

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

#include 

const int N = 1e5 + 7;
int a[N], b[N];
std::multiset<int> st1, st2;
bool vis[N];

int solve(int A[], int B[], int n) {
    int ans = 0;
    int l1 = 1, l2 = 1, r1 = n, r2 = n;
    while (l1 <= r1 && l2 <= r2) {
        if (A[l1] > B[l2]) ans += 2, l1++, l2++;
        else if (A[r1] > B[r2]) ans += 2, r1--, r2--;
        else ans += (A[l1] == B[r2]) ? 1 : 0, l1++, r2--;
    }
    return ans;
}

int main() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", a + i);
    for (int i = 1; i <= n; i++)
        scanf("%d", b + i);
    std::sort(a + 1, a + 1 + n);
    std::sort(b + 1, b + 1 + n);
    printf("%d %d\n", solve(a, b, n), 2 * n - solve(b, a, n));
}
View Code

 

1036. [ZJOI2008]树的统计Count

树剖板子题。

#include 
using namespace std;

template
inline void read(T &x) {
    x = 0; T f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - 48; ch = getchar(); }
    x *= f;    
}

const int INF = 0x3f3f3f3f;
const int N = 30010;
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 * 2];
int head[N], cnt;

struct Seg {
    #define lp p << 1
    #define rp p << 1 | 1
    int sum[N << 2], mx[N << 2];
    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 >> 1;
        build(lp, l, mid);
        build(rp, mid + 1, 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 >> 1;
        if (pos <= mid) update(lp, l, mid, pos, val);
        else update(rp, mid + 1, 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 = 0;
        int mid = l + r >> 1;
        if (x <= mid) ans += query1(lp, l, mid, x, y);
        if (y > mid) ans += query1(rp, mid + 1, 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 >> 1;
        if (x <= mid) ans = max(ans, query2(lp, l, mid, x, y));
        if (y > mid) ans = max(ans, query2(rp, mid + 1, 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] = 1;
    for (int i = head[u]; i; i = e[i].ne) {
        int v = e[i].v;
        if (v == pre) continue;
        dfs1(v, u, d + 1);
        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 = 0;
    while (top[u] != top[v]) {
        if (dep[top[u]] < dep[top[v]]) swap(u, v);
        ans += seg.query1(1, 1, n, dfn[top[u]], dfn[u]);
        u = fa[top[u]];
    }
    if (dep[u] > dep[v]) swap(u, v);
    ans += seg.query1(1, 1, 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(1, 1, n, dfn[top[u]], dfn[u]));
        u = fa[top[u]];
    }
    if (dep[u] > dep[v]) swap(u, v);
    ans = max(ans, seg.query2(1, 1, n, dfn[u], dfn[v]));
    return ans;
}

char s[20];

int main() {
    read(n);
    for (int i = 1; i < n; i++) {
        int u, v;
        read(u), read(v);
        add(u, v);
    }
    for (int i = 1; i <= n; i++) read(w[i]);
    dfs1(1, 0, 1);
    dfs2(1, 1);
    seg.build(1, 1, n);
    read(q);
    while (q--) {
        scanf("%s", s);
        int u, v;
        read(u), read(v);
        if (s[1] == 'M') {
            printf("%d\n", solve2(u, v));
        } else if (s[1] == 'S') {
            printf("%d\n", solve1(u, v));
        } else {
            seg.update(1, 1, n, dfn[u], v);
        }
    }
    return 0;
}
View Code

 

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 

const int MOD = 12345678;
const int N = 157;
int dp[N][N][22][22];

void M(int &x) {
    if (x >= MOD) x -= MOD;
    if (x < 0) x += MOD;
}

int main() {
    int n, m, k;
    scanf("%d%d%d", &n, &m, &k);
    dp[0][0][0][0] = 1;
    for (int i = 0; i <= n; i++)
        for (int j = 0; j <= m; j++)
            for (int o = 0; o <= k; o++)
                for (int p = 0; p <= k; p++) if (dp[i][j][o][p]) {
                    if (i < n && o < k)
                        M(dp[i + 1][j][o + 1][std::max(p - 1, 0)] += dp[i][j][o][p]);
                    if (j < m && p < k)
                        M(dp[i][j + 1][std::max(o - 1, 0)][p + 1] += dp[i][j][o][p]);
                }
    int ans = 0;
    for (int i = 0; i <= k; i++)
        for (int j = 0; j <= k; j++)
            M(ans += dp[n][m][i][j]);
    printf("%d\n", ans);
    return 0;
}
View Code

 

1038. [ZJOI2008]瞭望塔

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

#include 

#define db double

const db eps = 1e-9;
inline int sign(db k) { return k < -eps ? -1 : 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 == -1 : cmp(y, rhs.y) == -1; }
  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) == 1 || (sign(y) == 0 && sign(x) >= 0); }
  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[2];
  L() {}
  L(const P &p0, const P &p1) {
    ps[0] = p0; ps[1] = p1;
  }
  P &operator[](int i) { return ps[i]; }
  P dir() { return ps[1] - ps[0]; }
  bool include(const P &p) { return sign((ps[1] - ps[0]).det(p - ps[0])) > 0; }
  L push() { // push eps outawrd
    const db Eps = 1e-6;
    P delta = (ps[1] - ps[0]).normal() * Eps;
    return {ps[0] - delta, ps[1] - 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) != 0;
}
// 直线交点
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[0], l1[1], l2[0], l2[1]);
}
/***** 线段相交 *****/
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) == -1 || cmp(r2, l1) == -1);
}
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) <= 0
         && crossOp(q1, q2, p1) * crossOp(q1, q2, p2) <= 0;
}
bool isSS_strict(const P &p1, const P &p2, const P &q1, const P &q2) {
  return crossOp(p1, p2, q1) * crossOp(p1, p2, q2) < 0
         && crossOp(q1, q2, p1) * crossOp(q1, q2, p2) < 0;
}
/***************/
/***** 点在线段上判定 *****/
bool isMiddle(db a, db m, db b) {
  return sign(a - m) == 0 || sign(b - m) == 0 || (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) == 0 && isMiddle(p1, q, p2);
}
bool onSeg_strict(const P &p1, const P &p2, const P &q) {
  return crossOp(p1, p2, q) == 0 && sign((q - p1).dot(p1 - p2)) * sign((q - p2).dot(p1 - p2)) < 0;
}
/*******************/
// 投影
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) * 2 - 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 0;
  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

&ps) { db ans = 0; for (int i = 0, n = ps.size(); i < n; i++) ans += ps[i].det(ps[(i + 1) % n]); return ans; } // 点包含 2: inside 1: onSeg 0: outside int contain(const std::vector

&ps, const P &p) { int n = ps.size(), ret = 0; for (int i = 0; i < n; i++) { P u = ps[i], v = ps[(i + 1) % n]; if (onSeg(u, v, p)) return 1; if (cmp(u.y, v.y) <= 0) std::swap(u, v); if (cmp(p.y, u.y) > 0 || cmp(p.y, v.y) <= 0) continue; ret ^= crossOp(p, u, v) > 0; } return ret * 2; } // 凸包 std::vector

convexHull(std::vector

ps) { int n = ps.size(); if (n <= 1) return ps; std::sort(ps.begin(), ps.end()); std::vector

qs(n * 2); int k = 0; for (int i = 0; i < n; qs[k++] = ps[i++]) while (k > 1 && crossOp(qs[k - 2], qs[k - 1], ps[i]) <= 0) --k; for (int i = n - 2, t = k; i >= 0; qs[k++] = ps[i--]) while (k > t && crossOp(qs[k - 2], qs[k - 1], ps[i]) <= 0) --k; qs.resize(k - 1); return qs; } std::vector

convexHullNonStrict(std::vector

ps) { int n = ps.size(); if (n <= 1) return ps; std::sort(ps.begin(), ps.end()); std::vector

qs(n * 2); int k = 0; for (int i = 0; i < n; qs[k++] = ps[i++]) while (k > 1 && crossOp(qs[k - 2], qs[k - 1], ps[i]) < 0) --k; for (int i = n - 2, t = k; i >= 0; qs[k++] = ps[i--]) while (k > t && crossOp(qs[k - 2], qs[k - 1], ps[i]) < 0) --k; qs.resize(k - 1); return qs; } // 点集直径 db convexDiameter(const std::vector

&ps) { int n = ps.size(); if (n <= 1) return 0; int is = 0, js = 0; for (int k = 1; 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 + 1) % n] - ps[i]).det(ps[(j + 1) % n] - ps[j]) >= 0) (++j) %= n; else (++i) %= n; ret = std::max(ret, ps[i].distTo(ps[j])); } while (i != is || j != js); return ret; } // convecCut std::vector

convexCut(const std::vector

&ps, const P &q1, const P &q2) { std::vector

qs; int n = ps.size(); for (int i = 0; i < n; i++) { P p1 = ps[i], p2 = ps[(i + 1) % n]; int d1 = crossOp(q1, q2, p1), d2 = crossOp(q1, q2, p2); if (d1 >= 0) qs.push_back(p1); if (d1 * d2 < 0) qs.push_back(isLL(p1, p2, q1, q2)); } return qs; } // min_dis db min_dis(const std::vector

&ps, int l, int r) { if (r - l <= 5) { 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 >> 1; db ret = std::min(min_dis(ps, l, mid), min_dis(ps, mid, r)); std::vector

qs; for (int i = l; i < r; i++) if (cmp(fabs(ps[i].x - ps[mid].x), ret) <= 0) qs.push_back(ps[i]); std::sort(qs.begin(), qs.end(), [](const P & a, const P & b) -> bool { return cmp(a.y, b.y) < 0; }); for (int i = 1; i < qs.size(); i++) for (int j = i - 1; j >= 0 && cmp(qs[j].y, qs[i].y - ret) >= 0; 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) == 1) return 4; // 相离 if (cmp(d, r1 + r2) == 0) return 3; // 外切 if (cmp(d, fabs(r1 - r2)) == 1) return 2; // 相交 if (cmp(d, fabs(r1 - r2)) == 0) return 1; // 内切 return 0; } bool parallel(L l0, L l1) { return sign(l0.dir().det(l1.dir())) == 0; } bool sameDir(L l0, L l1) { return parallel(l0, l1) && sign(l0.dir().dot(l1.dir())) == 1; } bool cmp(P a, P b) { if (a.quad() != b.quad()) { return a.quad() < b.quad(); } else { return sign(a.det(b)) > 0; } } bool operator < (L l0, L l1) { if (sameDir(l0, l1)) { return l1.include(l0[0]); } 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 + 7; L que[N]; std::vector halfPlaneIS(std::vector &l) { std::sort(l.begin(), l.end()); int head = 0, tail = 0; for (int i = 0; i < l.size(); i++) { if (i && sameDir(l[i], l[i - 1])) continue; while (tail - head > 1 && !check(que[tail - 2], que[tail - 1], l[i])) tail--; while (tail - head > 1 && !check(que[head + 1], que[head], l[i])) head++; que[tail++] = l[i]; } while (tail - head > 2 && !check(que[tail - 2], que[tail - 1], que[0])) tail--; while (tail - head > 2 && !check(que[1], que[0], que[tail - 1])) head++; std::vector ans; for (int i = head; i < tail; i++) ans.push_back(que[i]); return ans; } db gety(P p, std::vector

point, std::vector line) { int n = point.size(); if (sign(p.x - point[0].x) <= 0) return isLL(line[0], L(p, P(p.x, p.y + 10))).y; if (sign(p.x - point[n - 1].x) >= 0) return isLL(line[n], L(p, P(p.x, p.y + 10))).y; for (int i = 0; i < n - 1; i++) { if (isMiddle(point[i].x, p.x, point[i + 1].x)) return isLL(line[i + 1], L(p, P(p.x, p.y + 10))).y; } return 1e11; } db getyy(P p, std::vector

point) { for (int i = 0, sz = point.size(); i < sz - 1; i++) { if (isMiddle(point[i].x, p.x, point[i + 1].x)) return isLL(point[i], point[i + 1], p, P(p.x, p.y + 10)).y; } return 1e11; } int main() { int n; scanf("%d", &n); if (n <= 2) { puts("0"); return 0; } std::vector

p(n); for (int i = 0; i < n; i++) scanf("%lf", &p[i].x); for (int i = 0; i < n; i++) scanf("%lf", &p[i].y); std::vector l; for (int i = 0; i < n - 1; i++) l.push_back(L(p[i], p[i + 1])); std::vector half = halfPlaneIS(l); db ans = 1e10; std::vector

ss; for (int i = 0, sz = half.size(); i < sz - 1; i++) ss.push_back(isLL(half[i], half[i + 1])); for (int i = 0; i < n; i++) { ans = std::min(ans, std::fabs(gety(p[i], ss, half) - p[i].y)); } for (int i = 0, sz = half.size(); i < sz - 1; i++) { P pp = ss[i]; ans = std::min(ans, std::fabs(getyy(pp, p) - pp.y)); } printf("%.3f\n", ans); return 0; }

View Code

 

1039. [ZJOI2008]无序运动Movement

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

#include 

namespace IO {
    char buf[1 << 21], buf2[1 << 21], a[20], *p1 = buf, *p2 = buf, hh = '\n';
    int p, p3 = -1;
    void read() {}
    void print() {}
    inline int getc() {
        return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++;
    }
    inline void flush() {
        fwrite(buf2, 1, p3 + 1, stdout), p3 = -1;
    }
    template 
    inline void read(T &x, T2 &... oth) {
        T f = 1; x = 0;
        char ch = getc();
        while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getc(); }
        while (isdigit(ch)) { x = x * 10 + ch - 48; ch = getc(); }
        x *= f;
        read(oth...);
    }
    template 
    inline void print(T x, T2... oth) {
        if (p3 > 1 << 20) flush();
        if (x < 0) buf2[++p3] = 45, x = -x;
        do {
            a[++p] = x % 10 + 48;
        } while (x /= 10);
        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 + 7;
int n, m, par[N], tol, ans[N];
std::vector

point[N], all; std::vector<int> flag[N]; std::mapint> 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 = 0; if ((B - A).det(C - B) > 0) crs = 1; else if ((B - A).det(C - B) < 0) crs = -1; return {lena, lenb, lenc, crs}; } void done(std::vector

vec, int id) { if (vec.size() < 3) { ans[id] = n - vec.size() + 1; return; } par[id] = 1; int rt = 0; for (int i = 0; i < vec.size() - 2; i++) { Node o = getnode(vec[i], vec[i + 1], vec[i + 2]); if (o.dir) par[id] = 0; 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[0]) 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

&vec) { int rt = 0; for (int i = 0; i < n - 2; i++) { Node node = getnode(vec[i], vec[i + 1], vec[i + 2]); 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 = 1; i <= m; i++) { int k; IO::read(k); point[i].resize(k); for (int j = 0; j < k; j++) point[i][j].read(); done(point[i], i); } build(); all.resize(n); for (int i = 0; i < n; i++) all[i].read(); solve(all); for (int i = 0; i < n; i++) all[i].y *= -1; solve(all); for (int i = 1; i <= tol; i++) for (int u: flag[i]) ans[u] += cnt[i] / (par[u] + 1); for (int i = 1; i <= m; i++) IO::print(ans[i]); IO::flush(); return 0; }

View Code

 

1040. [ZJOI2008]骑士

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

#include 

namespace IO {
    char buf[1 << 21], buf2[1 << 21], a[20], *p1 = buf, *p2 = buf, hh = '\n';
    int p, p3 = -1;
    void read() {}
    void print() {}
    inline int getc() {
        return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++;
    }
    inline void flush() {
        fwrite(buf2, 1, p3 + 1, stdout), p3 = -1;
    }
    template 
    inline void read(T &x, T2 &... oth) {
        T f = 1; x = 0;
        char ch = getc();
        while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getc(); }
        while (isdigit(ch)) { x = x * 10 + ch - 48; ch = getc(); }
        x *= f;
        read(oth...);
    }
    template 
    inline void print(T x, T2... oth) {
        if (p3 > 1 << 20) flush();
        if (x < 0) buf2[++p3] = 45, x = -x;
        do {
            a[++p] = x % 10 + 48;
        } while (x /= 10);
        do {
            buf2[++p3] = a[p];
        } while (--p);
        buf2[++p3] = hh;
        print(oth...);
    }
}

#define ll long long
const int N = 1e6 + 7;
int n, fa[N], root, _root;
ll dp[N][2], val[N];
bool vis[N];
int head[N];
struct E {
    int v, ne;
} e[N << 1];
int cnt = 1, 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] = 1;
    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][0] = 0; dp[u][1] = val[u];
    for (int i = head[u]; i; i = e[i].ne) {
        int v = e[i].v;
        if (v == f || i == ban || i == (ban ^ 1)) continue;
        DP(v, u);
        dp[u][0] += std::max(dp[v][0], dp[v][1]);
        dp[u][1] += dp[v][0];
    }
}

int main() {
    IO::read(n);
    for (int i = 1, u; i <= n; i++)
        IO::read(val[i], u), add(u, i), add(i, u);
    ll ans = 0;
    for (int i = 1; i <= n; i++) if (!vis[i]) {
        dfs(i, -1);
        DP(root, -1);
        ll temp = dp[root][0];
        DP(_root, -1);
        temp = std::max(temp, dp[_root][0]);
        ans += temp;
    }
    printf("%lld\n", ans);
    return 0;
}
View Code

 

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 

#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 = 0;
    for (ll g = 1; g * g <= 2 * r; g++) {
        if ((2 * r) % g) continue;
        for (ll a = 1; a * a < g / 2; a++) {
            ll b = sqrt(g - a * a + 0.5);
            if (a * a + b * b == g && gcd(a, b) == 1) ans++;
        }
        for (ll a = 1; a * a < r / g; a++) {
            ll b = sqrt(2 * r / g - a * a + 0.5);
            if (a * a + b * b == 2 * r / g && gcd(a, b) == 1) ans++;
        }
    }
    printf("%d\n", ans * 4 + 4);
    return 0;
}
View Code

 

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 
#define ll long long

const int N = 1e5 + 7;
ll dp[N];
int w[10], T, d[10];

int get(int x) {
    int cnt = 0;
    while (x) {
        cnt++;
        x &= (x - 1);
    }
    return cnt;
}

int main() {
    for (int i = 0; i < 4; i++)
        scanf("%d", w + i);
    scanf("%d", &T);
    dp[0] = 1;
    for (int i = 0; i < 4; i++)
        for (int j = w[i]; j <= N - 7; j++)
            dp[j] += dp[j - w[i]];
    while (T--) {
        for (int i = 0; i < 4; i++)
            scanf("%d", d + i);
        int s;
        scanf("%d", &s);
        ll ans = 0;
        for (int i = 0; i < 16; i++) {
            int sz = get(i), sum = s;
            int c = sz & 1 ? -1 : 1;
            for (int j = 0; j < 4; j++)
                if (i >> j & 1)
                    sum -= w[j] * (d[j] + 1);
            if (sum >= 0)
                ans += c * dp[sum];
        }
        printf("%lld\n", ans);
    }
    return 0;
}
View Code

 

1044. [HAOI2008]木棍分割

 第一问二分傻逼题。

第二问前缀和优化一下DP

#include 

const int N = 5e4 + 7;
const int MOD = 10007;
int n, m, a[N], dp[N][2], sum[N];
int dp_sum[N][2];

void M(int &x) {
    if (x >= MOD) x -= MOD;
    if (x < 0) x += MOD;
}

inline bool chkmax(int &a, int b) {
    return a < b ? a = b, 1 : 0;
}

inline bool chkmin(int &a, int b) {
    return a > b ? a = b, 1 : 0;
}

bool check(int mid) {
    int cnt = 1, sum = 0;
    for (int i = 1; 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 = 0, r = 0;
    for (int i = 1; i <= n; i++)
        scanf("%d", a + i), chkmax(l, a[i]), r += a[i], sum[i] = sum[i - 1] + a[i];
    int ans = 0;
    while (l <= r) {
        int mid = l + r >> 1;
        if (check(mid)) ans = mid, r = mid - 1;
        else l = mid + 1;
    }
    for (int i = 1; i <= n; i++)
        dp[i][1] = (sum[i] <= ans ? 1 : 0), M(dp_sum[i][1] = dp_sum[i - 1][1] + dp[i][1]);
    int ans2 = dp[n][1];
    for (int j = 2; j <= m; j++) {
        int cur = j & 1;
        int last = 0;
        for (int i = 1; i <= n; i++) {
            dp[i][cur] = 0;
            while (i > last && sum[i] - sum[last] > ans) last++;
            M(dp[i][cur] += dp_sum[i - 1][cur ^ 1] - dp_sum[std::max(last - 1, 0)][cur ^ 1]);
            M(dp_sum[i][cur] = dp_sum[i - 1][cur] + dp[i][cur]);
        }
        
        M(ans2 += dp[n][cur]);
    }
    printf("%d %d\n", ans, ans2);
    return 0;
}
View Code

 

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 
typedef long long ll;

const int N = 1e6 + 7;
ll a[N];

int main() {
    int n;
    scanf("%d", &n);
    ll sum = 0;
    for (int i = 1; i <= n; i++)
        scanf("%lld", a + i), sum += a[i];
    sum /= n;
    for (int i = 1; i <= n; i++)
        a[i] = a[i - 1] + sum - a[i];
    std::sort(a + 1, a + 1 + n);
    sum = a[(n + 1) / 2];
    ll ans = 0;
    for (int i = 1; i <= n; i++)
        ans += std::abs(a[i] - sum);
    printf("%lld\n", ans);
    return 0;
}
View Code

 

1046. [HAOI2007]上升序列

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

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

#include 

const int N = 1e4 + 7;

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 = 0;
        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 = 1; i <= n; i++)
        scanf("%d", a + i), v[i] = a[i];
    std::sort(v + 1, v + 1 + n);
    cnt = std::unique(v + 1, v + 1 + n) - v - 1;
    int ans = 0;
    for (int i = n; i >= 1; i--) {
        a[i] = std::lower_bound(v + 1, v + 1 + cnt, a[i]) - v;
        a[i] = cnt - a[i] + 1;
        dp[i] = bit.query(a[i] - 1) + 1;
        bit.add(a[i], dp[i]);
        ans = std::max(ans, dp[i]);
        a[i] = cnt - a[i] + 1;
    }
    int m;
    scanf("%d", &m);
    for (int i = 1; i <= m; i++) {
        int len;
        scanf("%d", &len);
        if (len > ans) {
            puts("Impossible");
            continue;
        }
        int last = 0;
        for (int i = 1; i <= n; i++) {
            if (!len) break;
            if (dp[i] >= len && a[i] > last) {
                printf("%d%c", v[a[i]], " \n"[len == 1]);
                len--;
                last = a[i];
            }
        }
    }
    return 0;
}
View Code

 

1047. [HAOI2007]理想的正方形

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

#include 

namespace IO {
char buf[1 << 21], buf2[1 << 21], a[20], *p1 = buf, *p2 = buf, hh = '\n';
int p, p3 = -1;
void read() {}
void print() {}
inline int getc() {
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++;
}
inline void flush() {
    fwrite(buf2, 1, p3 + 1, stdout), p3 = -1;
}
template 
inline void read(T &x, T2 &... oth) {
    T f = 1; x = 0;
    char ch = getc();
    while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getc(); }
    while (isdigit(ch)) { x = x * 10 + ch - 48; ch = getc(); }
    x *= f;
    read(oth...);
}
template 
inline void print(T x, T2... oth) {
    if (p3 > 1 << 20) flush();
    if (x < 0) buf2[++p3] = 45, x = -x;
    do {
        a[++p] = x % 10 + 48;
    } while (x /= 10);
    do {
        buf2[++p3] = a[p];
    } while (--p);
    buf2[++p3] = hh;
    print(oth...);
}
}

const int N = 1007;
int A[N][N], a, b, n, mx[N][N], mn[N][N];

int main() {
    IO::read(a, b, n);
    for (int i = 1; i <= a; i++)
        for (int j = 1; j <= b; j++)
            IO::read(A[i][j]);
    static int que1[N], que2[N];
    for (int i = 1; i <= a; i++) {
        int l1 = 1, r1 = 1, l2 = 1, r2 = 1;
        for (int j = 1; j <= b; j++) {
            {
                while (l1 < r1 && que1[l1] <= j - n) l1++;
                while (l1 < r1 && A[i][que1[r1 - 1]] >= 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 - 1]] <= 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 = 1, r1 = 1, l2 = 1, r2 = 1;
        for (int i = 1; i <= a; i++) {
            {
                while (l1 < r1 && que1[l1] <= i - n) l1++;
                while (l1 < r1 && mn[que1[r1 - 1]][j] >= mn[i][j]) r1--;
                que1[r1++] = i;
            }
            {
                while (l2 < r2 && que2[l2] <= i - n) l2++;
                while (l2 < r2 && mx[que2[r2 - 1]][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 0;
}
View Code

 

1048. [HAOI2007]分割矩阵

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

#include 

typedef double db;

const db eps = 1e-7;
const int N = 12;
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 0;
    return x > 0 ? 1 : -1;
}

db sqr(db x) {
    return x * x;
}

db Sum(int x1, int x2, int y1, int y2) {
    return sum[x2][y2] + sum[x1 - 1][y1 - 1] - sum[x1 - 1][y2] - sum[x2][y1 - 1];
}

db solve(int x1, int x2, int y1, int y2, int c) {
    db &ans = f[x1][x2][y1][y2][c];
    if (ans != -1) return ans;
    if (c == 1) return ans = sqr(Sum(x1, x2, y1, y2) - ave);
    ans = 1e9;
    for (int i = x1; i < x2; i++)
        for (int cc = 1; cc < c; cc++)
            ans = std::min(ans, solve(x1, i, y1, y2, cc) + solve(i + 1, x2, y1, y2, c - cc));
    for (int i = y1; i < y2; i++)
        for (int cc = 1; cc < c; cc++)
            ans = std::min(ans, solve(x1, x2, y1, i, cc) + solve(x1, x2, i + 1, y2, c - cc));
    return ans;
}

int n, m, c;

int main() {
    scanf("%d%d%d", &n, &m, &c);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            for (int k = 1; k <= m; k++)
                for (int l = 1; l <= m; l++)
                    for (int o = 1; o <= c; o++)
                        f[i][j][k][l][o] = -1;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            scanf("%d", &A[i][j]), sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + A[i][j];
    ave = 1.0 * sum[n][m] / c;
    printf("%.2f\n", sqrt(solve(1, n, 1, m, c) / c));
    return 0;
}
View Code

 

 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 

typedef long long ll;
const int N = 4e4 + 7;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int inf = 0x3f3f3f3f;

template<class T>
inline bool chkmax(T &a, T b) {
    return a < b ? a = b, 1 : 0;
}

template<class T>
inline bool chkmin(T &a, T b) {
    return a > b ? a = b, 1 : 0;
}

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 = 0;
        for (int i = x; i; i -= lowbit(i))
            chkmax(ans, tree[i]);
        return ans;
    }
} bit;

int main() {
    scanf("%d", &n);
    for (int i = 1; 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 + 1, v + 1 + n);
    cnt = std::unique(v + 1, v + 1 + n) - v - 1;
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        b[i] = std::lower_bound(v + 1, v + 1 + cnt, b[i]) - v;
        dp[i] = bit.query(b[i]) + 1;
        bit.add(b[i], dp[i]);
        chkmax(ans, dp[i]);
    }
    ans = n - ans;
    printf("%d\n", ans);
    for (int i = 0; i <= n; i++) {
        vec[dp[i]].push_back(i);
        b[i] = v[b[i]];
        g[i] = INF;
    }
    g[0] = 0; b[0] = -inf;
    for (int i = 1; i <= n; i++)
        for (int j = 0, sz = vec[dp[i] - 1].size(); j < sz; j++) {
            int pos = vec[dp[i] - 1][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 + 1; k <= i; k++)
                s1[k] += s1[k - 1], s2[k] += s2[k - 1];
            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 0;
}
View Code

 

1188. [HNOI2007]分裂游戏

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

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

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

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

#include 

const int N = 64;
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 = 1; i <= n; i++) 
            scanf("%d", a + i), SG[i] = 0;
        for (int i = n - 1; i; i--) {
            memset(vis, 0, sizeof(vis));
            for (int j = i + 1; j <= n; j++)
                for (int k = j; k <= n; k++)
                    vis[SG[j] ^ SG[k]] = 1;
            for (int j = 0; ; j++)
                if (!vis[j]) {
                    SG[i] = j;
                    break;
                }
        }
        int A = 0, B = 0, C = 0, cnt = 0;
        int ans = 0;
        for (int i = 1; i <= n; i++)
            if (a[i] & 1)
                ans ^= SG[i];
        for (int i = 1; i <= n; i++) if (a[i])
            for (int j = i + 1; 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 - 1, B - 1, C - 1, cnt);
    }
    return 0;
}
View Code

 

1791. [Ioi2008]Island 岛屿

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

#include 
#define ll long long

namespace IO {
    void read() {}
    template<class T, class ... T2>
    inline void read(T &x, T2 &... oth) {
        x = 0; T f = 1; char ch = getchar();
        while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar(); }
        while (isdigit(ch)) x = x * 10 + ch - '0', ch = getchar();
        x *= f;
        read(oth...);
    }
}

const int N = 1e6 + 7;
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 = 0; }
    void clear() { l = r = 0; }
    bool empty() { return !(l ^ r); }
    void push_back(int v) { que[r++] = v; r %= LEN; }
    void push_front(int v) { que[l = (l - 1 + LEN) % LEN] = v; }
    void pop_front() { ++l; l %= LEN; }
    void pop_back() { r = (r - 1 + LEN) % LEN; }
    int front() { return que[l]; }
    int back() { return que[r - 1]; }
} que;

template<class T>
inline bool chkmax(T &a, T b) {
    return a < b ? a = b, 1 : 0;
}

int cur[N * 2];
ll s[N * 2];

int main() {
    IO::read(n);
    for (int i = 1; i <= n; i++) {
        IO::read(to[i], w[i]);
        in[to[i]]++;
    }
    for (int i = 1; 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 = 0;
    for (int i = 1; i <= n; i++) if (in[i] && !vis[i]) {
        int cnt = 0;
        ll temp = 0;
        for (int j = i; !vis[j]; j = to[j]) {
            cur[++cnt] = j;
            vis[j] = 1;
            chkmax(temp, dp[j]);
        }
        int dcnt = cnt << 1;
        for (int i = cnt + 1; i <= dcnt; i++)
            cur[i] = cur[i - cnt];
        que.clear();
        for (int j = 1; j <= dcnt; j++) {
            s[j] = s[j - 1] + w[cur[j - 1]];
            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 0;
}
View Code

 

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

莫队板子题。

#include 

const int N = 5e4 + 7;

int n, m, B, ans[N][2], 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 = 1; i <= n; i++)
        scanf("%d", col + i);
    for (int i = 1; i <= m; i++)
        scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
    B = sqrt(n);
    std::sort(q + 1, q + 1 + m);
    int l = 1, r = 0;
    for (int i = 1; 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 == 0) {
            ans[q[i].id][0] = ans[q[i].id][1] = 0;
            continue;
        }
        int g = std::__gcd(fz, fm);
        ans[q[i].id][0] = fz / g;
        ans[q[i].id][1] = fm / g;
    }
    for (int i = 1; i <= m; i++)
        printf("%d/%d\n", ans[i][0], ans[i][1]);
    return 0;
}
View Code

 

2120. 数颜色

带修莫队板子题。

#include 

const int N = 1e4 + 7;
int cnt[N * 100], 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] == 1)
        ans++;
}

void del(int x) {
    --cnt[x];
    if (cnt[x] == 0)
        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[10];
    scanf("%d%d", &n, &m);
    B = n / sqrt(m * 2 / 3);
    for (int i = 1; i <= n; i++)
        scanf("%d", a + i);
    int qcnt = 0, ccnt = 0;
    for (int i = 1; i <= m; i++) {
        scanf("%s", s);
        if (s[0] == '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 = 1, r = 0, cur = 0;
    std::sort(q + 1, q + 1 + qcnt);
    for (int i = 1; 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 = 1; i <= qcnt; i++)
        printf("%d\n", res[i]);
}
View Code

 

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 

const int MOD = 20101009;
const int N = 1e7 + 7;
int prime[N], prin;
int f[N];
bool vis[N];

void M(int &x) {
    if (x >= MOD) x -= MOD;
    if (x < 0) x += MOD;
}

void init(int N) {
    f[1] = 1;
    for (int i = 2; i < N; i++) {
        if (!vis[i]) {
            prime[++prin] = i;
            M(f[i] = 1 - i);
        }
        for (int j = 1; j <= prin && i * prime[j] < N; j++) {
            vis[i * prime[j]] = 1;
            if (i % prime[j] == 0) {
                int temp = i;
                while (temp % prime[j] == 0) temp /= prime[j];
                f[i * prime[j]] = 1LL * (1 - prime[j] + MOD) * f[temp] % MOD;
                break;
            }
            f[i * prime[j]] = 1LL * f[i] * f[prime[j]] % MOD;
        }
    }
    for (int i = 1; i < N; i++)
        M(f[i] = (f[i - 1] + 1LL * f[i] * i % MOD)); 
}

int cal(int n, int m) {
    int x = 1LL * n * (n + 1) / 2 % MOD;
    int y = 1LL * m * (m + 1) / 2 % MOD;
    return 1LL * x * y % MOD;
}

int solve(int n, int m) {
    if (n > m) std::swap(n, m);
    int ans = 0;
    for (int i = 1, j; i <= n; i = j + 1) {
        j = std::min(n / (n / i), m / (m / i));
        M(ans += 1LL * cal(n / i, m / i) * (f[j] - f[i - 1] + MOD) % MOD);
    }
    return ans;
}

signed main() { 
    int n, m;
    scanf("%d%d", &n, &m);
    init(std::min(n, m) + 1);
    printf("%d\n", solve(n, m));
    return 0;
}
View Code

 

2179. FFT快速傅立叶

 FFT板子

#include 

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 + 7;
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 = 0; i < limit; i++)
        if (i < r[i])
            std::swap(a[i], a[r[i]]);
    for (int mid = 1; mid < limit; mid <<= 1) {
        Complex wn = Complex(cos(pi / mid), pd * sin(pi / mid));
        for (int l = mid << 1, j = 0; j < limit; j += l) {
            Complex w = Complex(1.0, 0.0);
            for (int k = 0; 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 == -1)
        for (int i = 0; i < limit; i++)
            a[i] = Complex(a[i].r / limit, a[i].i / limit);
}

int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        int x;
        scanf("%d", &x);
        x += 20000;
        A[x]++;
        B[x * 2]++;
        C[x * 3]++;
    }
    for (int i = 0; i <= 40000; i++)
        a[i] = Complex((double)A[i], 0.0);
    for (int i = 0; i <= 80000; i++)
        b[i] = Complex((double)B[i], 0.0);
    limit = 1;
    while (limit <= 40000 + 80000)
        limit <<= 1, l++;
    for (int i = 0; i < limit; i++)
        r[i] = r[i >> 1] >> 1 | ((i & 1) << (l - 1));
    FFT(a, 1);
    FFT(b, 1);
    for (int i = 0; i < limit; i++)
        b[i] = b[i] * a[i];
    for (int i = 0; i < limit; i++)
        a[i] = a[i] * a[i] * a[i];
    FFT(a, -1);
    FFT(b, -1);
    for (int i = 0; i <= 120000; i++) {
        long long ans = (long long)((a[i].r - 3.0 * b[i].r + 2.0 * C[i]) / 6.0 + 0.5);
        if (ans > 0)
            printf("%d : %lld\n", i - 60000, ans);
    }
    return 0;
}
View Code

 

2238. Mst

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

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

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

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

#include 

const int N = 1e5 + 7;
inline bool chkmax(int &a, int b) { return a < b ? a = b, 1 : 0; }
inline bool chkmin(int &a, int b) { return a > b ? a = b, 1 : 0; }

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] = 1;
    dep[u] = dep[f] + 1;
    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 << 2];
    void build(int p, int l, int r) {
        tree[p] = INF;
        if (l == r) return;
        int mid = l + r >> 1;
        build(lp, l, mid);
        build(rp, mid + 1, 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 >> 1;
        if (x <= mid) update(lp, l, mid, x, y, z);
        if (y > mid) update(rp, mid + 1, r, x, y, z);
    }
    int query(int p, int l, int r, int pos) {
        if (l == r) return tree[p];
        int mid = l + r >> 1;
        if (pos <= mid) return std::min(tree[p], query(lp, l, mid, pos));
        return std::min(tree[p], query(rp, mid + 1, 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(1, 1, n, dfn[top[u]], dfn[u], w);
        u = fa[top[u]];
    }
    if (dep[u] > dep[v]) std::swap(u, v);
    seg.update(1, 1, n, dfn[u] + 1, dfn[v], w);
}

struct DSU {
    int fa[N];
    void init(int n) { for (int i = 1; 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 0;
        fa[u] = v;
        return 1;
    }
} dsu;

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; 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 + 1, sort_edge + 1 + m);
    int ed = 0, ans = 0;
    for (int i = 1; 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] = 1;
    }
    int q;
    scanf("%d", &q);
    if (ed < n - 1) {
        while (q--) {
            int x;
            scanf("%d", &x);
            puts("Not connected");
        }
        return 0;
    }
    dfs1(1, 0);
    dfs2(1, 1);
    seg.build(1, 1, n);
    assert(n == tol);
    for (int i = 1; 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(1, 1, n, dfn[u]);
        if (temp == INF) puts("Not connected");
        else printf("%d\n", ans - edge[i].w + temp);
    }
    return 0;
}
View Code

 

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 

const int N = 5e4 + 7;
int prime[N], prin, mu[N];
bool vis[N];

void init() {
    mu[1] = 1;
    for (int i = 2; i < N; i++) {
        if (!vis[i]) prime[++prin] = i, mu[i] = -1;
        for (int j = 1; j <= prin && i * prime[j] < N; j++) {
            vis[i * prime[j]] = 1;
            if (i % prime[j] == 0) {
                mu[i * prime[j]] = 0;
                break;
            }
            mu[i * prime[j]] = -mu[i];
        }
    }
    for (int i = 1; i < N; i++)
        mu[i] += mu[i - 1];
}

int solve(int n, int m) {
    int res = 0;
    for (int i = 1, j; i <= std::min(n, m); i = j + 1) {
        j = std::min(n / (n / i), m / (m / i));
        res += (mu[j] - mu[i - 1]) * (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 - 1) / k) - solve((a - 1) / k, d / k) + solve((a - 1) / k, (c - 1) / k));
    }
    return 0;
}
View Code

 

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 

const int MOD = 100000009;
const int N = 1e7 + 7;
int prime[N], prin;
int f[N];
bool vis[N];

void M(int &x) {
    if (x >= MOD) x -= MOD;
    if (x < 0) x += MOD;
}

void init(int N) {
    f[1] = 1;
    for (int i = 2; i < N; i++) {
        if (!vis[i]) {
            prime[++prin] = i;
            M(f[i] = 1 - i + MOD);
        }
        for (int j = 1; j <= prin && i * prime[j] < N; j++) {
            vis[i * prime[j]] = 1;
            if (i % prime[j] == 0) {
                int temp = i;
                while (temp % prime[j] == 0) temp /= prime[j];
                f[i * prime[j]] = 1LL * (1 - prime[j] + MOD) * f[temp] % MOD;
                break;
            }
            f[i * prime[j]] = 1LL * f[i] * f[prime[j]] % MOD;
        }
    }
    for (int i = 1; i < N; i++)
        f[i] = (1LL * f[i - 1] + 1LL * f[i] * i % MOD) % MOD; 
}

int cal(int n, int m) {
    int x = 1LL * n * (n + 1) / 2 % MOD;
    int y = 1LL * m * (m + 1) / 2 % MOD;
    return 1LL * x * y % MOD;
}

int solve(int n, int m) {
    if (n > m) std::swap(n, m);
    int ans = 0;
    for (int i = 1, j; i <= n; i = j + 1) {
        j = std::min(n / (n / i), m / (m / i));
        (ans += 1LL * cal(n / i, m / i) * (f[j] - f[i - 1] + MOD) % MOD) %= MOD;
    }
    return ans;
}

signed main() {
    int T;
    scanf("%d", &T);
    init(N - 6);
    while (T--) {
        int n, m;
        scanf("%d%d", &n, &m);
        printf("%d\n", solve(n, m));
    }
    return 0;
}
View Code

 

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 
#define ll long long

const int N = 1e7 + 7;
int prime[N], prin, mu[N];
bool vis[N];
ll f[N];

void init() {
    mu[1] = 1;
    for (int i = 2; i < N; i++) {
        if (!vis[i]) {
            prime[++prin] = i;
            mu[i] = -1;
            f[i] = 1;
        }
        for (int j = 1; j <= prin && i * prime[j] < N; j++) {
            vis[i * prime[j]] = 1;
            if (i % prime[j] == 0) {
                mu[i * prime[j]] = 0;
                f[i * prime[j]] = mu[i];
                break;
            }
            mu[i * prime[j]] = -mu[i];
            f[i * prime[j]] = -f[i] + mu[i];
        }
    }
    for (int i = 1; i < N; i++)
        f[i] += f[i - 1];
}

ll solve(int n, int m) {
    ll ans = 0;
    for (int i = 1, j; i <= std::min(n, m); i = j + 1) {
        j = std::min(n / (n / i), m / (m / i));
        ans += (f[j] - f[i - 1]) * (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 0;
}
View Code

 

2940. [Poi2000]条纹 

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

复杂度 $O(n ^ 2)$

#include 

const int N = 1100;
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 <= 1000; i++) {
        memset(vis, 0, sizeof(vis));
        for (int j = 0; j <= i - a; j++)
            vis[SG[j] ^ SG[i - a - j]] = 1;
        for (int j = 0; j <= i - b; j++)
            vis[SG[j] ^ SG[i - b - j]] = 1;
        for (int j = 0; j <= i - c; j++)
            vis[SG[j] ^ SG[i - c - j]] = 1;
        for (int j = 0; ; j++)
            if (!vis[j]) {
                SG[i] = j;
                break;
            }
    }
    int T;
    scanf("%d", &T);
    while (T--) {
        int n;
        scanf("%d", &n);
        puts(SG[n] ? "1" : "2");
    }
    return 0;
}
View Code

 

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 

namespace IO {
    char buf[1 << 21], buf2[1 << 21], a[20], *p1 = buf, *p2 = buf, hh = '\n';
    int p, p3 = -1;
    void read() {}
    void print() {}
    inline int getc() {
        return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++;
    }
    inline void flush() {
        fwrite(buf2, 1, p3 + 1, stdout), p3 = -1;
    }
    template 
    inline void read(T &x, T2 &... oth) {
        T f = 1; x = 0;
        char ch = getc();
        while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getc(); }
        while (isdigit(ch)) { x = x * 10 + ch - 48; ch = getc(); }
        x *= f;
        read(oth...);
    }
    template 
    inline void print(T x, T2... oth) {
        if (p3 > 1 << 20) flush();
        if (x < 0) buf2[++p3] = 45, x = -x;
        do {
            a[++p] = x % 10 + 48;
        } while (x /= 10);
        do {
            buf2[++p3] = a[p];
        } while (--p);
        buf2[++p3] = hh;
        print(oth...);
    }
} // using namespace IO

const int N = 1e5;
int prime[N + 7], prin, mu[N + 7], n, d[N + 7];
bool vis[N + 7];
std::pair<int, int> p[N + 7];

void init() {
    mu[1] = 1;
    for (int i = 2; i <= N; i++) {
        if (!vis[i]) {
            prime[++prin] = i;
            mu[i] = -1;
        }
        for (int j = 1; j <= prin && i * prime[j] <= N; j++) {
            vis[i * prime[j]] = 1;
            if (i % prime[j] == 0) break;
            mu[i * prime[j]] = -mu[i];
        }
    }
    for (int i = 1; i <= N; i++)
        for (int j = 1; 1LL * i * j <= N; j++)
            d[i * j] += i;
    for (int i = 1; i <= N; i++) {
        p[i].first = d[i], p[i].second = i;
    }
    std::sort(p + 1, p + 1 + 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 = 0;
        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 = 0;
    for (int i = 1, j; i <= std::min(n, m); i = j + 1) {
        j = std::min(n / (n / i), m / (m / i));
        ans += 1u * (n / i) * (m / i) * (bit.query(j) - bit.query(i - 1));
    }
    return ans;
}

int main() {
    init();
    int q;
     IO::read(q);
    for (int i = 1; i <= q; i++) {
        IO::read(que[i].n, que[i].m, que[i].a);
        que[i].id = i;
    }
    std::sort(que + 1, que + 1 + q);
    int cur = 1;
    for (int i = 1; i <= q; i++) {
        while (cur <= N && p[cur].first <= que[i].a) {
            for (int j = 1; 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 = 1; i <= q; i++)
        IO::print(ans[i] & ((1u << 31) - 1));
    IO::flush();
    return 0;
}
View Code

 

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 

const int N = 1e5 + 7;
int SG[N], vis[N], T, F;
std::vector<int> a[110];

int get(int n) {
    for (int i = 2, j, s; i <= n; i = j + 1) {
        j = n / (n / i);
        s = n / i;
        int y = n % i, x = i - y;
        vis[((x & 1) * SG[s]) ^ ((y & 1) * SG[s + 1])] = n;
        if (i < j) {
            y = n % (i + 1);
            x = i + 1 - y;
            vis[((x & 1) * SG[s]) ^ ((y & 1) * SG[s + 1])] = n;
        }
    }
    for (int i = 0; ; 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 = 0;
    for (int i = 1; i <= T; i++) {
        int n;
        scanf("%d", &n);
        a[i].resize(n);
        for (int j = 0; j < n; j++)
            scanf("%d", &a[i][j]), mx = std::max(mx, a[i][j]);
    }
    init(mx);
    for (int i = 1; i <= T; i++) {
        int n = a[i].size();
        int ans = 0;
        for (int j = 0; j < n; j++) {
            int x = a[i][j];
            ans ^= SG[x];
        }
        printf("%d%c", ans ? 1 : 0, " \n"[i == T]);
    }
    return 0;
}
View Code

 

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 
#define pii pair
#define fi first
#define se second
#define ll long long

const int N = 1e5 + 5;
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 * 40];
    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 >> 1;
        if (pos <= mid) update(tree[p].lp, tree[q].lp, l, mid, pos, v);
        else update(tree[p].rp, tree[q].rp, mid + 1, 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 >> 1;
        ll ans = 0;
        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 + 1, r, x, y);
        return ans;
    }
} seg;

void dfs1(int u, int f = 0) {
    fa[u] = f;
    dep[u] = dep[f] + 1;
    sz[u] = 1;
    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 = 0) {
    root[u] = root[f];
    for (int v: query[u]) {
        seg.update(root[u], root[u], 1, cnt, in[v], 1);
        seg.update(root[u], root[u], 1, cnt, out[v], -1);
    }
    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 = 1; i < n; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        vec[u].push_back(v);
        vec[v].push_back(u);
    }
    for (int i = 1; i <= m; i++) {
        scanf("%d%d", &edge[i].fi, &edge[i].se);
        query[edge[i].fi].push_back(edge[i].se);
    }
    std::sort(edge + 1, edge + 1 + m);
    dfs1(1);
    dfs2(1, 1);
    dfs(1);
    ll ans = 0;
    for (int i = 1; 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], 1, cnt, in[f], in[u]);
        ans += seg.query(root[u], root[v], root[f], root[ff], 1, cnt, in[f], in[v]);
        ans -= seg.query(root[u], root[v], root[f], root[ff], 1, cnt, in[f], in[f]);
        ans--;
    }
    //printf("%lld\n", ans);
    long long all = 1LL * m * (m - 1) / 2;
    long long g = gcd(all, ans);
    printf("%lld/%lld\n", ans / g, all / g);
    return 0;
}
View Code

 

3781. 小B的询问

莫队。

#include 
#define ll long long

const int N = 5e4 + 7;
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 = 1; i <= n; i++)
        scanf("%d", a + i);
    for (int i = 1; i <= m; i++)
        scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
    std::sort(q + 1, q + 1 + m);
    int l = 1, r = 0;
    for (int i = 1; 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 = 1; i <= m; i++)
        printf("%lld\n", ans[i]);
    return 0;
}
View Code

 

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 

const int N = 5e4 + 7;
int mu[N], prime[N], prin, d[N], t[N];
bool vis[N];

void init(int n) {
    mu[1] = d[1] = t[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) {
            prime[++prin] = i;
            d[i] = 2;
            t[i] = 1;
            mu[i] = -1;
        }
        for (int j = 1; j <= prin && i * prime[j] < N; j++) {
            vis[i * prime[j]] = 1;
            if (i % prime[j] == 0) {
                t[i * prime[j]] = t[i] + 1;
                mu[i * prime[j]] = 0;
                d[i * prime[j]] = d[i] / (t[i] + 1) * (t[i] + 2);
                break;
            }
            d[i * prime[j]] = d[i] * 2;
            t[i * prime[j]] = 1;
            mu[i * prime[j]] = -mu[i];
        }
    }
    for (int i = 1; i <= n; i++)
        mu[i] += mu[i - 1], d[i] += d[i - 1];
}

#define ll long long

ll solve(int n, int m) {
    ll ans = 0;
    if (n > m) std::swap(n, m);
    for (int i = 1, j; i <= n; i = j + 1) {
        j = std::min(n / (n / i), m / (m / i));
        ans += 1LL * (mu[j] - mu[i - 1]) * 1LL * d[n / i] * d[m / i];
    }
    return ans;
}

int main() {
    init(N - 1);
    int T;
    scanf("%d", &T);
    while (T--) {
        int n, m;
        scanf("%d%d", &n, &m);
        printf("%lld\n", solve(n, m));
    }
    return 0;
}
View Code

 

4025. 二分图

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

#include 

namespace IO {
    void read() {}
    template<class T, class... T2>
    void read(T &x, T2 &... oth) {
        x = 0; T f = 1; char ch = getchar();
        while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar(); }
        while (isdigit(ch)) x = x * 10 + ch - '0', ch = getchar();
        x *= f;
        read(oth...);
    }
}

const int N = 2e5 + 7;

int st[N], top;

struct DSU {
    int fa[N], d[N], dep[N];
    void init(int n) {
        for (int i = 0; i <= n; i++)
            fa[i] = i, d[i] = dep[i] = 0;
    }
    int getfa(int x) {
        while (x != fa[x]) 
            x = fa[x];
        return x;
    }
    int getdis(int x) {
        int dis = 0;
        while (x != fa[x])
            dis ^= d[x], x = fa[x];
        return dis;
    }
    bool merge(int x, int y) {
        int D = getdis(x) ^ getdis(y) ^ 1;
        x = getfa(x), y = getfa(y);
        if (x == y) return D == 0;
        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 1;
    }
    void del(int tp) {
        while (top > tp) {
            int x = st[top--];
            if (x < 0)
                dep[-x]--;
            else 
                fa[x] = x, d[x] = 0;
        }
    }
} 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 >> 1;
    std::vector<int> L, R;
    for (int i = 0, 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] = 1;
                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 + 1, r, R);
    dsu.del(tp);
}

int main() {
    IO::read(n, m, T);
    dsu.init(n);
    std::vector<int> vec;
    for (int i = 1; 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(1, T, vec);
    for (int i = 1; i <= T; i++)
        puts(ans[i] ? "No" : "Yes");
    return 0;
}
View Code

 

4299. Codechef FRBSUM

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

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

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

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

View Code

 

4424. Cf19E Fairy

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

#include 

namespace IO {
    void read() {}
    template<class T, class... T2>
    void read(T &x, T2 &... oth) {
        x = 0; T f = 1; char ch = getchar();
        while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar(); }
        while (isdigit(ch)) x = x * 10 + ch - '0', ch = getchar();
        x *= f;
        read(oth...);
    }
}

const int N = 1e6 + 7;
struct E {
    int v, ne, id;
} e[N << 1];
int head[N], tag[N], dep[N], cnt = 1;
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 ^ 1)) continue;
        int v = e[i].v;
        if (dep[v]) {
            if (dep[v] > dep[u]) continue;
            if ((dep[u] - dep[v] + 1) & 1) {
                tol++;
                fei = e[i].id;
                tag[u]++; tag[v]--;
            } else {
                tag[u]--; tag[v]++;
            }
        } else {
            dep[v] = dep[u] + 1;
            dfs(v, i);
            tag[u] += tag[v];
        }
    }
}

std::vector<int> vec;

void dfs(int u) {
    vis[u] = 1;
    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 = 1; i <= m; i++) {
        int u, v;
        IO::read(u, v);
        if (u == v && !self) {
            self = i;
            continue;
        }
        if (u == v) {
            self = -1;
            continue;
        }
        add(u, v, i);
    }
    if (self == -1) {
        puts("0");
        return 0;
    }
    for (int i = 1; i <= n; i++) {
        if (!dep[i])
            dep[i] = 1, dfs(i, 0);
    }
    if (tol == 0) {
        if (self) {
            printf("1\n%d\n", self);
        } else {
            printf("%d\n", m);
            for (int i = 1; i <= m; i++)
                printf("%d%c", i, " \n"[i == m]);
        }
        return 0;
    }
    if (self) {
        puts("0");
        return 0;
    }
    for (int i = 1; i <= n; i++)
        if (!vis[i])
            dfs(i);
    if (tol == 1)
        vec.push_back(fei);
    printf("%d\n", (int)vec.size());
    std::sort(vec.begin(), vec.end());
    for (int i = 0; i < vec.size(); i++)
        printf("%d%c", vec[i], " \n"[i + 1 == vec.size()]);
    return 0;
}
View Code

 

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 

const int N = 1e6, XN = N + 7;
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[1] = 1;
    for (int i = 2; i <= N; i++) {
        if (!vis[i]) {
            prime[++prin] = i;
            mu[i] = -1;
        }
        for (int j = 1; j <= prin && i * prime[j] <= N; j++) {
            vis[i * prime[j]] = 1;
            if (i % prime[j] == 0) break;
            mu[i * prime[j]] = -mu[i];
        }
    }
    for (int i = 1; i <= N; i++) {
        fs[i] = fs[i - 1] + mu[i] * (gcd(i, k) == 1);
        gs[i] = gs[i - 1] + (gcd(i, k) == 1);
    }
}

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 = 1;
    for (int i = 2, j; i <= n; i = j + 1) {
        j = n / (n / i);
        ans -= 1LL * (GS(j) - GS(i - 1)) * solve(n / i);
    }
    return mp[n] = ans;
}

signed main() {
    scanf("%d%d%d", &n, &m, &k);
    init(k);
    ll ans = 0;
    for (int i = 1, j; i <= std::min(n, m); i = j + 1) {
        j = std::min(n / (n / i), m / (m / i));
        ans += 1LL * (solve(j) - solve(i - 1)) * (n / i) * GS(m / i);
    }
    printf("%lld\n", ans);
    return 0;
}
View Code

 

4742. [Usaco2016 Dec]Team Building

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

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

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

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

View Code

 

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 

const int MOD = 1e9 + 7;

int qp(int a, int b = MOD - 2) {
    int ans = 1;
    while (b) {
        if (b & 1) ans = 1LL * ans * a % MOD;
        b >>= 1;
        a = 1LL * a * a % MOD;
    }
    return ans;
}

const int N = 1e6;
int prime[N + 7], prin, mu[N + 7], g[N + 7], f[N + 7], fi[N + 7];
bool vis[N + 7];

inline int add(int x, int y) {
    return x + y >= MOD ? x + y - MOD : x + y;
}

void init() {
    mu[1] = 1;
    f[0] = 0; f[1] = 1;
    fi[1] = 1;
    g[0] = g[1] = 1;
    for (int i = 2; i <= N; i++) {
        g[i] = 1;
        f[i] = add(f[i - 1], f[i - 2]);
        fi[i] = qp(f[i]);
        if (!vis[i]) {
            prime[++prin] = i;
            mu[i] = -1;
        }
        for (int j = 1; j <= prin && i * prime[j] <= N; j++) {
            vis[i * prime[j]] = 1;
            if (i % prime[j] == 0) break;
            mu[i * prime[j]] = -mu[i];
        }
    }
    for (int i = 1; i <= N; i++) if (mu[i])
        for (int j = 1; 1LL * i * j <= N; j++)
            g[i * j] = 1LL * g[i * j] * (mu[i] == 1 ? f[j] : fi[j]) % MOD;
    for (int i = 2; i <= N; i++)
        g[i] = 1LL * g[i] * g[i - 1] % 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 = 1;
        for (int i = 1, j; i <= n; i = j + 1) {
            j = std::min(n / (n / i), m / (m / i));
            ans = 1LL * ans * qp(1LL * g[j] * qp(g[i - 1]) % MOD, 1LL * (n / i) * (m / i) % (MOD - 1)) % MOD;
        }
        printf("%d\n", ans);
    }
    return 0;
}
View Code

你可能感兴趣的:(BZOJ练习记)