“小马智行杯“第十九届广东省大学生程序设计竞赛 暨中国大学生程序设计大赛广东省赛 B D E F H J K L

补题链接:https://pintia.cn/market/item/1534086632285245440

B FFT

诈骗题:单看求解的东西很复杂,其实整个集合就是所有的排列,答案即为n!

const int N = 1e7+10;
int ans[N];
int fac[N],inv[N];
void laoya()
{
	int n;
	cin>>n;
	fac[0]=inv[0]=1;
	for(int i=1;i<=n;i++)
	{
		fac[i]=fac[i-1]*i%mod;
	}
	cout<<fac[n]<<endl;
}

D 剪纸

不会思路,不过很容易打表,打完发现除了2是1 1外其他数是小于等于n的前两个
下面是题解
在这里插入图片描述

void laoya() 
{
	int n;
	cin>>n;
	if(n==2)
	{
		cout<<1<<" "<<1<<endl;
		return ;
	}
	int f1=1,f2=1;
	while(1)
	{
		int f3=f1+f2;
		if(f3>n)break;
		f1=f2;
		f2=f3;
	}
	cout<<f1<<" "<<f2<<endl;
}

E 黑白大陆

思路:把0和1的联通块分别缩点,缩完点后在0和1的的联通块之间加边,建完图后,我们可以发现,如果我们当前在u,有u->v1,u->v2的边,如果我们对u连通块使用一次操作,那么就会走到v1,v2点,即三个点连通了,最后的颜色为v1,v2的颜色,于是我们可以发现一个性质,从某个点出发后每次对自己染色,就相当于走一条边,那么我们bfs一遍求最短路,最短路里面最长的那个点就是我们从这个点出发把整个联通块染成这个颜色的步数,要注意的是如果终点颜色是1的话,我们要加上一步把整个联通块变成0
代码

const int N = 51;
int n,m;
int a[N][N];
int col[N*N];//联通块的颜色
vector<int> v[N*N];
int cnt;
int id[N*N],d[N*N];
bool vis[N*N];
int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};
void dfs(int i,int j,int belong)
{
	int pos=(i-1)*m+j;
	if(vis[pos])return ;
	vis[pos]=1;
	id[pos]=belong;
	for(int k=0;k<4;k++)
	{
		int x=i+dx[k],y=j+dy[k];
		if(x<=0 || x>n || y<=0 || y>m)continue;
		if(a[i][j]==a[x][y])dfs(x,y,belong);
	}
}
int bfs(int u) //从u号点出发
{
	memset(d,0x3f,sizeof d);
	memset(vis,0,sizeof vis);
	queue<int> q;
	d[u]=0;
	vis[u]=1;
	q.push(u);
	while(q.size())
	{
		int u=q.front();
		q.pop();
		for(auto to:v[u])
		{
			if(!vis[to])
			{
				d[to]=d[u]+1;
				vis[to]=1;
				q.push(to);
			}
		}
	}
	int ma=0;
	for(int i=1;i<=cnt;i++)
	{
		if(col[i]==1)d[i]++; //如果终点是1那么要多用一次
		ma=max(d[i],ma);
	}
	return ma;
}
void laoya()
{
	cin>>n>>m; 
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			cin>>a[i][j];
			int pos=(i-1)*m+j;
		}
	}	
	//缩点
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			int pos=(i-1)*m+j;
			if(!vis[pos])
			{
				cnt++;
				dfs(i,j,cnt);
				col[cnt]=a[i][j];
			}
		}
	}
	//加边
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			int b=(i-1)*m+j;
			for(int k=0;k<4;k++)
			{
				int x=i+dx[k],y=j+dy[k];
				if(x<=0 || x>n || y<=0 || y>m)continue;
				int c=(x-1)*m+y;
				if(a[i][j]!=a[x][y])
				{
					v[id[b]].push_back(id[c]);
				}
			}
		}
	}

	for(int i=1;i<=cnt;i++) //删掉重边
	{
		sort(v[i].begin(),v[i].end());
		v[i].erase(unique(v[i].begin(),v[i].end()),v[i].end());
	}

	//对每一个点跑最短路
	int ans=1e9;
	for(int i=1;i<=cnt;i++)ans=min(ans,bfs(i));
	cout<<ans<<endl;
}

F 望舒客栈的每日委托

思路:模拟,用5个set分别存每一张桌子的编号即可,枚举时间,用自定义的deque或set实现弹出队头和插入队尾操作即可
代码

const int N = 1e7+10;
int n;
int sum[N]; //第i张桌子有sum[i]个人
int belong[N]; //id这个人属于哪一张桌子
set<int> num[6]; //还剩i个人的桌子的编号
struct node
{
	int id,x,a,d,t;
	bool operator<(const node &t)
	{
		return a<t.a;
	};
}q[N];
class Mycompare{
public:
    bool operator()(int a, int b){//重载运算符
        return q[a].d < q[b].d;//降序排列
    }
};
void laoya()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		int x,a,d,t;
		cin>>x>>a>>d>>t;
		q[i]={i,x,a,d,t};
	}
	sort(q+1,q+1+n); //按照到来时间排序
	int tot=0; //总共tot张桌子
	int idx=1;
	set<int,Mycompare> st;  //在队列中的人,按照离开时间从小到大排序
	for(int time=1;time<=2*n;time++)
	{
		//出队
		while(st.size() && q[*st.begin()].d<time)
		{
			//人的编号
			int pos=*st.begin();
			//belong[id]是桌子编号
			num[sum[belong[pos]]].erase(belong[pos]);
			sum[belong[pos]]+=q[pos].x;
			num[sum[belong[pos]]].insert(belong[pos]);
			st.erase(pos);
		}


		while(idx<=n && q[idx].a==time) //入队
		{
			st.insert(idx);
			if(!q[idx].t) //社恐
			{
				int x=q[idx].x;
				if(num[4].size()) //剩下4个人的桌子
				{
					int t=*(num[4].begin());
					num[4].erase(t);
					belong[idx]=t;
					sum[t]-=x;
					num[sum[t]].insert(t);
				}
				else //再开一张新桌子
				{
					tot++;
					belong[idx]=tot;
					sum[tot]=4-x;
					num[sum[tot]].insert(tot);
				}
			}
			else  //社牛
			{
				int pos=1e9;
				int x=q[idx].x;
				for(int i=x;i<=4;i++) //找到第一张可以坐的桌子
				{
					if(num[i].size())pos=min(pos,*(num[i].begin()));
				}
				if(pos==1e9) //再开一张新桌子
				{
					tot++;
					belong[idx]=tot;
					sum[tot]=4-x;
					num[sum[tot]].insert(tot);
				}
				else 
				{
					num[sum[pos]].erase(pos);
					belong[idx]=pos;
					sum[pos]-=x;
					num[sum[pos]].insert(pos);
				}
			}
			idx++;
		}
	}
	cout<<tot<<endl;
}

H 梅花易数

思路:按照题意模拟即可

vector<string> ans;
void get(int x)
{
	string s[3]={"","",""};
	if(x==1)
	{
		s[0]+="---";
		s[1]+="---";
		s[2]+="---";
	}
	else if(x==2)
	{	
		s[0]+="- -";
		s[1]+="---";
		s[2]+="---";
	}
	else if(x==3)
	{
		s[0]+="---";
		s[1]+="- -";
		s[2]+="---";
	}
	else if(x==4)
	{
		s[0]+="- -";
		s[1]+="- -";
		s[2]+="---";
	}
	else if(x==5)
	{
		s[0]+="---";
		s[1]+="---";
		s[2]+="- -";
	}
	else if(x==6)
	{
		s[0]+="- -";
		s[1]+="---";
		s[2]+="- -";
	}
	else if(x==7)
	{
		s[0]+="---";
		s[1]+="- -";
		s[2]+="- -";
	}
	else 
	{
		s[0]+="- -";
		s[1]+="- -";
		s[2]+="- -";
	}
	for(int i=0;i<3;i++)
	{
		ans.push_back(s[i]);
	}
}
void laoya()
{
	map<string,int> mp;
	mp["Zi"]=1;
	mp["Chou"]=2;
	mp["Yin"]=3;
	mp["Mao"]=4;
	mp["Chen"]=5;
	mp["Si"]=6;
	mp["Wu"]=7;
	mp["Wei"]=8;
	mp["Shen"]=9;
	mp["You"]=10;
	mp["Xu"]=11;
	mp["Hai"]=12;
	string yy,hh;
	int y,m,d,h;
	cin>>yy>>m>>d>>hh;
	y=mp[yy];
	h=mp[hh];
	get((y+m+d)%8);
	get((y+m+d+h)%8);
	for(int i=0;i<6;i++)cout<<ans[i]<<endl;
	cout<<endl;
	int idx=(y+m+d+h)%6;
	if(idx==0)idx=0;
	else idx=6-idx;
	if(ans[idx][1]==' ')ans[idx][1]='-';
	else ans[idx][1]=' ';
	for(int i=0;i<6;i++)cout<<ans[i]<<endl;
}

J 新英雄

思路:有线性写法,不过我简单二分了一下,注意坑点是输入要排序(找了好久没发现错哪了)
假设我们总共走了k步,首先我们得每一步走完后的总法力值时刻大于等于0,其次我们可以选择(k+1)/2个选择踏,k/2个选择踩。
具体做法:首先如果第一个小兵是敌人,那么我肯定是走不到终点的直接return。第一个小兵是朋友,那么我们只要二分向这个小兵借了多少步,然后线性扫一遍如果前缀时刻大于等于0那么就true
代码

const int N = 2e5+10;
int op[N],l[N],r[N];
int n,m;
int ans=1e18;
struct node
{
	int x,y,z;
}q[N];
bool cmp(node a,node b)
{
	return a.y<b.y;
}
bool check(int jie)
{
	int tmpl=l[1];
	int sum=jie;
	if(jie%2==1)
	{
		l[1]++;
	}
	bool flag=1;
	for(int i=l[1]<=r[1]?1:2;i<=m;i++)
	{
		int len=r[i]-l[i]+1;
		if(op[i]==1)sum+=len;
		else sum-=len;
		if(sum<0)
		{
			flag=0;
			break;
		}
	}
	l[1]=tmpl;
	if(flag)
	{
		int tot=jie;
		if(jie%2==1)tot+=n-1;
		else tot+=n;
		ans=min(ans,(tot+1)/2*3+tot/2);
		return 1;
	}
	return 0;
}
void laoya()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>q[i].x>>q[i].y>>q[i].z;	
	}
	sort(q+1,q+1+m,cmp);
	for(int i=1;i<=m;i++)
	{
		op[i]=q[i].x;
		l[i]=q[i].y;
		r[i]=q[i].z;
	}
	if(op[1]==2) //根本走不了
	{
		cout<<"0/21/0"<<endl;
		return ;
	}
	int l=0,r=n+2;
	while(l<r)
	{
		int mid=l+r>>1;
		if(check(mid))r=mid;
		else l=mid+1;
	}
	cout<<ans<<endl;
}

K 斐波那契

在这里插入图片描述
这题解已经很明确了,这个将可达性矩阵变成斐波那契矩阵非常妙。由于矩阵乘法的性质,我们把本来权值为i的边变成斐波那契矩阵的第i项个项,可以画两个矩阵就能发现这样算出来的答案恰好就是矩阵乘的答案,还有一点是这个题卡常数,取模运算改成add函数后非常勉强的过了
在这里插入图片描述

const int mod = 998244353;
int n,m,k;
vv a(2,vi(2));
inline void add(ll &x,ll y)
{
	x=x+y;
	if(x>=mod)x-=mod;
}
inline vv mul(vv a,vv b) //斐波那契矩阵乘
{
	vv c(2,vi(2));
	for(int i=0;i<2;i++)
	{
		for(int j=0;j<2;j++)
		{
			for(int k=0;k<2;k++)
			{
				int tmp=a[i][k]*b[k][j]%mod;
				add(c[i][j],tmp);		
			}
		}
	}
	return c;
}

inline vv f(vv a,int b) //斐波那契第x项矩阵
{
	vv ans(2,vi(2));
	ans[0][0]=ans[1][1]=1;
	while(b)
	{
		if(b&1)ans=mul(ans,a);
		a=mul(a,a);
		b>>=1;
	}
	return ans;
}

//可达性矩阵乘法
inline vv operator*(vv a,vv b)
{
	vv c(2*n+1,vi(2*n+1));
	for(int i=1;i<=2*n;i++)
	{
		for(int j=1;j<=2*n;j++)
		{
			for(int k=1;k<=2*n;k++)
			{
				int tmp=a[i][k]*b[k][j]%mod;
				add(c[i][j],tmp);		
			}
		}
	}
	return c;
}

inline vv qpow(vv a,int b)
{
	vv ans(2*n+1,vi(2*n+1));
	for(int i=1;i<=2*n;i++)ans[i][i]=1;
	while(b)
	{
		if(b&1)ans=ans*a;
		a=a*a;
		b>>=1;
	}
	return ans;
}

void laoya()
{
	a={
		{0,1},
		{1,1}
	};
	cin>>n>>m>>k;
	vv A(2*n+1,vi(2*n+1)); //可达性矩阵
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		vv g=f(a,w); //边权转化为矩阵
		int x0,y0,x1,y1;
		x1=u*2;
		y1=v*2;
		x0=x1-1;
		y0=y1-1;
		//叠加到可达性矩阵上面
		add(A[x0][y0],g[0][0]);
		add(A[x0][y1],g[0][1]);
		add(A[x1][y0],g[1][0]);
		add(A[x1][y1],g[1][1]);
	}

	//求A的k次方即可
	A=qpow(A,k);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			cout<<A[i*2-1][j*2];
			if(j!=n)cout<<" ";
		}
		cout<<'\n';
	}
}

L 起航者

思路:我是换根dp做的,看了题解好像有点类似。
数组:down[i]表示以1为根 i 往下走的答案,
dp[i][0]表示从i出发走最大的那个点的答案
dp[i][1]表示从i出发走第二大的那个点的答案
第一步:第一步先求出每个点连着的最大的两个点ma[i][0],ma[i][1],再跑一遍以1为根的dfs1(即从1号点出发),求出每个节点只向下走的答案down[i],此时down[1]其实也就是从1号点出发的答案了。
第二步:有了down数组,我们就可以求出dp[1][0],dp[1][1];

for(int i=1;i<=n;i++)
{
	if(w[i]==ma[1][0])dp[1][0]=w[1]+down[i];
	else if(w[i]==ma[1][1])dp[1][1]=w[1]+down[i];
}

第三步:我们继续dfs一遍,进行换根dp,假设当前dfs到u这个节点,他的父亲fa的dp[fa][0],dp[fa][1]已经求出来了(因为1号已经求出来了,往下能不断求出来)那么我们可以进行讨论
分类讨论如下
一.往父亲走
1.如果父亲是我最大的点
(1).我是父亲最大的点,那么父亲只能走第二大的点,
dp[u][0]=w[u]+dp[fa][1];
(2).我不是父亲最大的点,那么父亲就走最大的点
dp[u][0]=w[u]+dp[fa][0];
2.父亲是我第二大的点
(1).我是父亲最大的点,那么父亲只能走第二大的点;
dp[u][1]=w[u]+dp[fa][1];
(2).我不是父亲最大的点,那么父亲就走最大的点
dp[u][1]=w[u]+dp[fa[[0];
二.往儿子走(儿子为to)
1.如果儿子是最大的点
dp[u][0]=w[u]+down[to];
2.如果儿子是第二大的点;
dp[u][1]=w[u]+down[to];
注意递归时是先把当前的dp[u][0]和dp[u][1]算出来后再往下dfs

const int N = 1e6+10;
int n;
vector<int> v[N];
int w[N];
int ma[N][2];
int dp[N][2];
int down[N];
void getma(int x,int y)
{
	if(w[y]>ma[x][0])
	{
		ma[x][1]=ma[x][0];
		ma[x][0]=w[y];
	}
	else if(w[y]>ma[x][1])
	{
		ma[x][1]=w[y];
	}
}

void dfs1(int u,int fa)
{
	for(auto to:v[u])
	{
		if(to==fa)continue;
		dfs1(to,u);
	}
	int ma=0,idx=0;
	for(auto to:v[u])
	{
		if(to==fa)continue;
		if(w[to]>ma)
		{
			ma=w[to];
			idx=to;
		}
	}
	down[u]=w[u]+down[idx];
}

void dfs(int u,int fa)
{
	for(auto to:v[u])
	{
		
		if(to==fa && u!=1)
		{
			if(w[fa]==ma[u][0]) //父亲是我最大的点
			{
				if(w[u]==ma[fa][0]) //我是父亲最大的点
				{
					dp[u][0]=w[u]+dp[fa][1];
				}
				else //我不是父亲最大的点 
				{
					// if(u==5)cout<<u<<" "<<fa<<" "<<dp[fa][0]<<endl;
					dp[u][0]=w[u]+dp[fa][0];
				}
			}
			if(w[fa]==ma[u][1]) //父亲是我第二大的点
			{
				if(w[u]==ma[fa][0]) //我是父亲最大的点
				{
					dp[u][1]=w[u]+dp[fa][1];
				}
				else //我不是父亲最大的点 
				{
					dp[u][1]=w[u]+dp[fa][0];
				}
			}
			continue;
		}

		else
		{
			if(w[to]==ma[u][0])
			{
				dp[u][0]=w[u]+down[to];
			}
			else if(w[to]==ma[u][1])
			{
				dp[u][1]=w[u]+down[to];
			}
		}
	}

	for(auto to:v[u])
	{
		if(to==fa)continue;
		dfs(to,u);
	}
}

void laoya()
{
	int n;
	cin>>n;
	for(int i=2;i<=n;i++)
	{
		int fa;
		cin>>fa;
		v[i].push_back(fa);
		v[fa].push_back(i);
	}
	for(int i=1;i<=n;i++)
	{
		cin>>w[i];
		ma[i][0]=ma[i][1]=-1;
		dp[i][0]=dp[i][1]=w[i];
	}
	for(int i=1;i<=n;i++)
	{
		for(auto x:v[i])
		{
			getma(i,x);
		}
	}
	dfs1(1,1); //求down数组
    //求从1号点出发的最大的两条路
	for(int i=1;i<=n;i++)
	{
		if(w[i]==ma[1][0])dp[1][0]=w[1]+down[i];
		else if(w[i]==ma[1][1])dp[1][1]=w[1]+down[i];
	}
	dfs(1,1);
	int ans=0;
	for(int i=1;i<=n;i++)
	{
		ans=max(ans,dp[i][0]);
	}
	for(int i=1;i<=n;i++)
	{
		if(dp[i][0]==ans)
		{
			cout<<i<<endl;
			cout<<ans<<endl;
			return ;
		}
	}

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是学生管理系统的数据库系统概念设计: 1. 学生表(Student):包含学生的基本信息,包括学生ID、姓名、性别、出生日期、联系方式等字段。 2. 课程表(Course):包含课程的基本信息,包括课程ID、课程名称、学分、授课教师等字段。 3. 成绩表(Score):包含学生的成绩信息,包括学生ID、课程ID、成绩等字段。 4. 教师表(Teacher):包含教师的基本信息,包括教师ID、姓名、性别、出生日期、联系方式等字段。 5. 用户表(User):包含用户的基本信息,包括用户ID、用户名、密码、用户类型等字段。 6. 学生选课表(StudentCourse):包含学生选课的信息,包括学生ID、课程ID等字段。 7. 教师授课表(TeacherCourse):包含教师授课的信息,包括教师ID、课程ID等字段。 8. 班级表(Class):包含班级的基本信息,包括班级ID、班级名称、班级人数等字段。 9. 学生班级表(StudentClass):包含学生所属班级的信息,包括学生ID、班级ID等字段。 10. 教师班级表(TeacherClass):包含教师所教班级的信息,包括教师ID、班级ID等字段。 以上这些表是学生管理系统中最基本的一些数据表,通过它们可以实现系统中的各种功能,如学生信息管理、课程信息管理、成绩管理、教师信息管理、用户管理等。这些表之间通过外键进行关联,实现数据的一致性和完整性。在实际应用中,还可以根据需求进行扩展和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值