• 省选模拟测试1


    期望得分: (55+40+29 = 124)

    实际得分: (55+0+29 = 84)

    (T2)(40) 分的换根 (dp) 不知道那里打挂了。

    第一题和第二题都想出来了大概的思路。

    但第一题因为之前没写过回滚莫队,第二题担心点分治复杂度不对,就都打的暴力,直接就 (GG) 了。

    第三题一直在想怎么用容斥加 (dp) 解决,但想了想发现不太好做,打了个爆搜就溜了。

    T1 值域连续

    题意描述

    给你一个长度为 (n) 的排列 (p_1,p_2...p_n),有 (m) 个询问,每次询问问你区间 ([l,r]) 最长的值域连续段的长度。

    值域连续段指的是,从 (p_l-p_r) 中选出一些数,使得这些数排序后构成了连续的一段正整数,那么这段正整数就是一个值域连续段。

    数据范围: 对于 (55\%) 的数据, (n,mleq 30000), 对于 (100\%) 的数据,(n,mleq 50000)

    solution

    考试的时候打了个莫队套线段树,骗到了 (O(msqrt{n} logn))(55) 分。

    回滚莫队 + 并查集。

    对于当前维护的区间,只考虑插入点的操作,只需要用并查集把 (x) 的前驱和后继的所在值域连续段合并起来。

    考虑如何避免删除操作(回滚莫队的思想)

    • 先把询问按左端点所在的块的编号为第一关键字,右端点为第二关键字,不难发现当左端点在同一个块的时候右端点是单调递增的。
    • 当左端点和右端点所在的块相同的时候,块的元素最多只有 (sqrt{n}) 个,我们直接暴力扫即可。
    • 我们依次处理每一个块内的询问,初始化左指针指向块的右端点 (+1) 的地方,右指针指向块的右端点 (这是一个空区间),每做一个询问因为询问的右端点是递增的,因此右指针只需要不断往右移,即不断往里面添加元素。左指针从块的右端点 (+1) 移动到当前询问的左端点,就可以得到这个询问的答案,处理完这个询问则需要把左指针移回块的右端点 (+1) 的地方。

    对于这道题用回滚莫队套带插销的并查集维护一下即可。

    代码(咕咕咕):

    
    

    T2点对游戏

    题意描述

    有三个人 A、B、C,它们在一个有 个结点的树上玩游戏,A、B、C 轮流从树上所有未被占有的节点中选取一点,归为己有,轮流顺序为 A、B、C、A、B、C、A……。该选取过程直到树上所有点都被选取后结束。

    选完点后便可计算每人的得分。点对游戏中有 (m) 个幸运数,对于一个点对 ((u,v)) ,若 $ (u,v)$ 之间的距离是一个幸运数,并且同时被同一个人选中,这个人就得到一分(树上两点之间的距离定义为两点之间的简单路径的边数)。

    你的任务是,假设 A、B、C 每次选取时,都是从未被占有的节点中等概率选取一点,计算每人的期望得分。

    数据范围: (nleq 50000,mleq 10)

    solution

    考试的时候,只打了 (40) 分的换根 (dp), 还写挂了。

    期望加点分治。

    设三个人选的点的数量分别为 (a,b,c), 分类讨论一下就可以得到 (a,b,c) 的取值。

    我们在尝试推一下一个人选 (m) 个点的期望得分的式子,首先总的情况数肯定是 (C_{n}^{m}) ,一个合法点对 (u,v) 会出现 (C_{n-2}^{m-2}) 次 ,所以期望为 (tot imes C_{n-2}^{m-2}over C_ {n}^{m}) ((tot) 即为距离为幸运数的点对数) ,化简可得 (tot imes m imes (m-1)over n imes (n-1))

    然后我们的问题就转化为了 怎么求距离为幸运数的点对数。

    用点分治求解,对于当前的分治重心 (G), 依次遍历他的子树,维护一个 $tong $ 数组,表示长度为 (i) 的路径的条数。

    每遍历完一棵子树,就把子树中的路径和之前已经遍历过的子树中的路径合并一下,用 (tong) 数组就可以得到这一条路径可以和他合并的路径数。

    当处理完 (G) 的所有子树,就把 (tong) 数组清空,再继续分治下去。

    时间复杂度: (O(mnlogn))

    Code

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<vector>
    using namespace std;
    const int N = 1e5+10;
    int n,m,tot,u,v,ans,cnt,root,maxn;
    int head[N],siz[N],max_siz[N],tong[N],b[N],a[N];
    bool vis[N];
    vector<int> p;
    struct node
    {
        int to,net;
    }e[N<<1];
    inline int read()
    {
        int s = 0, w = 1; char ch = getchar();
        while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
        while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
        return s * w;
    }
    void add(int x,int y)
    {
        e[++tot].to = y;
        e[tot].net = head[x];
        head[x] = tot;
    }
    void get_root(int x,int fa)
    {
        siz[x] = 1; max_siz[x] = 0;
        for(int i = head[x]; i; i = e[i].net)
        {
            int to = e[i].to;
            if(to == fa || vis[to]) continue;
            get_root(to,x);
            siz[x] += siz[to];
            max_siz[x] = max(max_siz[x],siz[to]);
        }
        max_siz[x] = max(max_siz[x],maxn-siz[x]);
        if(max_siz[x] < max_siz[root]) root = x;
    }
    void get_dis(int x,int fa,int dep)
    {
        b[++cnt] = dep;
        for(int i = head[x]; i; i = e[i].net)
        {
            int to = e[i].to;
            if(to == fa || vis[to]) continue;
            get_dis(to,x,dep+1);
        }
    }
    void calc(int x)
    {
        tong[0] = 1;
        for(int i = head[x]; i; i = e[i].net)
        {
            int to = e[i].to; cnt = 0;
            if(to == x || vis[to]) continue;
            get_dis(to,x,1);
            for(int j = 1; j <= cnt; j++)
            {
                for(int k = 1; k <= m; k++)
                {
                    if(b[j] <= a[k]) ans += tong[a[k]-b[j]];
                }
            }
            for(int j = 1; j <= cnt; j++)
            {
                tong[b[j]]++;
                if(tong[b[j]] == 1) p.push_back(b[j]);
            }
        }
        for(int i = 0; i < p.size(); i++) tong[p[i]] = 0;
        p.clear();
    }
    void slove(int x)
    {
        vis[x] = 1; calc(x);
        for(int i = head[x]; i; i = e[i].net)
        {
            int to = e[i].to;
            if(to == x || vis[to]) continue;
            root = 0; maxn = siz[to], max_siz[0] = n;
            get_root(to,0); slove(root);
        }
    }
    int main()
    {
        freopen("game.in","r",stdin);
        freopen("game.out","w",stdout);
        n = read(); m = read();
        for(int i = 1; i <= m; i++) a[i] = read();
        for(int i = 1; i <= n-1; i++)
        {
            u = read(); v = read();
            add(u,v); add(v,u);
        }
        root = 0; maxn = max_siz[0] = n;
        get_root(1,1); slove(root);
        if(n % 3 == 0)
        {
        	m = n/3;
        	double sum1 = 1.0 * ans / n / (n-1) * m * (m-1);
        	printf("%.2lf
    %.2lf
    %.2lf
    ",sum1,sum1,sum1);
    	}
    	else
    	{
    		m = n/3;
    		double sum1 = 1.0 * ans / n / (n-1) * m * (m-1);
    		m++;
    		double sum2 = 1.0 * ans / n / (n-1) * m * (m-1);
    		if(n % 3 == 1) printf("%.2lf
    %.2lf
    %.2lf
    ",sum2,sum1,sum1);
    		else printf("%.2lf
    %.2lf
    %.2lf
    ",sum2,sum2,sum1);
    	}
        }
        fclose(stdin); fclose(stdout);
        return 0;
    }
    
    

    T3 集合划分

    题意描述

    给定一张 (n) 个点的无向图,要求把 (n) 个点分入 (A,B) 两个集合,且满足以下条件:

    • 若点 (i,j) 不相连,则点 (i,j) 不能同时分入 (A) 集合。
    • 若点 (i,j) 相连,则点 (i, j) 不能同时分入 (B) 集合。
    • (A, B) 集合均不为空。

    求满足条件的方案数, 无解输出 (0)

    数据范围: (nleq 5000)

    solution

    2-sat + 调整算法。

    你会发现这道题长得像 2-sat,但是 2-sat 求不了方案数,只能求出一个具体的方案出来。

    具体来说就是我们把每个点拆分成 (i)(i+n) 两个点,根据 2-sat 的那套理论,如果 (i)(j) 不相连,则把 ((i,j+n)) , ((j+n,i)) 连边,如果 (i)(j) 相连,则把 ((i+n,j)), ((j+n,i)) 连边。

    建完图之后跑一下 (2-sat) ,就可以得到一个具体的方案。

    我们考虑利用调整算法来得到其他合法的方案,简单来说就是改变任意几个点的所在集合,判断当前划分是否合法。

    这道题有一个比较好的性质,就是不能同时把 (A) 集合中的两个即以上的点换到 (B) 集合当中,同理也不能同时把 (B) 集合中的两个即以上的点换到 (A) 集合当中。

    那我们的调整途径只剩下三种:

    • 情况1:把 (A) 集合中的一个点换到 (B) 集合中。
    • 情况2:把 (B) 集合中的一个点换到 (A) 集合中。
    • 情况3:把 (A) 集合中的一个点换到 (B) 集合中,在把 (B) 集合中的一个点换到 (A) 集合中。

    对于情况 (1) 我们枚举 (A) 集合中的点 (i), 在枚举 (B) 集合中的点 (j) ,如果 (i,j) 相连,那么点 (i) 就不能换到 (B) 集合中。

    对于情况 (2) ,同情况 (1)

    对于情况 (3) ,我们枚举 (A) 集合中的点 (i)(B) 集合中的点 (j), 表示我们需要换到另一个集合的两个点,还需要枚举一遍 (A,B) 集合中的其他点来判断这次调整是否合法。

    总的时间复杂度就是 (O(n^3)) 的,但一般来说是不会达到这个上界的。

    还有 (O(n^2)) 的做法,但实现起来要比这个方法要难很多。

    一些剪枝:

    • 对于集合 (A) 中的点 (i),预处理出 (B) 集合中和他相连的点的个数,记为 (num1) ,显然如果 (num1geq 2), 那个点 (i) 是无论怎么样都不能换到 (B) 集合中的,因为如果点 (i) 换到 (B) 集合中,那么 (B) 集合中和他相连的点都要被换到 (A) 集合中,但我们不能同时把 (B) 集合中的两个即两个以上的点换到 (A) 集合中,因此点 (i) 是不可能被换到 (B) 集合中去的。
    • 对于集合 (B) 中的点 (i),预处理出 (A) 集合中和他不想连的点的个数,记为 (num2), 如果 (num2geq 2), 那么点 (i) 是不可能被换到 (A) 集合中的。证明同剪枝1。

    事实证明上面的两个剪枝对于情况 (3) 的优化是非常多的。

    一些坑点:

    • 如果我们跑完 2-sat 之后得到的那个具体方案的 (A,B) 集合都不为空,答案的初值为 (1), 否则为 (0)
    • 对于情况 (1),我们还需要判断把 (A) 集合中的一个点换到 (B) 集合中后, (A) 集合是否为空,如果为空,这种情况也是不合法的。对于情况 (2) 同理。
    • 存边的数组要开大一些,因为最多会有 (3e7) 条边,(别问,问就是一遍遍调参得到的)。

    Code

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<vector>
    using namespace std;
    const int N = 2e4+10;
    int n,m,tot,ans,num,cnt,cnta,cntb,k,x,num1,num2,top;
    int head[N],dfn[N],low[N],shu[N],sta[N],p[5010][5010],a[N],b[N],sum[N];
    bool vis[N];
    vector<int> v[N];
    struct node
    {
    	int to,net;
    }e[30000010];
    inline int read()
    {
    	int s = 0, w = 1; char ch = getchar();
    	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
    	return s * w;
    }
    void add(int x,int y)
    {
    	e[++tot].to = y;
    	e[tot].net = head[x];
    	head[x] = tot;
    }
    void tarjain(int x)
    {
    	dfn[x] = low[x] = ++num;
    	vis[x] = 1; sta[++top] = x;
    	for(int i = head[x]; i; i = e[i].net)
    	{
    		int to = e[i].to;
    		if(!dfn[to])
    		{
    			tarjain(to);
    			low[x] = min(low[x],low[to]);
    		}
    		else if(vis[to]) low[x] = min(low[x],dfn[to]);
    	}
    	if(dfn[x] == low[x])
    	{
    		cnt++; int y;
    		do
    		{
    			y = sta[top--];
    			shu[y] = cnt;
    			vis[y] = 0;
    		}while(x != y);
    	}
    }
    int main()
    {
    	freopen("conspiracy.in","r",stdin);
    	freopen("conspiracy.out","w",stdout);
    	n = read();
    	for(int i = 1; i <= n; i++)
    	{
    		k = read();
    		for(int j = 1; j <= k; j++)
    		{
    			x = read();
    			p[i][x] = 1;
    		}
    	}
    	for(int i = 1; i <= n; i++)
    	{
    		for(int j = i+1; j <= n; j++)
    		{
    			if(p[i][j] == 0) add(i,j+n), add(j,i+n);
    			else add(j+n,i), add(i+n,j);
    		}
    	}
    	for(int i = 1; i <= 2*n; i++) if(!dfn[i]) tarjain(i);
    	for(int i = 1; i <= n; i++)
    	{
    		if(shu[i] == shu[i+n])
    		{
    			printf("%d
    ",0);
    			return 0;
    		}
    		if(shu[i] <= shu[i+n]) a[++cnta] = i;
    		else b[++cntb] = i;
    	}
    	if(cnta == 0 || cntb == 0) ans = 0;
    	else ans = 1;
        //如果跑完2-sat所得到的方案中 A,B集合有一个集合为空,答案的初值为0,否则为1
    	if(cnta > 1)//如果把A集合中的一个点换到B集合中去,A集合为空,那么这次调整也是不合法的
    	{
    		for(int i = 1; i <= cnta; i++)
    		{
    			int flag = 1;
    			for(int j = 1; j <= cntb; j++)
    			{
    				if(p[a[i]][b[j]] == 1) flag = 0, sum[a[i]]++;
    			}
    			ans += flag;
    		}
    	}
    	if(cntb > 1)//保证调整之后B集合不为空
    	{
    		for(int i = 1; i <= cntb; i++)
    		{
    			int flag = 1;
    			for(int j = 1; j <= cnta; j++)
    			{
    				if(p[b[i]][a[j]] == 0) flag = 0, sum[b[i]]++;
    			}
    			ans += flag;
    		}
    	}
    	for(int i = 1; i <= cnta; i++)//情况3
    	{
    		if(sum[a[i]] >= 2) continue;//剪枝1
    		for(int j = 1; j <= cntb; j++)
    		{
    			if(sum[b[j]] >= 2) continue;//剪枝2
    			int flag = 0;
    			for(int k = 1; k <= cntb; k++)
    			{
    				if(j != k && p[a[i]][b[k]] == 1) flag = 1;
    				if(flag) continue;
    			}
    			if(flag) continue;
    			for(int k = 1; k <= cnta; k++)
    			{
    				if(i != k && p[b[j]][a[k]] == 0) flag = 1;
    				if(flag) continue;
    			}
    			if(flag) continue;
    			if(flag == 0) ans++;
    		}
    	}
    	printf("%d
    ",ans);
    	fclose(stdin); fclose(stdout);
    	return 0;
    }
    
  • 相关阅读:
    自己写个pager控件
    再忙也不能忽视的东西
    ACE_Reactor学习2 Reactor类API的功能分类
    ACE_Reactor学习3 ACE_Reactor初始化相关的实现分析
    ACE_Reactor学习1 总体计划
    windows下信号机制的学习
    咋了
    C#编写Window服务
    Javascript引用类型小结,及字符串处理
    .NET调用控制台下生成的exe文件,传参及获取返回参数
  • 原文地址:https://www.cnblogs.com/genshy/p/14461630.html
Copyright © 2020-2023  润新知