正睿线上集训1

正睿线上集训1

题目链接

A

神仙贪心题,没怎么听懂

B

首先,这道题看上去就非常根号分治,实际上也是如此.

先考虑一下模型转化,首先一个常识树的点数-边数= 1,森林中的连通块数等于点数减边数,那么考虑这道题目

我们如果对于相邻的两个不同颜色连边,那么极长的亮灯区间个数实质上是可以看作是亮灯的个数减去连续的亮灯个数

第一个东西我们可以十分方便的维护,重点来看一下第二个东西什么办

由于一段连续的同一种颜色和一个该种颜色的点没有什么区别,所以我们就把一段连续的区间压缩成一个点(这一个区间要么同时亮,要么同时暗,亮的话点- 边恒等于1所以和一个点没区别)

压缩完成之后(压缩的一个实用小技巧详见代码第一行),我们对于相邻的颜色建边,存vector(1和n)需要特殊处理让他们向\(0\)颜色建边,这样能够保证只会有点的贡献而不会有边的贡献

这样首先我们考虑边数小于\(\sqrt{n}\)的点,对于这样的点,我们就直接暴力去判断是否这一条边的两个顶点由一亮一暗变为都亮或者变成一亮一暗而导致边数发生变化

大于\(\sqrt{n}\)的点,我们可以考虑它连接的所有点,如果是一个大于\(\sqrt{n}\)的点,我们可以预处理出他们之间的边数.然后对于大点我们就可以\(O(\sqrt{n})\)的枚举其他的大点,然后用预处理的数组来和上面一样算引起的边数变化

如果一个大于\(\sqrt{n}\)的点连接了一个小于\(\sqrt{n}\)的点,我们是不太好办的

那么我们考虑把贡献放到小点上去算,设\(tag_x\)表示\(x\)这个大点连接的小点中亮着的数量

每次枚举小点所连接的边的过程中,我们就判断如果是个大点就把它的\(tag\)值做出相应的改变,最后我们就可以利用tag数组和预处理的数组来计算大点对边的数量的影响

#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<vector>
#include<ctime>
#include<cmath>
#include<set>
#include<map>
#define LL long long
#define pii pair<int,int>
#define mk make_pair
#define fi first
#define se second
using namespace std;
const int N = 2e5 + 3;
const int block = 499;
int e[505][505];
vector <int> G[N];
vector <int> BIG;
int n,m,k;
int co[N],num[N],idx[N];
bool book[N];
int tag[N];
inline int read()
{
	int v = 0,c = 1;char ch = getchar();
	while(!isdigit(ch))
	{
		if(ch == '-') c = -1;
		ch = getchar();
	}
	while(isdigit(ch))
	{
		v = v * 10 + ch - 48;
		ch = getchar();
	}
	return v * c;
}
int main(){
	n = read(),m = read(),k = read();
	for(int i = 1;i <= n;++i)
	{
		co[i] = read();
		if(co[i] == co[i - 1])
		{
			i--;
			n--;
			continue;	
		}
		num[co[i]]++;
	}
	co[0] = 0,co[n + 1] = 0;
	for(int i = 0;i <= n;++i)
	{
		G[co[i]].push_back(co[i + 1]);
		G[co[i + 1]].push_back(co[i]);	
	}
	for(int i = 1;i <= k;++i)
	{
		if(G[i].size() > block)
		{
			BIG.push_back(i);
			idx[i] = BIG.size();
		}
	}
	for(int u = 0;u < (int)BIG.size();++u)
	{
		for(int v = 0;v < (int)G[BIG[u]].size();++v)
		{
			int x = BIG[u],y = G[x][v];
			if(G[y].size() > block) e[idx[x]][idx[y]]++;	
		}
	}
	int V = 0,E = 0;
	while(m--){
		int x = read();
		int w = book[x] ? -1 : 1;
		V += w * num[x];
		if(G[x].size() < block){
			for(int i = 0;i < (int)G[x].size();++i){
				if(G[G[x][i]].size() < block){
					if(book[x] == 1 && book[G[x][i]] == 1) E--;
					if(book[x] == 0 && book[G[x][i]] == 1) E++;			
				}
				else{
					if(book[x] == 1 && book[G[x][i]] == 1) E--;
					if(book[x] == 0 && book[G[x][i]] == 1) E++;	
					if(book[x] == 0) tag[G[x][i]]++;
					else tag[G[x][i]]--;
				}
			}
		}
		else{
			for(int i = 0;i < (int)BIG.size();++i){
				if(x == BIG[i]) continue;
				if(book[x] == 1 && book[BIG[i]] == 1) E -= e[idx[x]][idx[BIG[i]]];
				if(book[x] == 0 && book[BIG[i]] == 1) E += e[idx[x]][idx[BIG[i]]];
			}
			E += w * tag[x];
		}
		printf("%d\n",V - E);
		book[x] ^= 1;
	}
	return 0;
}

C

首先,\(2^n\)枚举子集的做法应该比较裸

我们先证明一个东西:在一个确定的胜负关系下,对于一个确定的\(k\),最多只存在一组合法解 因为如果存在两组合法解\(S,T\)

那么必定存在\(u,v\)使得\(u\in S,u\notin T,v\in T,v\notin S\)

也就是说\(u\)\(v\)在两个集合中的胜负关系不唯一,所以最多一组和法解

现在求存在合法解的概率

考虑一个比较朴素的\(n^2\),\(f_{i,j}\)表示前\(i\)个人选了\(j\)个胜利者的概率

转移暴力枚举第\(i + 1\)个人是赢得还是输的

\[f_{i + 1,j} = f_{i,j}\times p^{j} + f_{i,j - 1} \times q^{i - j + 1} \]

其中\(q = 1 - p\)

这样我们就得到了一个\(n^2\)做法

接下来是神仙操作

我们把上面的DP倒过来,从大向小插入人

\[f_{i + 1,j} = f_{i,j} \times q^j + f_{i,j - 1} \times p^{i - j + 1} \]

之后我们可以得到

\[f_{i,j} \times p^j + f_{i,j - 1}\times q^{i- j + 1} =f_{i,j} \times q^{j} +f_{i,j - 1}\times p^{i - j + 1} \]

移项

\[f_{i,j} = f_{i,j - 1}\times \frac{(p^{i - j + 1}-q^{i - j + 1})}{p^j - q^j} \]

之后我们发下,我们得到了\(j\)的递推式!

边界有\(f_{i,0} = 1\)就是可以不颁奖,概率为\(1\)

到这里就结束了么?没有

上面的时刻成立必须满足分母不为\(0\)

也就是\(p \not=q\)

\(p = q\)是应该怎么做,这时候发现胜负和编号没有关系了,也就是我们直接组合数学

枚举\(k\)个人颁奖

\[ans_k = \binom{n}{k}\frac{1}{2^{k\times (n - k)}} \]

直接把爆算即可

#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<vector>
#include<ctime>
#include<cmath>
#include<set>
#include<map>
#define LL long long
#define pii pair<int,int>
#define mk make_pair
#define fi first
#define se second
using namespace std;
const int N = 2e5 + 3;
const int block = 499;
int e[505][505];
vector <int> G[N];
vector <int> BIG;
int n,m,k;
int co[N],num[N],idx[N];
bool book[N];
int tag[N];
inline int read()
{
	int v = 0,c = 1;char ch = getchar();
	while(!isdigit(ch))
	{
		if(ch == '-') c = -1;
		ch = getchar();
	}
	while(isdigit(ch))
	{
		v = v * 10 + ch - 48;
		ch = getchar();
	}
	return v * c;
}
int main(){
	n = read(),m = read(),k = read();
	for(int i = 1;i <= n;++i)
	{
		co[i] = read();
		if(co[i] == co[i - 1])
		{
			i--;
			n--;
			continue;	
		}
		num[co[i]]++;
	}
	co[0] = 0,co[n + 1] = 0;
	for(int i = 0;i <= n;++i)
	{
		G[co[i]].push_back(co[i + 1]);
		G[co[i + 1]].push_back(co[i]);	
	}
	for(int i = 1;i <= k;++i)
	{
		if(G[i].size() > block)
		{
			BIG.push_back(i);
			idx[i] = BIG.size();
		}
	}
	for(int u = 0;u < (int)BIG.size();++u)
	{
		for(int v = 0;v < (int)G[BIG[u]].size();++v)
		{
			int x = BIG[u],y = G[x][v];
			if(G[y].size() > block) e[idx[x]][idx[y]]++;	
		}
	}
	int V = 0,E = 0;
	while(m--){
		int x = read();
		int w = book[x] ? -1 : 1;
		V += w * num[x];
		if(G[x].size() < block){
			for(int i = 0;i < (int)G[x].size();++i){
				if(G[G[x][i]].size() < block){
					if(book[x] == 1 && book[G[x][i]] == 1) E--;
					if(book[x] == 0 && book[G[x][i]] == 1) E++;			
				}
				else{
					if(book[x] == 1 && book[G[x][i]] == 1) E--;
					if(book[x] == 0 && book[G[x][i]] == 1) E++;	
					if(book[x] == 0) tag[G[x][i]]++;
					else tag[G[x][i]]--;
				}
			}
		}
		else{
			for(int i = 0;i < (int)BIG.size();++i){
				if(x == BIG[i]) continue;
				if(book[x] == 1 && book[BIG[i]] == 1) E -= e[idx[x]][idx[BIG[i]]];
				if(book[x] == 0 && book[BIG[i]] == 1) E += e[idx[x]][idx[BIG[i]]];
			}
			E += w * tag[x];
		}
		printf("%d\n",V - E);
		book[x] ^= 1;
	}
	return 0;
}

posted @ 2019-10-14 22:00  wyxdrqcccc  阅读(...)  评论(...编辑  收藏