Educational Codeforces Round 64 (Rated for Div. 2) (A-F)

心得

体验较差的一场CF,被A题卡爆了不说,C题用一个卡常的方法卡过去了……

D、E、F一个树形dp,一个单调栈,一个概率dp都没写出来,好好补题吧……

A. Inscribed Figures(特判)

题意

给你n(n<=100)个数,每个数的值∈{1,2,3},保证相邻的不重复

其中1代表一个圆,2代表一个高和底相等的底边和x轴平行的等腰三角形,3代表一个底边和x轴平行的正方形

给定的序列里,后一个图形是前一个的最大内接图形,

问最多有多少个不同的相交点,无穷时输出无穷

题解

注意3 1 2时三角形和圆会有一个公共点,其余就按+3,+4,无穷的方法判就好

B.Ugly Pairs(构造)

题意

给定样例数T(T<=100),每个样例包含一个长为s(s<=100)的小写字母串,

问是否存在一种重排方式使得新串的相邻字母不为字母表里的相邻字母(认为a和z不相邻),

如果存在输出任意一种,否则输出No answer

题解

这题我也不知道大家怎么写的,自己很zz的分类讨论

先按基数排序的思想把每个字母出现的次数归到一起,

打算相同的字母相邻输出,再把出现的字母排个序,

①一种字母,输出原串

②两种字母,如果相邻输出No,否则输出原串

③三种字母,如果两个均相邻(形如aabbcc)输出No,否则总能全排列找到一种不相邻的,输出即可

④四种字母,样例里有个abcd输出cadb的,直接扒过来按3、1、4、2输出

⑤五种及五种以上,先按奇数输出,再按偶数输出,也就是按排名依次输出1,3,5,2,4,6……

C. Match Points(二分)

题意

有n(2<=n<=2e5)个数在数轴上,每个数的位置为xi(1<=xi<=1e9),可能有重复位置的数

给定一个数z(1<=z<=1e9),问最多能有多少数对(i,j)满足xi和xj之间的距离不小于z,

数对里的数只能使用一次,也就是出现在这个数对里之后不能出现在其它数对中

题解

正经题解是先排序,然后二分最终的答案数为k对,

每次将x_{1},...,x_{k}x_{n-k+1},...,x_{n}一一对应配对,可以的话增大k,否则缩小k

口胡一下证明大概就是,

如果x1能和xn配对,那么需要两对时,x1可以将能与其配对的往前挪一挪,把xn留给x2,因为xn是距x2最远的

已经匹配k对的情况下,把这k对都往前挪一挪,然后把xn给xk+1,

注意到是有这个单增性质的,也就是挪到某处的时候,答案不能再增加了,所以二分即可

被自己用一个二分+贪心+vector的erase卡常的方法卡过去了,

就是排序之后把数分成前后两半,然后每次x1去后半段里找最小的xi使之能与自己匹配,

匹配之后就把右边的删了,其实这个贪心也没什么毛病,保证答案相同的时候能匹配的最左罢了, 

只是erase的写法理论复杂度是O(n)的,只是由于STL实现略快,卡过了n=20W

想不被卡的话可以用线段树把[后半段的第一个,xi]都赋成0,然后再在线段树上二分

D. 0-1-Tree(树形dp)

题目

有一棵n(2<=n<=2e5)个点的树,树边有边权∈{0,1},

在树上遍历时,如果从点x出发,要去点y

遍历时,走过边权为1的边之后,就不能再走边权为0的边

问有多少对(x,y),满足从x出发,能到达点y

思路来源

CF红名玩家step_by_step代码

凡神

题解

①考虑最后合法的(x,y)的遍历的边权序列,一定是000000,111111,00001111的一种

那就根据权为0和权为1分别建图,前二者可以通过统计0的联通块和1的联通块的大小来实现,

如果有一个全为0边权的联通块,那么这个块上任意两点(xi,xj)都可达,且(xj,xi)也可达,

A_{num}^{2}即可,num为联通块大小,全为1的联通块同理

第三种情况,考虑枚举边权0和边权1交界处的点,那么这个点的贡献就是

(这个点所在的0的联通块减去自己的数量的点 作起点x)*(这个点所在的1的联通块减去自己的数量的点 作终点y)这样的(x,y)的对数

考虑到相同的(x,y)在树上只有一条路可达,所以这个中间的分界点是唯一的,故统计不会重复

三者相加即为答案

②也可以考虑每个点的贡献,是以它为起点的联通块siz[0]-1和联通块siz[1]-1,还有以它为01分界点的(siz[0]-1)*(siz[1]-1),

开两个并查集遇到边就合并就行,或者同上dfs在搜索的过程中统计大小

qls的代码写的可读性也很强啊,是利用树形dp在回溯的时候上游和下游两段拼一起的答案,然而我太菜了看不懂哭唧唧

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+10;
typedef pair<int,int> P;
vector<int>E[maxn][2];
vector<int>cnt;
int n,u,v,c; 
ll sz,ans,num[maxn][2];
bool vis[maxn];
void dfs(int u,int fa,int op)
{
	cnt.push_back(u);
	vis[u]=1;
	for(int i=0;i<E[u][op].size();++i)
	{
		int &v=E[u][op][i];
		if(v==fa||vis[v])continue;
		dfs(v,fa,op);
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;++i)
	{
		scanf("%d%d%d",&u,&v,&c);
		//把0边和1边分开建图 
		E[u][c].push_back(v);
		E[v][c].push_back(u);
	}
	memset(vis,0,sizeof vis);
	for(int i=1;i<=n;++i) 
	{
		if(!vis[i])
		{
			cnt.clear();
			dfs(i,-1,0);
			sz=cnt.size();
			ans+=sz*(sz-1);//0联通块里的点对 
			for(int i=0;i<sz;++i)
			num[cnt[i]][0]=sz;
		} 
	}
	memset(vis,0,sizeof vis);
	for(int i=1;i<=n;++i) 
	{
		if(!vis[i])
		{
			cnt.clear();
			dfs(i,-1,1);
			sz=cnt.size();
			ans+=sz*(sz-1);//1联通块里的点对 
			for(int i=0;i<sz;++i)
			num[cnt[i]][1]=sz;
		} 
	}
	for(int i=1;i<=n;++i)//枚举0边和1边的分界点i,前后的点构成的点对,要求路径上至少有0和1两条边,且路径形如00001111 
	ans+=(num[i][0]-1)*(num[i][1]-1);
	printf("%I64d\n",ans);
	return 0;
} 

E. Special Segments of Permutation(单调栈)

题目

如果某个子区间[l,r]满足,两个端点的值al+ar的和等于这个区间的最大值,那就称这个子区间是特殊的,

给定一个1-n(3<=n<=2e5)的排列,问有多少子区间是特殊的,

思路来源

CF红名玩家step_by_step代码

题解

这是一个简单题鸭,可惜昨天被ABC打击了没有搞出来

单调栈搞一发,把每个值ai是最大值的覆盖范围[L[i],R[i]]搞出来

枚举最大值ai,[L[i],R[i]]内,如果有两个值之和等于这个最大值ai,

一定是左半段取一个,右半段取一个,在读入的时候记录下来每个值的位置

左半段和右半段哪一半短就枚举哪一半的值aj,检查另一半里是不是有想要的值ai-aj

最大值区间是有互相覆盖的,但据说复杂度是O(nlogn)

翻代码的时候 也看到了什么并查集什么set什么分治的做法 学不来学不来

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
const int INF=0x3f3f3f3f;
int n,a[maxn],pos[maxn];
int stk[maxn],l[maxn],r[maxn],tot;
int ans;
int main()
{
	scanf("%d",&n);
	a[0]=a[n+1]=INF;
	for(int i=1;i<=n;++i)
	{
	    scanf("%d",&a[i]);
	    pos[a[i]]=i;
    }
	tot=0;
	stk[++tot]=0;
	for(int i=1;i<=n;++i)
	{
		while(a[i]>=a[stk[tot]])tot--;
		l[i]=stk[tot]+1;
		stk[++tot]=i;
	}
	tot=0;
	stk[++tot]=n+1;
	for(int i=n;i>=1;--i)
	{
		while(a[i]>a[stk[tot]])tot--;
		r[i]=stk[tot]-1;
		stk[++tot]=i;
	}
	for(int i=1;i<=n;++i)
	{
		if(i-l[i]<r[i]-i)//左半段短,扫左半段,判断需要的值是否在右半段 
		{
			for(int j=l[i];j<=i;++j)
			{
				int need=a[i]-a[j];
				if(need>=1&&need<=n&&pos[need]>=i&&pos[need]<=r[i])
				ans++;
			}
		}
		else//右半段短,扫右半段,判断需要的值是否在左半段 
		{
			for(int j=i;j<=r[i];++j)
			{
				int need=a[i]-a[j];
				if(need>=1&&need<=n&&pos[need]>=l[i]&&pos[need]<=i)
				ans++;
			}
		}
	}
	printf("%d\n",ans);
	return 0;
} 

F. Card Bag

题目

给你n(2<=n<=5000)个卡片,第i个卡片上有一个数ai(1<=ai<=n),

每次你可以选择一个之前没选过的卡片,第一次随便选,

以后每次选的如果比上次小就输了,如果和上次相等就赢了,否则游戏继续,

如果卡片没了就输了,问赢的概率,

答案如果是P/Q的形式,输出P*Q^{-1}mod(998244353)

思路来源

CF红名玩家step_by_step代码

题解

只统计赢的,因此直接增序访问卡片,

若在游戏在第j轮时还没结束,

面对卡片i,有三种选择,

①不选卡片i

②用接下来的一轮选择卡片i,然后去选卡片i+1

③用接下来的两轮选择卡片i,然后获胜

①和②是01背包,③对答案有方案数贡献,

dp[j]统计有多少种方案能使游戏进行到第j轮不结束即可

初始化部分搞的是分母的逆元,比如n==10,那么第一次是10选1,第二次9选1,以此类推

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=998244353;
const int maxn=5005;
ll rev[maxn],inv[maxn];
ll dp[maxn],cnt[maxn];
int n,v;
ll ans;
//dp[i]表示i轮后游戏不结束的方案数 
ll modpow(ll x,ll n,ll mod)
{
	ll ans=1;
	for(;n;n/=2,x=(x*x)%mod)
	if(n&1)ans=(ans*x)%mod;
	return ans;
}
void init()
{
	rev[0]=1;
	for(int i=1;i<=n;++i)
	rev[i]=rev[i-1]*(n+1-i)%mod;//rev[1]=n rev[2]=n*(n-1) 
	for(int i=1;i<=n;++i)
	inv[i]=modpow(rev[i],mod-2,mod);//inv[1]=1/n inv[n]=1/n! 
}
int main()
{
	scanf("%d",&n);
	init(); 
	dp[0]=1;
	for(int i=1;i<=n;++i)
	{
		scanf("%d",&v);
		cnt[v]++;
	} 
	for(int i=1;i<=n;++i)//注意到 一定要增序选才能不负 
	{
		if(cnt[i]>=2)//从j轮起 连续选择2轮i从而获胜 即用i来终止局面 
		{
			for(int j=0;j<=n;++j)//游戏进行了j轮 再选两轮i
			//注意到j+2可能超出n轮 用rev[j+2]==0从而保证对答案无贡献 
		    ans=(ans+dp[j]*cnt[i]%mod*(cnt[i]-1)%mod*inv[j+2]%mod)%mod; 
		} 
		for(int j=n-1;j>=0;--j)//01背包 即不用i来终止局面
		//让游戏不结束的情况下 选择1轮或0轮i 
		dp[j+1]=(dp[j+1]+dp[j]*cnt[i])%mod;
	}
	printf("%I64d\n",ans); 
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Code92007/article/details/89761589