Educational Codeforces Round 49 (Rated for Div. 2) (D/dfs找环+E/线性dp+F/并查集)

本文精选三道算法竞赛题目,包括最小捕鼠夹放置成本、满足特定条件的染色方案数以及考试时间安排问题的解决方案。通过对每个问题的深入解析,提供详细的思路和代码实现,适合算法爱好者和竞赛选手学习参考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

思路来源

https://codeforces.com/blog/entry/61311(官方题解)

https://www.cnblogs.com/tobyw/p/9513876.html(E题)

D.Mouse Hunt

n(n<=2e5)个房间,有一只老鼠,可能在t=0时出现在任意房间,

第i个房间放捕鼠夹的代价是ci(1<=ci<=1e4),

在这一秒出现在i房间的老鼠下一秒出现的房间号是ai(1<=ai<=n)

问最小的放捕鼠夹代价和,使得老鼠出现在任意一个房间都能被捕

题解

从一个点出发,要么到下一个点,要么到自身

所以每个连通块要么是一个ρ字形,要么是一个环(含自环)

考虑到,环上必须放捕鼠夹,且只放在环上就能保证该连通块可捕鼠,

那么放在环上的最小cost房间上即可,

dfs统计每个连通块的环,将每个环上的最小值计入贡献

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+10;
int n,a[maxn+2],c[maxn+6];
int cnt;//cnt个cycle 
vector<int>cycle[maxn+5];
int vis[maxn+3];//为0未访问 为1在栈中 为2已访问 
int path[maxn+4],tot,tmp;
int mn,len,v;
ll ans;
void dfs(int u)
{
	path[++tot]=u;//记录当前路径,入栈 
	vis[u]=1;
	int v=a[u];//实际不用建图,找后继即可 
	if(vis[v]!=2)
	{
		if(vis[v]==1)//发现环 
		{
			cnt++;
			tmp=tot;//只是记录 并不在记录环时退栈 
			while(path[tmp]!=v)cycle[cnt].push_back(path[tmp--]);//退栈记录环 其实就是tarjan 
			cycle[cnt].push_back(v);
		}
		else dfs(v);
	} 
	--tot;//退栈 
	vis[u]=2;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	scanf("%d",&c[i]);
	for(int i=1;i<=n;++i)
	scanf("%d",&a[i]);
	for(int i=1;i<=n;++i) 
	if(!vis[i])dfs(i);
	for(int i=1;i<=cnt;++i)
	{
		len=cycle[i].size();
		mn=c[cycle[i][0]];//记录环上点pos 对应权值c[pos] 
		for(int j=0;j<len;++j)
		{
			v=cycle[i][j]; 
			mn=min(mn,c[v]);
		}
		ans+=mn; 
	}
	printf("%I64d\n",ans);
	return 0;
}

E.Inverse Coloring

n*n(1<=n<=500)的方格板,每个方格可以涂黑或白色,

求满足下列条件的方案数:

①相邻行的颜色完全相同或完全相反

②相邻列的颜色完全相同或完全相反

③单一颜色的联通块长方形面积小于给定的k(1<=k<=n*n)

题解

用01序列来代表行的染色情况,那么有01对应黑白①和01对应白黑②两种,

如果行的01序列、列的01序列确定,就可以按数独一样,确定整张图

再在染色方法①、②中选一种,唯一确定这张图

把01序列转化成连续的计数,如011000转化为123,

那么根据坐标离散化,新的点的面积=离散化的行宽*离散化的列高,

要求每个矩形的面积都小于k,即最大值小于k,即行宽最大值*列高最大值<k(因为x行y列一定会有一个交叉点(x,y))

考虑和为n,最大值为m的方案数dp[n][m]:

该方案数可以由其子情况补上一个不大于m的数得来,可以补1...2...m

即dp[n][m]=dp[n-1][m]+(dp[n-2][m]+...+dp[n-m][m])③

类比有dp[n-1][m]=(dp[n-2][m]+...+dp[n-m][m])+dp[n-m-1][m]④

④代③有,dp[n][m]=2*dp[n-1][m]-dp[n-m-1][m]

前提是n-m-1>=0,即n>=m+1,这也意味着dp[0][m]得赋值为1,作为base情况

而n<=m的情况,意味着最大值大于等于n,即整数n任意划分

n个球n-1个空任意放挡板的方案数2^{n-1}

dp[n][m]=2*dp[n-1][m]-dp[n-m-1][m](n>m)

dp[n][m]=2^{n-1} (n<=m)

注意到最大值具有前缀和的性质,即行max为m,是这一行的max不超过m,

统计这一行max恰为m的方案数,用max[m]-max[m-1]即可

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=998244353;
const int maxn=500;
const int N=maxn+5;
//dp[i][j]表示和为i最大值为j的方案数 
int n,k,dp[N][N];
int ans,now;
void add(int &x,int y)
{
	x+=y;
	if(x<0)x+=mod;
	if(x>=mod)x-=mod;
}
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=maxn;++i)
	dp[0][i]=1;//底部条件 便于n-m-1==0 
	dp[1][1]=1;//dp[n][n]=2^(n-1) n个任取 
	for(int i=2;i<=maxn;++i)
	dp[i][i]=(dp[i-1][i-1]*2)%mod;
	for(int i=1;i<=maxn;++i)
	{
		for(int j=1;j<i;++j)
		dp[i][j]=(2*dp[i-1][j]%mod-dp[i-j-1][j]%mod+mod)%mod;
		for(int j=i+1;j<=maxn;++j)
		dp[i][j]=dp[i][i]; 
	}
	for(int i=1;i<=n;++i)//枚举行max 由行max*列max<=k-1可得 
	{
		now=dp[n][i]-dp[n][i-1];
		now=1ll*now*dp[n][min((k-1)/i,n)]%mod;
		add(ans,now);
	}
	printf("%d\n",(2*ans)%mod); 
	return 0;
} 

F.Session in BSU

n(1<=n<=1e6)门考试,第i门考试有第一次考试时间第ai天和第二次考试时间第bi天(1<=ai<bi<=1e9)

考试可能存在冲突的天,问是否能通过所有门的考试,

若能,输出通过考试的最小天数;若不能输出-1

题解

瞎搞并查集搞过去了

离散化ai和bi到2e6的范围,建图连ai和bi,

考虑每个连通块,每条边都得选一个点作为归属,边是考试,点是第几天

对于孤立点,没有边,直接跳过(事实上由于离散化,不会有这样的点)

对于树,点比边多一个,可以弃掉最大的点,取次大值

对于基环树,点边相等,不可舍弃,取最大值

对于有大于等于两个环的连通块,不能满足所有边都被选的要求

并查集维护每个连通块的最大值、次大值、环的个数,统计每个块对答案贡献

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
struct node
{
	int cyc,mx,se,fa,sz;
	node():cyc(0){
	}
}par[2*maxn];
int find(int x)
{
	return par[x].fa==x?x:par[x].fa=find(par[x].fa);
}
int n,a[maxn],b[maxn];
int c[2*maxn],cnt;
int tmp[4],tot;
int x,y,ans;
bool flag;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	{
		scanf("%d%d",&a[i],&b[i]);
		c[++cnt]=a[i];
		c[++cnt]=b[i];
	}
	sort(c+1,c+cnt+1);
	cnt=unique(c+1,c+cnt+1)-(c+1);
	for(int i=1;i<=n;++i)
	{
		a[i]=lower_bound(c+1,c+cnt+1,a[i])-c;
		b[i]=lower_bound(c+1,c+cnt+1,b[i])-c;
		//printf("a[]:%d b[]:%d\n",a[i],b[i]);
	}
	for(int i=1;i<2*maxn;++i)
	{
		par[i].sz=1;
		par[i].fa=i;
		par[i].mx=i;
		par[i].se=-1;//不存在第二大 
		par[i].cyc=0;
	}
	for(int i=1;i<=n;++i)
	{
		x=find(a[i]),y=find(b[i]);
		if(x==y)
		{
			par[x].cyc++;
			//printf("time[%d]:cyc[%d]:%d\n",i,x,par[x].cyc);
			continue;
		}
		if(par[x].sz<par[y].sz)swap(x,y);
		tot=0;
		tmp[tot++]=par[x].mx;tmp[tot++]=par[x].se;
		tmp[tot++]=par[y].mx;tmp[tot++]=par[y].se;
		sort(tmp,tmp+tot);
		par[x].mx=tmp[3];
		par[x].se=tmp[2];
		par[x].sz+=par[y].sz;
		par[x].cyc+=par[y].cyc;
		par[y].fa=x;
	}
	for(int i=1;i<2*maxn;++i)
	{
		if(par[i].fa==i&&par[i].sz>1)
		{
			//printf("%d:%d %d %d %d\n",i,par[i].sz,par[i].mx,par[i].se,par[i].cyc);
			if(par[i].cyc>1)flag=1;
			else if(par[i].cyc==1)ans=max(ans,c[par[i].mx]);
			else ans=max(ans,c[par[i].se]);
		}
	}
	if(!flag)printf("%d\n",ans);
	else puts("-1");
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值