[做题笔记] 退役前的思维题小练

CF1368E Ski Accidents

题目描述

点此看题

解法

考虑按如下方法把点划分成三个集合 \(A,B,C\)

  • \(A\):入度为 \(0\) 或者只有来自 \(C\) 的入边。
  • \(B\):至少有一条来自 \(A\) 的入边并且没有来自 \(B\) 的入边。
  • \(C\):至少有一条来自 \(B\) 的入边。

初始把入度为 \(0\) 的点设置为 \(A\),然后按照染色的方式跑出 \(B,C\),最后把剩下的点加入 \(A\) 即可。

发现如果我们删除 \(C\),满足 \(A,B\) 集合自身没有边,所以路径长度 \(\leq 1\);并且由于 \(2|A|\geq |B|\)\(2|B|\geq |C|\),所以 \(|C|\leq \frac{4}{7}n\),那么点数也是满足条件的。

这个做法是怎么得来的呢?\(\tt OUYE\) 的一句话让人醍醐灌顶:考虑一个有 \(7\) 个点的完全二叉树。

#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
const int M = 200005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int T,n,m,k,p[M],c[M];vector<int> g[M];
void work()
{
	n=read();m=read();k=0;
	for(int i=1;i<=n;i++)
		c[i]=0,g[i].clear();
	for(int i=1;i<=m;i++)
	{
		int u=read(),v=read();
		g[u].push_back(v);
	}
	for(int u=1;u<=n;u++) for(int v:g[u])
	{
		if(c[u]==1) c[v]=2;
		if(c[v]!=2 && c[u]==0) c[v]=1;
	}
	for(int i=1;i<=n;i++)
		if(c[i]==2) p[++k]=i;
	printf("%d\n",k);
	for(int i=1;i<=k;i++)
		printf("%d ",p[i]);
	puts("");
}
signed main()
{
	T=read();
	while(T--) work();
}

AGC032E Modulo Pairing

题目描述

点此看题

解法

首先我们可以把 \((x+y)\bmod m\) 分为两类:\(x+y<m\)\(x+y\geq m\)

那么如果只有第一类是好做的,从小到大排序之后把最大值和最小值匹配即可;如果只有第二类也是好做的,还是把最大值和最小值匹配;但是本题涉及两类匹配,就不是很好做了。

那么我们考虑融合这两种贪心方法,可以找到分界点 \(p\),使得 \(p\) 左边用第一种贪心,\(p\) 右边用第二种贪心。下图的蓝线表示一类匹配,红线表示二类匹配:

至于这种混合贪心的正确性可以考虑调整法:

img

左边一列的调整是平凡的,右边一类的调整可以考虑:蓝色匹配的代价必然 \(\geq\) 右端点,红色匹配的代价必然 \(<\) 左端点,那么不难发现调整之后最大代价都是变小了的。

现在的问题变成如何找分界点了,不难发现分界点越靠左越好(证明考虑上面的代价不等式),但是太左了可能不满足红线匹配的条件,所以可以二分这个分界点,时间复杂度 \(O(n\log n)\)

总结

混合多种方法的思路十分重要:对于计数问题,混合不同的计数方法可能让需要记录的信息大大减少;对于贪心问题,混合贪心可以让局部最优,我们再考虑怎么从局部最优拓展到整体最优即可;对于图论问题(或者其他问题),混合方法在满足某一条件时快速解决,再满足另一条件时也能快速解决。

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 200005;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,a[M],ans;
int check(int p)
{
	p<<=1;int r=0;
	for(int i=1;i<=p/2;i++)
		r=max(r,a[i]+a[p-i+1]);
	for(int i=1;i<=(n-p)/2;i++)
	{
		int v=a[p+i]+a[n-i+1];
		if(v<m) return 0;v-=m;
		r=max(r,v);
	}
	ans=min(ans,r);
	return 1;
}
signed main()
{
	n=read()<<1;m=read();
	for(int i=1;i<=n;i++) a[i]=read();
	sort(a+1,a+1+n);
	int l=0,r=n/2;ans=1e18;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(check(mid)) r=mid-1;
		else l=mid+1;
	}
	printf("%lld\n",ans);
}

AGC028E High Elements

题目描述

点此看题

解法

我们按位考虑,贪心地填 \(0\) 看是否可行,设 \(\{x\}\) 的前缀最大值 \(mx\),前缀最大值的次数是 \(cx\);设 \(\{y\}\) 的前缀最大值是 \(my\),前缀最大值的次数是 \(cy\),那么我们把问题转化成,找到子序列 \(\{a\}\)\(\{b\}\)

\[mx<a_1<a_2...<a_{n_1},my<b_1<b_2...<b_{n_2} \]

并且满足数量关系 \(cx+n_1=cy+n_2\),由于问题转化之后是子序列问题不是划分问题,所以我们还要添加限制:所有原序列前缀最大值(简称为大哥)都必须出现在子序列中,那么没有出现的元素就可以跟在大哥的后面,它们的影响就被消去了(彩蛋:我自己想这题的时候也是想到的最长上升子序列,只不过没有能力做出这么精妙的转化)

进一步考虑,因为我们只需要构造相等关系,所以可以使用调整法,让两边都去掉一个非大哥,这样相等关系还是成立。所以一定只有一边的子序列只含有大哥

不妨设序列 \(\{a\}\) 只存在大哥,设现在还剩下 \(c\) 个大哥,序列 \(\{b\}\) 占了 \(k\) 个大哥,有 \(m\) 个非大哥,那么满足的关系式子是:\(cx+c-k=cy+k+m\),移项可得 \(cx+c-cy=2k+m\)

由于如果 \(k\) 存在,\(k-2\) 也一定存在(可以把一个大哥调整给对面),并且因为左边是定值,所以我们只需要分奇偶性维护右边的最大值即可,发现右边就是一个最长上升子序列,可以直接用线段树维护,时间复杂度 \(O(n\log n)\)

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 200005;
const int inf = 0x3f3f3f3f;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,a[M],w[M],mx[M<<2][2],cnt[M],ans[M];
void ins(int i,int l,int r,int id,int c,int f)
{
	if(l==r) {mx[i][f]=c;return ;}
	int mid=(l+r)>>1;
	if(mid>=id) ins(i<<1,l,mid,id,c,f);
	else ins(i<<1|1,mid+1,r,id,c,f);
	mx[i][f]=max(mx[i<<1][f],mx[i<<1|1][f]);
}
int ask(int i,int l,int r,int L,int R,int f)
{
	if(L>r || l>R) return -inf;
	if(L<=l && r<=R) return mx[i][f];
	int mid=(l+r)>>1;
	return max(ask(i<<1,l,mid,L,R,f),
	ask(i<<1|1,mid+1,r,L,R,f));
}
int check(int x,int w)
{
	if(w<0) return 0;
	if(w&1) return ask(1,1,n,x,n,1)>=w;
	return ask(1,1,n,x,n,0)>=w;
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=0;i<=4*n;i++) mx[i][1]=-inf;
	for(int i=1,mx=0;i<=n;i++)
	{
		if(a[i]>mx) w[i]=2,mx=a[i];
		else w[i]=1;
	}
	for(int i=n;i>=1;i--)
	{
		int e=ask(1,1,n,a[i],n,0);//even
		int o=ask(1,1,n,a[i],n,1);//odd
		ins(1,1,n,a[i],(w[i]&1)?o+1:e+2,0);//even
		ins(1,1,n,a[i],(w[i]&1)?e+1:o+2,1);//odd
	}
	for(int i=n;i>=1;i--) cnt[i]=cnt[i+1]+w[i]-1;
	int mx=0,my=0,cx=0,cy=0;
	for(int i=1;i<=n;i++)
	{
		ins(1,1,n,a[i],0,0);
		ins(1,1,n,a[i],-inf,1);
		if(check(my,cx+(a[i]>mx)-cy+cnt[i+1])
		|| check(max(mx,a[i]),cy-cx-(a[i]>mx)+cnt[i+1]))
			cx+=(a[i]>mx),mx=max(mx,a[i]);
		else
			cy+=(a[i]>my),my=max(my,a[i]),ans[i]=1;
	}
	if(cx!=cy) {puts("-1");return 0;}
	for(int i=1;i<=n;i++) printf("%d",ans[i]);
	puts("");
}
posted @ 2022-03-16 10:26  C202044zxy  阅读(236)  评论(3编辑  收藏  举报