省选测试1

省选测试1

T1

​ 给定长度为\(n\)的排列,\(m\)个询问.每次询问区间\([l,r]\)中,最长的值域连续的一段.

​ 最长的值域连续的一段是指从排列的\([l,r]\)的区间中选出一些数, 使得这些数排序后构成了连续的一段正整数, 那么这些正整数就是一个值域连续段.

\(n, m <= 5e4\)

​ 回滚莫队 + 并查集.

​ 很显然莫队可做, 但是考试的时候并没有学过回滚莫队, 于是就用了普通莫队 + 线段树搞了一个\(O(n\sqrt n logn)\)的做法.

​ 我们发现这道题的难点就在于如何删除一个数字的影响. 那我们可以考虑只有加入操作没有删除操作. 这正好就是回滚莫队了.

​ 那么回滚莫队是什么呢? 其实就是莫队 + 栈.我们把加入数字时每一个影响都加入到栈中, 然后回滚的时候弹栈消除影响.

​ 首先, 对于左右区间在同一个快内的询问直接暴力查就好了.

​ 然后考虑怎么加入一个数的影响. 直接并查集合并就好了, 维护一个集合的大小\(siz\).

​ 我们找出左端点在同一个块\(x\)内的询问. 对于右端点, 我们已经排好序了, 是递增的, 所以只存在加入操作.对于左端点, 我们每次把左指针都回滚块\(x\)的右端点, 然后左移左端点, 也是只有加入操作.

​ 总结一下 : 右端点不断向右移, 左端点总是左移,回滚,左移,回滚......由于左端点所在块的大小为\(\sqrt n\)的, 所以和一般莫队相比也只是乘上了2的常数.总的复杂度为\(O(n \sqrt n \alpha)\).有一个并查集的常数.

#include <bits/stdc++.h>

#define rei register int

using namespace std;

inline int read() {
    int s = 0, f = 1; char ch;
    while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
    for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
    return s * f;
}

const int N = 5e4 + 5, M = 250;
int n, m, top, res, top1;
int a[N], L[N], R[N], fa[N], siz[N], vis[N], pos[N], ans[N], tnp[M];
struct ques { int l, r, id; } q[N];
struct stack { int opt, x, y; } sta[N << 3], sta1[N << 3];

int cmp(ques x, ques y) {
    return pos[x.l] == pos[y.l] ? x.r < y.r : pos[x.l] < pos[y.l];
}

int find(int x) {
    return x == fa[x] ? x : find(fa[x]);
}

void add(int x) {
    vis[a[x]] = 1;
    sta1[++ top1].opt = 1; sta1[top1].x = a[x];
    if(vis[a[x] - 1]) {
        int fx = find(a[x]), fy = find(a[x] - 1);
        if(siz[fy] < siz[fx]) swap(fx, fy);
        sta1[++ top1].opt = 2; sta1[top1].x = fx;
        fa[fx] = fy; 
        sta1[++ top1].opt = 3; sta1[top1].x = fy; sta1[top1].y = siz[fy];
        siz[fy] += siz[fx];
        res = max(res, siz[fy]);
        // cout << a[x] - 1 << " " << fy << " " << siz[fy] << "^^^\n";
    }
    if(vis[a[x] + 1]) {
        int fx = find(a[x]), fy = find(a[x] + 1);
        if(siz[fy] < siz[fx]) swap(fx, fy);
        sta1[++ top1].opt = 2; sta1[top1].x = fx;
        fa[fx] = fy; 
        sta1[++ top1].opt = 3; sta1[top1].x = fy; sta1[top1].y = siz[fy];
        siz[fy] += siz[fx];
        res = max(res, siz[fy]);
    }
}

void add_(int x) {
    // cout << a[x] << "------>\n";
    vis[a[x]] = 1;
    sta[++ top].opt = 1; sta[top].x = a[x];
    if(vis[a[x] - 1]) {
        // cout << "---\n";
        int fx = find(a[x]), fy = find(a[x] - 1);
        if(siz[fy] < siz[fx]) swap(fx, fy); 
        sta[++ top].opt = 2; sta[top].x = fx; 
        fa[fx] = fy;
        sta[++ top].opt = 3; sta[top].x = fy; sta[top].y = siz[fy];
        // cout << siz[fy] << " " << siz[fx] << ")))\n";
        siz[fy] += siz[fx];
        res = max(res, siz[fy]);
        // cout << res << "\n";
    }
    if(vis[a[x] + 1]) { 
        // cout << "+++\n";
        int fx = find(a[x]), fy = find(a[x] + 1);
        if(siz[fy] > siz[fx]) swap(fx, fy);
        sta[++ top].opt = 2; sta[top].x = fx;
        fa[fx] = fy;
        sta[++ top].opt = 3; sta[top].x = fy; sta[top].y = siz[fy];
        siz[fy] += siz[fx];
        res = max(res, siz[fy]);
        // cout << res << "\n";
    }
}

void Pop_() {
    if(sta[top].opt == 0) res = sta[top].x, top --;
    if(sta[top].opt == 1) vis[sta[top].x] = 0, top --;
    if(sta[top].opt == 2) fa[sta[top].x] = sta[top].x, top --;
    if(sta[top].opt == 3) siz[sta[top].x] = sta[top].y, top --;
}

void Pop() {
    if(sta1[top1].opt == 0) res = sta1[top1].x, top1 --;
    if(sta1[top1].opt == 1) vis[sta1[top1].x] = 0, top1 --;
    if(sta1[top1].opt == 2) fa[sta1[top1].x] = sta1[top1].x, top1 --;
    if(sta1[top1].opt == 3) siz[sta1[top1].x] = sta1[top1].y, top1 --;
}

int main() {

    freopen("permu.in","r",stdin); freopen("permu.out","w",stdout);

    n = read(); m = read(); int B = sqrt(n);
    for(int i = 1;i <= n; i++) fa[i] = i, siz[i] = 1; res = 1;
    for(int i = 1;i <= n; i++) L[i] = 2333333;
    for(rei i = 1;i <= n; i++) a[i] = read(), pos[i] = (i - 1) / B + 1;
    for(int i = 1;i <= n; i++) L[pos[i]] = min(L[pos[i]], i), R[pos[i]] = max(R[pos[i]], i);
    for(rei i = 1;i <= m; i++) q[i].l = read(), q[i].r = read(), q[i].id = i;
    // for(int i = 1;i <= n; i++) cout << pos[i] << " "; cout << "\n";
    sort(q + 1, q + m + 1, cmp);
    // for(int i = 1;i <= m; i++) cout << q[i].l << " " << q[i].r << " " << q[i].id << "\n";
    rei x, y;
    for(rei i = 1, j;i <= m; i = j) {
        int now_k = pos[q[i].l];
        x = R[now_k] + 1; y = R[now_k];
        // cout << x << " " << y << " " << res << "\n";
        sta1[++ top1].opt = 0; sta1[top1].x = res;
        for(j = i;pos[q[j].l] == now_k; j++) {
            if(pos[q[j].r] == pos[q[j].l]) {
                // cout << q[j].l << " " << q[j].r << "!!!\n";
                int cnt = 0;
                for(int k = q[j].l;k <= q[j].r; k++) tnp[++ cnt] = a[k];
                sort(tnp + 1, tnp + cnt + 1);
                int cop = 1, cip = 1;
                for(int k = 2;k <= cnt; k++)  
                    if(tnp[k] == tnp[k - 1] + 1) cop ++, cip = max(cip, cop);
                    else cop = 1;
                ans[q[j].id] = cip;
            }
            else {
                // cout << q[j].l << " " << q[j].r << ")))\n";
                while(y < q[j].r) add(++ y);
                // cout << j << ":" << res << "!!!\n";
                sta[++ top].opt = 0; sta[top].x = res;
                while(x > q[j].l) add_(-- x);
                ans[q[j].id] = res;
                while(top) Pop_();
                x = R[now_k] + 1;
            }
        }
        while(top1) Pop();
    }
    for(rei i = 1;i <= m; i++) printf("%d\n", ans[i]);

    fclose(stdin); fclose(stdout);

    return 0;
}

/*
8 3
3 1 7 2 5 8 6 4
1 4
5 8
1 7
*/

(上面代码写的太麻烦了, 其实好多东西都可以合并到一起去的).

T2

​ 有一颗\(n\)个点的树, 有三个点在树上轮流取点,直到点被取完.

​ 取完之后需要计算每一个人的得分.有\(m\)个幸运数, 对于一个点对\((u, v)\), 如果它们在原图上的距离为一个幸运数, 并且被同一个人取到, 那么这个人就得到一分.

​ 假设每个人取点时都是等概率的选取一个未被选过的点, 问每个人得分的期望.

\(n <= 5e4, m <= 10\).

​ 点分治.

​ (考场上完全没有思路啊)

​ 对于每个人其实都是可以分开算的, 因为选取每个点的概率都是相等的.

​ 假设一个人需要选择\(k\)个点, 那么总共就有\(C_n^k\)中方案, 对于某一个点对的选取概率是 : \(\frac{C_{n-2}^{k-2}}{C_n^k} = \frac{k*(k-1)}{n*(n-1)}\).意思就是当前的点对已经选好了, 剩下\(k-2\)个点随便选.

​ 每一个点对的概率都是这样的, 所以现在问题转化成了, 计算有多少个点对的距离为幸运数, 那么直接点分治就好了, 复杂度\(O(mnlogn)\).

#include <bits/stdc++.h>

using namespace std;

inline long long read() {
    long long s = 0, f = 1; char ch;
    while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
    for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
    return s * f;
}   

const int N = 5e4 + 5;
int n, m, rt, cnt, totsiz;
int k[11], dis[N], tmp[N], siz[N], num[N], vis[N], head[N], tong[N], Tmp[N], Tong[N], maxsiz[N];
struct edge { int to, nxt; } e[N << 1];

void add(int x, int y) {
    e[++ cnt].nxt = head[x]; head[x] = cnt; e[cnt].to = y;
}

void get_rt(int x, int fa) {
    siz[x] = 1; maxsiz[x] = 0;
    for(int i = head[x]; i ; i = e[i].nxt) {
        int y = e[i].to; if(vis[y] || y == fa) continue ;
        get_rt(y, x); siz[x] += siz[y];
        maxsiz[x] = max(maxsiz[x], siz[y]);
    }
    maxsiz[x] = max(maxsiz[x], totsiz - siz[x]);
    if(maxsiz[rt] > maxsiz[x]) rt = x;
}

void get_dis(int x, int fa) {
    if(!tong[dis[x]] && dis[x]) tmp[++ cnt] = dis[x];
    tong[dis[x]] ++;
    for(int i = head[x]; i ; i = e[i].nxt) {
        int y = e[i].to; if(y == fa || vis[y]) continue ;
        dis[y] = dis[x] + 1;
        get_dis(y, x);
    }
}

void calc(int x) {
    // cout << x << "--------->\n";
    int Cnt = 0; dis[x] = 0;
    for(int ii = head[x]; ii ; ii = e[ii].nxt) {
        int y = e[ii].to; if(vis[y]) continue ;
        dis[y] = dis[x] + 1;
        for(int i = 1;i <= cnt; i++) tong[tmp[i]] = 0; cnt = 0;
        get_dis(y, x); 
        sort(tmp + 1, tmp + cnt + 1);
        // cout << y << "---->\n";
        // for(int i = 1;i <= cnt; i++) cout << tmp[i] << " "; cout << "\n"; 
        // for(int i = 1;i <= cnt; i++) cout << tong[tmp[i]] << " "; cout << "\n";
        for(int i = 1;i <= m; i++) {
            num[i] += tong[k[i]];
            for(int j = 1;j <= cnt; j++) { 
                if(tmp[j] >= k[i]) break ;
                num[i] += tong[tmp[j]] * Tong[k[i] - tmp[j]];
            }
            // cout << i << ":" << num[i] << "\n";
        }
        for(int i = 1;i <= cnt; i++) {
            if(!Tong[tmp[i]]) Tmp[++ Cnt] = tmp[i];
            Tong[tmp[i]] += tong[tmp[i]];
        }
    }
    for(int i = 1;i <= Cnt; i++) Tong[tmp[i]] = tong[tmp[i]] = 0;
}

void solve(int x) {
    calc(x); vis[x] = 1;
    for(int i = head[x]; i ; i = e[i].nxt) {
        int y = e[i].to; if(vis[y]) continue ;
        maxsiz[0] = totsiz = siz[y]; rt = 0;
        get_rt(y, 0); solve(rt);
    }
}

int t[4];
void calc_sum(int x) {
    double k_ = 1.0 * t[x] * (t[x] - 1) / (1.0 * n * (n - 1)), sum = 0;
    for(int i = 1;i <= m; i++) sum += k_ * num[i];
    printf("%.2lf\n", sum);
}

int main() {

    freopen("game.in","r",stdin); freopen("game.out","w",stdout);

    n = read(); m = read();
    for(int i = 1;i <= m; i++) k[i] = read();
    for(int i = 1, x, y;i < n; i++) {
        x = read(); y = read(); add(x, y); add(y, x);
    }
    maxsiz[0] = totsiz = n; rt = 0;
    get_rt(1, 0); solve(rt);
    // for(int i = 1;i <= m; i++) cout << i << ":" << k[i] << " " << num[i] << "\n";
    int t1 = n / 3, t2 = n % 3;
    if(t2 == 0) t[1] = t[2] = t[3] = t1;
    if(t2 == 1) t[1] = t1 + 1, t[2] = t[3] = t1;
    if(t2 == 2) t[1] = t[2] = t1 + 1, t[3] = t1;
    for(int i = 1;i <= 3; i++) calc_sum(i);

    fclose(stdin); fclose(stdout);

    return 0;
}

/*
5 3
1 2 3
1 2
1 5
2 3
2 4
*/

T3

​ 给定一张\(n\)个点的无向图, 要求把\(n\)个点都分入\(A, B\)集合中.

​ 若点\(i, j\)不相连, 则\(i, j\)不可以同时放入\(A\)集合中;

​ 若点\(i, j\)相连,则\(i, j\)不可以同时放入\(B\)集合中;

\(A, B\)集合都不可以为空.

​ (相连是指有直接连边)

​ 问有多少种方案可以满足上面的条件.

\(n <= 5000\).

​ 2-sat.

​ 一个点只能有两种选择, 显然2-sat.

​ 如果两个点\(x,y\)相连, 那么连\((x+n \rightarrow y), (y+n \rightarrow x)\).

​ 如果两个点\(x, y\)不相连, 那么连\((x \rightarrow y + n)(y \rightarrow x +n)\).

​ 然后跑Tarjan判断有无解.

​ 主要是怎么找到所有方案.

​ 首先找到一个初始方案, 我们发现要得到其他方案只有这三种方式:

​ 1.找一个\(A\)集合中的点\(x\)放到\(B\)集合, 条件是与\(x\)相连的所有点都不可以在\(B\)集合.

​ 2.找一个\(B\)集合中的点\(y\)放到\(A\)集合, 条件是与\(y\)不相连的所有点都不可以在\(A\)集合.

​ 3.找一个\(A\)集合中的点\(x\)\(B\)集合中的点\(y\), 把它们交换, 条件是除了\(y\)所有与\(x\)相连的点都不可以在\(B\)集合, 除了\(x\)所有与\(y\)不相连的点都不可以在\(A\)集合.

​ 为什么只有这三种情况呢? 假设我们要从\(A\)集合移动两个点到\(B\)集合, 那么这两个点一定是有直接连边的, 那么它们就不能同时存在于\(B\)集合.所以不管移动那个集合, 一定是只能移动一个点的.

​ 记得最后判断一下\(A, B\)不为空就好了.

#include <bits/stdc++.h>

using namespace std;

inline long long read() {
    long long s = 0, f = 1; char ch;
    while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
    for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
    return s * f;
}

const int N = 5005, M = 3e7;
int n, cnt, tot, top, css, siz1, siz2;
int in[N << 1], col[N << 1], dfn[N << 1], sta[N << 1], low[N << 1], head[N << 1], inse[N], link[N][N];
struct edge { int to, nxt; } e[M];
vector <int> v[N];

void add(int x, int y) {
    e[++ cnt].nxt = head[x]; head[x] = cnt; e[cnt].to = y;
}

void Tarjan(int x) {
    dfn[x] = low[x] = ++ tot; in[x] = 1; sta[++ top] = x;
    for(int i = head[x]; i ; i = e[i].nxt) {
        int y = e[i].to; 
        if(!dfn[y]) Tarjan(y), low[x] = min(low[x], low[y]);
        else if(in[y]) low[x] = min(low[x], dfn[y]);
    }
    if(dfn[x] == low[x]) {
        int p; css ++;
        do {
            in[p = sta[top --]] = 0;
            col[p] = css;
        } while(p != x);
    }
}

void Sta_pro() {
    int ans;
    if(siz1 == 0 || siz2 == 0) ans = 0; // ***
    else ans = 1;
    for(int x = 1;x <= n; x++) {
        if(!inse[x] && siz1 > 1) { // ***
            int f = 0;
            for(int i = 0;i < (int) v[x].size(); i++)
                if(inse[v[x][i]]) { f = 1; break; }
            if(!f) ans ++;
        }
        if(inse[x] && siz2 > 1) { // ***
            int f = 0;
            for(int y = 1;y <= n; y++) {
                if(y == x || link[x][y]) continue ;
                if(!inse[y]) { f = 1; break ; } 
            }
            if(!f) ans ++;
        }
    }
    for(int x = 1;x <= n; x++) 
        for(int y = 1;y <= n; y++) 
            if(!inse[x] && inse[y]) {
                int f = 0;
                for(int i = 0;i < (int) v[x].size(); i++) 
                    if(inse[v[x][i]] && v[x][i] != y) { f = 1; break ; }
                if(f) continue ;
                for(int z = 1;z <= n; z++) {
                    if(z == x || z == y || link[z][y]) continue ;
                    if(!inse[z]) { f = 1; break ; }
                }
                if(!f) ans ++;
            }
    printf("%d", ans);
}

void Work2() {
    for(int i = 1;i <= n; i++)
        for(int j = i + 1;j <= n; j++) 
            if(link[i][j]) add(i + n, j), add(j + n, i);
            else add(i, j + n), add(j, i + n);
    for(int i = 1;i <= 2 * n; i++) if(!dfn[i]) Tarjan(i);
    // for(int i = 1;i <= n; i++) cout << i << ":" << col[i] << " " << col[i + n] << "\n";
    for(int i = 1;i <= n; i++) if(col[i] == col[i + n]) {
        printf("0"); return ;
    }
    for(int i = 1;i <= n; i++) inse[i] = col[i] < col[i + n] ? 0 : 1;
    for(int i = 1;i <= n; i++) 
        if(!inse[i]) siz1 ++;
        else siz2 ++;
    // for(int i = 1;i <= n; i++) cout << i << ":" << inse[i] << "\n";
    Sta_pro();
}

int main() {

    freopen("conspiracy.in","r",stdin); freopen("conspiracy.out","w",stdout);

    n = read();
    for(int i = 1, t;i <= n; i++) {
        t = read();
        for(int j = 1, x;j <= t; j++) 
            x = read(), v[i].push_back(x), link[i][x] = 1;
    }
    Work2();

    fclose(stdin); fclose(stdout);

    return 0;
}

/*
4
2 2 3
2 1 3
3 1 2 4
1 3
*/
posted @ 2021-03-01 07:50  C锥  阅读(60)  评论(0编辑  收藏  举报