2021/7/12 考试总结

这篇博客回顾了一次15天集训的模拟赛,涉及多项算法题目,包括完全背包问题的转换、有向图的环检测以及最小生成树在金融危机场景的应用。作者分享了每道题目的解题思路,如将完全背包问题优化为避免三重循环导致的超时,利用拓扑排序和DFS寻找图中环并判断去掉任意边后的连通性,以及在金融危机背景下找到最小道路开销的策略。此外,还提及了一道尚未掌握的简单环问题,涉及到圆方树上的动态规划解决方案。
摘要由CSDN通过智能技术生成

15天集训的第一次模拟赛,考完整个人都不好了

T1 神奇数列

题目描述

给定数列 a 1 a_1 a1 a 2 a_2 a2…… a n a_n an,定义正整数数列 b 1 b_1 b1 b 2 b_2 b2…… b m b_m bm为神奇数列当且仅当它满足:
· 对于 1 ≤ i ≤ m 1\leq i \leq m 1im满足 1 ≤ b i ≤ n 1\leq b_i \leq n 1bin
· ∑ i = 1 m b i ≤ k \sum_{i=1}^{m}{b_i}\leq k i=1mbik
∑ i = 1 m a b i \sum_{i=1}^{m}{a_{bi}} i=1mabi 的最大值。

解题思路

其实就可以转换为完全背包,只不过必须选M个物品
但是这样就会三重循环TLE,只有45分
因此思考当选的物品数量为i,总体积为j时,如果选到物品的体积为k,那么如果 k × i ≥ j k\times i \geq j k×ij那么显然无意义,又因为第k个物品体积为k,因此只需要把k循环到 ⌊ j i ⌋ \lfloor \frac{j}{i} \rfloor ij就可以了,最后从f[m][j]里选一个最大价值就可以了

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL f[1002][8002];
int a[3005],n,m,k;
inline int read()
{
	int X=0; bool flag=1; char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
	while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
	if(flag) return X;
	return ~(X-1);
}
int main()
{
	freopen("sequence.in","r",stdin);
	freopen("sequence.out","w",stdout);
	n=read();
	m=read();
	k=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();	
	}
	if(m==k)
	{
		LL ans=1ll*a[1]*1ll*m;
		cout<<ans;
		return 0;
	}
	memset(f,-120,sizeof(f));
	f[0][0]=0;
		for(int j=1;j<=m;j++)
		{
			for(int l=1;l<=k;l++)
			{	
				for(int i=1;i<=l/j;i++)
				{
					f[j][l]=max(f[j][l],f[j-1][l-i]+a[i]);
				}
		}
	}
	LL ans=0;
	for(int i=1;i<=k;i++)
	ans=max(ans,f[m][i]);
	cout<<ans;
	return 0;
} 

T2 F1 赛道

题目描述

想必大家对 F1 比赛都略有耳闻吧?
某国际赛车场有世界上最长最复杂的 F1 赛道,常年都有一中学子在此练习飙车技巧。该赛车场可以简单地认为由 n个岔道口和m 条单向赛道构成,其中岔道口的编号为1 ~ n,任意一条赛道连接着两个不
同的岔道口,且不存在两条赛道的起始岔道口和到达岔道口是完全一样的。
今年赛事的主办方派代表 steaunk 到该车场进行场地测试,判断赛道是否符合标准。主办方要求该车场中至少有一个环形赛道,但出于安全方面的考虑,还要求如果车场中的任意一个赛道毁坏了,还至少
能有一个完好的环形赛道应急。
steaunk 在检查完福一赛车场后,需要给总部一个答复,如果符合标准则回复 YES ,否则回复 NO 。

解题思路

题意可以转化为判断一个有向图中去掉任意一条边后是否至少还有一个环
考试时思路想错了,结果我的算法还漏了一种情况,因此只有20分
正解也很简单,先找一个环,然后枚举环上的每一条边,判断删掉它之后是否还有一个环。
找环可以用DFS或topsort或tarjan
PS:为了减少时间复杂度,最好可以先用拓扑把不在环上的边删去,然后第一次找环时最好用DFS找出边数最小的一个环,否则就66分

#include<bits/stdc++.h>
using namespace std;
const int N = 507;
const int M = 1e5+7;
struct node
{
	int y,next;
}e[2*M][2];
int link[N][2],t[2],n,m,degin[N],degout[N];
void add(int x,int y,int opt)
{
	e[++t[opt]][opt].y=y;
	e[t[opt]][opt].next=link[x][opt];
	link[x][opt]=t[opt];
}
int top=0,st[N],ed[N];
void Add(int x,int y)
{
	st[++top]=x;
	ed[top]=y;
}
queue<int> qin,qout;
void topsort()
{
	for(int i=1;i<=n;i++)
	{
		if(degin[i]==0)
		{
			qin.push(i);
		}
	}
	while(!qin.empty())
	{
		int x=qin.front();
		qin.pop();
		for(int i=link[x][0];i;i=e[i][0].next)
		{
			int y=e[i][0].y;
			if(degin[y]<=0) continue;
			degin[y]--;
			degout[x]--;
			if(degin[y]==0)
			{
				qin.push(y);
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		if(degout[i]==0&&degin[i]!=0)
		{
			qout.push(i);
		}
	}
	while(!qout.empty())
	{
		int x=qout.front();
		qout.pop();
		for(int i=link[x][1];i;i=e[i][1].next)
		{
			int y=e[i][1].y;
			if(degout[y]<=0||degin[y]<=0) continue;
			degout[y]--;
			if(degout[y]==0)
			{
				qout.push(y);
			}
		}
	}
}
inline int read()
{
	int X=0; bool flag=1; char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
	while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
	if(flag) return X;
	return ~(X-1);
}
bool vis[N];
int flag=0;
bool calc(int x)
{
	if(degin[x]>0&&degout[x]>0)
	return 1;
	return 0;
 } 
int root;
void Find(int x)
{
	vis[x]=1;
	for(int i=link[x][0];i;i=e[i][0].next)
	{
		int y=e[i][0].y;
		if(!calc(y)) continue;
		if(vis[y])
		{
			flag=1;
			Add(x,y);
			root=y;
			return;
		}
		vis[y]=1;
		Find(y);
		vis[y]=0;
		if(x==root&&flag==1)
		{
			Add(x,y);
			flag=2;
			return;
		}
		if(flag==2)
		{
			return;
		}
		if(flag==1)
		{
			Add(x,y);
			return;
		}
	}
}
int X,Y;
void check(int x)
{
	vis[x]=1;
	for(int i=link[x][0];i;i=e[i][0].next)
	{
		int y=e[i][0].y;
		if(!calc(y)) continue;
		if(x==X&&y==Y) continue;
		if(vis[y])
		{
			flag=1;
			return;
		}
		vis[y]=1;
		check(y);
		if(flag==1) return;
		vis[y]=0;
	}
}
int main()
{
	freopen("f1.in","r",stdin);
	freopen("f1.out","w",stdout);
	n=read();
	m=read();
	for(int i=1;i<=m;i++)
	{
		int x,y;
		x=read();
		y=read();
		add(x,y,0);
		add(y,x,1);
		degin[y]++;
		degout[x]++;
	}
	topsort();
	for(int i=1;i<=n;i++)
	{
		if(calc(i))
		{
			Find(i);	
			break;		
		} 	
	}
	bool cuf=0;
	for(int i=1;i<=top;i++)
	{
		X=st[i];
		Y=ed[i];
		flag=0;
		for(int j=1;j<=n;j++)
		{
			if(calc(j))
			{
				for(int k=1;k<=n;k++)
				vis[k]=0;
				check(j);
				if(flag==1) break;
			}
		}
		if(flag!=1) 
		{
			cuf=1;
			break;	
		}
	}
	if(top!=0&&cuf==0) cout<<"YES";
	else cout<<"NO";
	return 0;
}

T3 金融危机

T 国有 n 个城市,1 号城市是该国的首都,城市之间由 m 条道路将其连通,每条道路的开销为 a 或 b。

然而 2008 年,T 国遇上了金融危机,需要将原有的道路削减为 n−1 条,使得所有的城市间仍相互连通,且这 n−1 条道路开销之和最小。

但是事情怎么会这么简单呢?与此同时该国的居民想要知道,在满足上述条件的情况下,从首都到不同城市(城市 1 到 n)的道路开销和最小是多少?

解题思路

题意自己体会吧,反正考试时我理解错了
改题有3个比较重要的性质
首先假设a<b(大不了交换一下)
性质一:由a边连接的点构成的连通块之间的如果有b边,则b边无效(有kruskal的性质得出)
性质二:一条从a连通块出来又回去的b路径肯定不在最小生成树上,因此可以计算曾经去过那些连通块,如果再次去就不去了,跑一遍最短路就有36分了
性质三:性质二算法的连通块个数最多为 n/2因此复杂度过大,性质三就是大小小于等于3的连通块不需要记录为连通块
因为这个连通块至少有2条a边,出来又进去也至少有两条b边,因为a<b,所以2a<2b,直接最短路就不会考虑到这条路径

完整代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL N = 79;
const LL M = 208;
struct edge
{
	LL y,v,next;
}e[2*M];
struct seple
{
	LL x,y,v;
}s[2*M];
struct node
{
	LL S,pos;
	LL v;
};
bool operator < (const node &x,const node &y)
{
	return x.v>y.v;
}
priority_queue<node> q;
LL t=0;
LL link[N]; 
void add(LL x,LL y,LL v)
{
	e[++t].y=y;
	e[t].next=link[x];
	e[t].v=v;
	link[x]=t;
}
LL fa[N],siz[N],bel[N];
LL dis[(1<<18)+7][N];
LL cnt=0;
LL Find(LL x)
{
	if(x==fa[x]) return x;
	return fa[x]=Find(fa[x]);
}
inline LL read()
{
	LL X=0; bool flag=1; char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
	while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
	if(flag) return X;
	return ~(X-1);
}
LL a,b,n,m;
node change(LL x,LL S,LL v)
{
	node tmp;
	tmp.pos=x;
	tmp.S=S;
	tmp.v=v;
	return tmp; 
}
int main()
{	
freopen("crisis.in","r",stdin);
	freopen("crisis.out","w",stdout);
	memset(bel,-1,sizeof(bel));
	memset(dis,66,sizeof(dis));
	n=read();
	m=read();
	a=read();
	b=read();
	if(a>b) swap(a,b);
	for(LL i=1;i<=n;i++)
	{
		fa[i]=i;
		siz[i]=1;
	}
	for(LL i=1;i<=m;i++)
	{
		s[i].x=read();
		s[i].y=read();
		s[i].v=read();
		if(s[i].v==a)
		{
			LL x=Find(s[i].x);
			LL y=Find(s[i].y);
			fa[x]=y;
			siz[y]+=siz[x];
		}
	}
	for(LL x=1;x<=n;x++)
	{
		LL fx=Find(x);
		if(siz[fx]>3)
		{
			if(bel[fx]==-1)
			{
				bel[fx]=cnt++;
				bel[x]=cnt-1;
			}
			else bel[x]=bel[fx];
		}
	}
	for(LL i=1;i<=m;i++)
	{
		LL x=s[i].x,y=s[i].y,v=s[i].v;
		if(v==b&&Find(x)==Find(y)) continue;
		add(x,y,v);
		add(y,x,v);
	}
	LL INF=dis[0][0];
	q.push(change(1,bel[1]==-1?0:1<<(bel[1]),0));
	while(!q.empty())
	{
		node tmp=q.top();
		LL x=tmp.pos,tS=tmp.S,v=tmp.v;
		q.pop();
		if(dis[tS][x]!=INF) continue;
		dis[tS][x]=v;
		for(LL i=link[x];i;i=e[i].next)
		{
			LL S=tS;
			LL y=e[i].y;
			if(bel[y]!=-1)
			{
				if((S&(1<<(bel[y])))&&bel[x]!=bel[y]) continue;
				S|=(1<<(bel[y]));
			}
			if(dis[S][y]!=INF) continue;
			q.push(change(y,S,v+e[i].v));
		}
	}
	for(LL i=0;i<((1<<(cnt)));i++)
	for(LL j=1;j<=n;j++)
	dis[0][j]=min(dis[0][j],dis[i][j]);
	for(LL i=1;i<=n;i++)
	printf("%lld ",dis[0][i]);
	return 0;
}

T4 简单环问题

有一个含有 n 个点 m 条边的无向连通图,其中每条边至多属于一个简单环(包含每个点至多一次的环),你需要为每条边定向,使得每个点的出度不超过 2,求方案数,答案对 998244353 取模。
正解是圆方树上DP,不过我还不会
等写出来再说吧

总结

整个考试基本没什么大的思路,不过只要想写的基本都写出来了,只能下次模拟考再考好了

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值