雅礼学习10.5

雅礼学习10.5

上午考试

各题状况

T1

模拟挂成\(10\)分??

每次更新答案的时候位置搞错了。

想到了可能是线段树动态开点,但没写出来,因为标记下传不会。。。

T2

理解错了题目含义。

选出的\(m\)个物品中,至少要有\(k\)个是\(A\)喜欢的,至少\(k\)个是\(B\)喜欢的

那么很显然只要满足了上面的限制条件,俩人都不喜欢的也能选。。。

但考场上没想到这层

就凉了

正解变骗分,\(15\)

T3

搞完上面两个题目之后没剩多少时间,就随便扔了个东西上去。。

也不知道写的是个啥

题目及考场代码

T1

图片.png
图片.png

/*
 * 第一眼感觉是线段树
 * 想了想,感觉线段树太浪费了,bitset应该可以搞
 * 但是。。。看了数据之后
 * 长度为10^18的01序列
 * 这。。做个头啊做。
 *
 * 第三种操作是0变1,1变0
 *
 * 对于n,m<=1000的数据,可以模拟
 * 对于只有1操作的情况,考虑维护全0区间和全1区间的分界点
 * 对于只有1,2操作的情况,因为是强制变成0或1,所以同样只考虑分界点
 * 对于n<=10^5的情况,线段树应该可以?但是不会维护。
 */
#include <cstdio>
#include <algorithm>

inline long long read()
{
	long long n=0;int w=1;register char c=getchar();
	while(c<'0' || c>'9'){if(c=='-')w=-1;c=getchar();}
	while(c>='0' && c<='9')n=n*10+c-'0',c=getchar();
	return n*w;
}

const int M=1e5+1,qwq[]={0,0,1};
int a[100001],ty[M];
long long l[M],r[M];
std::pair<int,int> pr[M];

int main()
{
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);

	int m,flag=0;long long n=0;
	m=read();
	for(int i=1;i<=m;++i)
	{
		ty[i]=read(),l[i]=read(),r[i]=read();
		n=std::max(n,r[i]);
		flag=std::max(flag,ty[i]);
	}
	if(flag==1)
	{
		int id=1,cnt=0;
		for(int i=1;i<=m;++i)
		{
			if(l[i]<=id && r[i]>=id)
			{
				int x=r[i]+1;
				for(int j=1;j<i;++j)
					if(l[j]<=x && r[j]>=x)
						x=r[j]+1;
				id=x;
			}
			printf("%d\n",id);
		}
	}
	else
		if(n<=100000)
		{
			int id=1;
			for(int x,i=1;i<=m;++i)
			{
				if(ty[i]!=3)
				{
					x=(ty[i]==1?1:0);
					for(int j=l[i];j<=r[i];++j)
						a[j]=x;
				}
				else
					for(int j=l[i];j<=r[i];++j)
						a[j]^=1;
				if(l[i]<=id && id<=r[i])
				{
					for(;l[i]<=r[i];++l[i])
						if(!a[l[i]])
						{
							id=l[i];
							break;
						}
				}
				printf("%d\n",id);
			}
		}
		else
			while(m--)
				puts("1");

	fclose(stdin),fclose(stdout);
	return 0;
}

T2

图片.png

/*
 * 分成两人都喜欢的,只有A喜欢的,只有B喜欢的
 * 然后枚举从两个人都喜欢的物品里面选k个最便宜的
 * 然后贪心选剩下的部分
 *
 * 大样例跑不出来。。
 */
#include <cstdio>
#include <algorithm>

inline int read()
{
	int n=0,w=1;register char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
	while(c>='0'&&c<='9')n=n*10+c-'0',c=getchar();
	return n*w;
}

const int N=2e5+1;
int n,m,k,a[N],b[N],v[N],both[N];
/*
int aaa,bbb;
void dfs(int now,int step,int aa,int bb)
{
	if(aa>k || bb>k)return ;
	if(step==m)
	{
		if(aa==k && bb==k)
			++ans;
		return ;
	}
	if(aaa==now)
	{
		++aaa;
		if(bbb==now)
		{
			++bbb;
			dfs(now+1,step+1,aa+1,bb+1);
			dfs(now+1,step,aa,bb);
			--bbb;
		}
		else
		{
			dfs(now+1,step+1,aa+1,bb);
			dfs(now+1,step,aa,bb);
		}
		--aaa;
	}
	else
		if(bb==now)
		{
			++bbb;
			dfs(now+1,step+1,aa,bb+1);
			dfs(now+1,step,aa,bb);
			--bbb;
		}
}
*/
inline bool cmp(const int &x,const int &y)
{return v[x]<v[y];}

int main()
{
	freopen("b.in","r",stdin);
	freopen("b.out","w",stdout);

	n=read(),m=read(),k=read();
	for(int i=1;i<=n;++i)
		v[i]=read();
	int aa=read(),bb;
	for(int i=1;i<=aa;++i)
		a[i]=read();
	bb=read();
	for(int i=1;i<=bb;++i)
		b[i]=read();

	std::sort(a+1,a+1+aa);
	std::sort(b+1,b+1+bb);
//哪些俩人都喜欢
	int aaa=1,bbb=1,emp=0;
	while(aaa<=aa && bbb<=bb)
	{
		if(a[aaa]>b[bbb])
			++bbb;
		else
			if(a[aaa]<b[bbb])
				++aaa;
			else
			{
				both[++emp]=a[aaa];
				++aaa,++bbb;
			}
	}
/*
	for(int i=1;i<=emp;++i)
		printf("%d ",both[i]);
*/
//
	std::sort(both+1,both+1+emp,cmp);
	long long ans=0;
	for(int i=1;i<=k;++i)
		ans+=v[both[i]];
	
	if(emp>k)
	{//先把都喜欢的减掉
		m-=k;
		k=0;
	}
	else
	{
		m-=emp;
		k-=emp;
	}
//判断剩下的是不是够
	if(m<(k<<1))
	{
		printf("-1");
		fclose(stdin);fclose(stdout);
		return 0;
	}

	int cnt=1;emp=0;
	for(int i=1;i<=aa;++i)
	{
		if(a[i]!=both[cnt])
			a[++emp]=a[i];
		else	++cnt;
	}
	aa=emp;
	cnt=1,emp=0;
	for(int i=1;i<=bb;++i)
	{
		if(b[i]!=both[cnt])
			b[++emp]=b[i];
		else	++cnt;
	}
	bb=emp;

	if(k>aa || k>bb)
	{
		printf("-1");
		fclose(stdin);fclose(stdout);
		return 0;
	}

/*
	for(int i=1;i<=aa;++i)
		printf("%d ",a[i]);
	puts("");
	for(int i=1;i<=bb;++i)
		printf("%d ",b[i]);
*/

	std::sort(a+1,a+1+aa,cmp);
	std::sort(b+1,b+1+bb,cmp);
//然后贪心选剩下的
	for(int i=1;i<=k;++i)
		ans+=v[a[i]]+v[b[i]];
	int i=k+1,j=k+1;
	while(i+j<m)
	{
		if(v[a[i]]>v[b[j]])
			ans+=v[b[j++]];
		else	ans+=v[a[i++]];
	}
	printf("%lld",ans);
	
	fclose(stdin);fclose(stdout);
	return 0;
}

T3

图片.png
图片.png

#include<cstdio>
#include<iostream>
#include<fstream>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
#define Set(a) memset(a,0,sizeof(a))
#define F(i,a,b) for(int i=a;i<=b;++i)
#define UF(i,a,b) for(int i=a;i>=b;--i)
#define openf(a) freopen(#a".in","r",stdin);freopen(#a".out","w",stdout)
typedef long long LL;
typedef long long ll;
typedef unsigned long long ULL;
typedef unsigned long long ull;

const int maxn=400+10;
const int maxm=5e4+10;
ll n,m;
ll u[maxm],v[maxm];
bool used[maxn],a[maxn][maxn];
ll f[maxn],xx;
ll tt;
void check()
{
	xx=0;
	Set(f);
	F(i,1,n) if(!used[i]) f[++xx]=i;
	F(i,1,xx)
	F(j,1,i)
	if(f[i]!=f[j]&&!a[f[i]][f[j]]) {a[f[i]][f[j]]=1;++tt;}
}
void dfs(int step)
{
	if(step==m+1) check();
	ll u1=u[step],v1=v[step];
	if(!used[u1]&&!used[v1]){used[u1]=1;dfs(step+1);used[u1]=0;used[v1]=1;dfs(step+1);used[v1]=0;}
	if( used[u1]&&!used[v1]){used[v1]=1;dfs(step+1);used[v1]=0;}
	if(!used[u1]&& used[v1]){used[u1]=1;dfs(step+1);used[u1]=0;}
	if( used[u1]&& used[v1])dfs(step+1);
	return;
}
int main()
{
	openf(c);
	scanf("%d%d",&n,&m);
	F(i,1,m) scanf("%d%d",&u[i],&v[i]);
	Set(used);
	Set(a);
	dfs(1);
	printf("%lld",tt);
	return 0;
}

正解及代码

T1

离散化线段树

对每个区间,维护一个\(tag\),维护的是最左边的\(0\)和最左边的\(1\)的位置

那么\(1\)操作就是把这个区间的\(tag\)变成区间左端点

然后考虑标记下传

\(1\)操作\(+\)任意操作还是\(1\)操作

\(2\)操作\(+\)任意操作还是\(2\)操作

\(3\)操作\(+1\)操作变成\(2\)操作

\(3\)操作\(+2\)操作变成\(1\)操作

\(3\)操作\(+3\)操作变成没有操作

然后直接离散化+线段树就行了,不需要动态开点


T2

把所有的物品分成:两人都喜欢的,\(A\)喜欢的,\(B\)喜欢的,都不喜欢的

枚举两人都喜欢的物品选了多少个(假设是\(x\)),那么再在\(A\)喜欢的物品中和\(B\)喜欢的点钟至少都还要选\(k-x\)个(用贪心的思路想,这里显然要选权值前\(k-x\)小的那些),然后如果还是没有选够\(m\)个,就在两人都不喜欢的物品中选最小的那些直到满足条件

那么从小到大枚举\(x\),不停求和就行了

T3

对这个问题,考虑一个更一般的问题:假设\(f_k(S)=f_{k-1}(S\cup \{v_i\})\)表示\(k\)个人来过之后,\(S\)集合内的苹果都还存活的概率是否\(\gt 0\),讨论得到三种情况

  • \(u_k,v_k\)都在\(S\)中,那么\(f_k(S)=0\)(两者至少要吃掉一个,所以\(S\)集合内的苹果不可能都剩下)
  • \(u_k,v_k\)有一个在\(S\)中,假设是\(u_k\)\(S\)中,那么\(f_k(S)=f_{k-1}(S\cup \{v_i\})\)
  • \(u_k,v_k\)都不在\(S\)中,那么\(f_k(S)=f_{k-1}(S)\)

那么现在将所有的\(f_m(\{p\})=1\)都求出来,顺便求出\(g(p)\)是从\(f_m(\{p\})\)运行上面算法得到的最终集合

观察得到\((u,v)\)合法的条件就是\(f_m(\{u\})=1,f_m(\{v\})=1,g(u)\cap g(v)=\varnothing\)

直接枚举,即可得到答案

下午讲课:树上的题

例1

给定一棵\(n\)个节点的树,你可以进行\(n−1\)次操作,每次操作步骤如下:

  • 选择\(u,v\)两个度数为\(1\)的节点。
  • \(u,v\)之间的距离加到\(ans\)上。
  • \(u\)从树上删除。

求一个操作序列使得\(ans\)最大。

解:

先把直径拿出来,将直径外的点一个一个的和直径中的某一个端点配对并删掉,最后再将直径删掉。

证明:

如果当前直径外已经没有点了,那么显然只能将直径的端点删掉;否则一定不会去删直径的端点。
因为先删一个直径外的点再删直径端点一定是不劣于先删直径端点再删这个直径外的点的。

比如有这样三个点\(x,y,z\)\(x,z\)是该树直径的两端,
图片.png

假设删除顺序是\(x,y\),那么\(ans+=dis[x][z]+dis[y][z]\)
图片.png

假设删除顺序是\(y,x\),那么\(ans+=\max(dis[x][y],dis[y][z])+dis[x][z]\)
图片.png

例2

给定一张\(n\)个点\(m\)条边的带权无向联通图,\(q\)次询问,每次询问\(u_i\)\(v_i\)的最短路长度

\(n,q\le 10^5,m-n\le 20\)

解:

注意到\(m-n\le 20\),那么这张图其实就是一个树多了屈指可数的几条非树边

先随便搞一棵生成树,那么会有几条边不在这个生成树上,标记那些有非树边连接的点

那么被标记的点最多\(40\)个,先跑\(40\)遍单源最短路求出这些被标记的点到其余所有点的最短路

对于一个询问,如果最短路(跑\(LCA\)来求)只经过生成树上的边,那么直接计算;若经过了被标记的点,就查看走这个点连接的非树边是否会对答案造成影响,尝试更新就行了

例3

给定一棵\(n\)个节点的树,定义叶子是度数为\(1\)的节点,点集\(S\)是好的如果满足任意两个\(S\)中的点之间的距离\(\le k\),现在要求将所有叶子分成若干不相交的好的集合,请问最少分成多少个好的集合。

\(n,k\le 10^6\)

解:

讲师说这类题目一般都可以贪心,我也不知道是哪类题目。。。(话说树上题目有什么分类的么orz)

先随便选一个根,方便起见选一个不是叶子的根,然后可以自底向上贪心。

\(f_p\)表示\(p\)为根的子树内已完成的好的集合的数量,\(g_p\)表示未完成的集合中离\(p\)最远的叶子的距离。

考虑如何计算\(f_p\)\(g_p\)

首先将所有儿子的\(f\)先求和,然后将所有儿子的\(g\)排序,检查 一下最大的\(g\)和次大的\(g\)相加是否大于\(k\),如果大于,那么就将最大的\(g\)删掉,并把\(f_p\)加上\(1\)。否则就令\(g_p\)等于最大的\(g+1\)

例4

给定\(n\)个节点,每个节点上有一个数字,要求用这些节点构建一棵二叉搜索树,满足有边相邻的两个节点上的数字互质
\(n\le 600\)

解:
先排序,令\(f[i][j][k]\)表示第\(i\)个点到第\(j\)个点以\(k\)为根是否可行
转移的时候首先检查是否存在\(u\in [i,k-1]\)满足\(f[i][k-1][u]=1\)\(u\)上的数字跟\(k\)上的数字互质
右侧同理,检查是否存在\(v\in [k+1,j]\)满足\(f[k+1][j][v]=1\)\(v\)上的数字跟\(k\)上的数字互质
图片.png

但是这样是\(O(n^4)\)的(数组是\(n^3\)的,转移是\(n\)的)
考虑优化:
注意到上面的做法有一些重复运算。于是设\(L[i][j]\)表示是否存在\(k\in [i,j]\)满足\(f[i][j][k]=1\)\(k\)上的数字和\(j+1\)上的数字互质,类似地定义\(R[i][j]\)

posted @ 2018-10-06 08:13  快乐永恒  阅读(170)  评论(0编辑  收藏  举报