2024牛客暑期多校训练营6 B|F|I|J补题

B-Cake 2_2024牛客暑期多校训练营6 (nowcoder.com)

欧拉定理 : V − E + F = 2 V - E + F = 2 VE+F=2

v: 顶点,e:边数,f:面数

扩展若为 k 个联通分支 V − E + F = k + 1 V-E + F = k+1 VE+F=k+1

线段相交的数量可以由小的那一侧顶点数推出。就可以画个图,发现里面的形状其实和外面一样,即里面有 n n n 个点,然后我们可以去看一条线被切割了 m i n ( n , n − k ) min(n,n-k) min(n,nk) 份,然后 × n \times n ×n 就好。

最后推出答案为 2 ∗ k = n 2*k = n 2k=n 时候答案为 n n n 因为相交于重心,其余情况就是 n × m i n ( k , n − k ) + 1 n\times min(k,n-k) +1 n×min(k,nk)+1

F-Challenge NPC 2_2024牛客暑期多校训练营6 (nowcoder.com)

由于是森林,即无环,然后很显然,当是一个菊花图,即 n − 1 n-1 n1 个点与 另一个点相连的时候,是无法使得他的补图是连通的。因为补图中没有可以与 那个中心点相连的边。

然后我们考虑如果存在我们所要的连通的,如何去构造?

我们会发现其实同一层之间是可以在序列里面相邻,就可以直接按照顺序把深度丢入。而且在上面的约束下,仅有一棵树的时候必然链的长度最小为 4 4 4 ,我们可以按照深度 2 , 4...1 , 3... n 2,4 ...1,3...n 2,4...1,3...n 这样去构造,最后就可以得到答案。但是我们会发现,这其实是个森林,每个树都有链,而且可能 某个数的链长 < 4 <4 <4 是个菊花图,那此时其实我们可以直接连接直径,这样连接到最后的直径肯定是 ≥ 4 \ge 4 4

但是大家会发现还会 w a wa wa 因为 n = 3 , m = 1 n=3,m=1 n=3,m=1 的时候我们没有考虑

例如有样例:

3 1

1 2

如果此时连边,会使得 2 与 3 相连,最后我们输出的时候可能是 2 1 3 ,显然是错误解,所以我们特判一下就好了。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e5+10;

vector<int> e[N];
vector<int> dep[N];
int vis[N];
int mx ;
int root;

int deps[N];
int ls ;
void dfs(int u,int fa)
{	deps[u] = deps[fa] +1;
	vis[u] = true;
	for(auto v:e[u])
	{	if(v==fa)
			continue ;
			
		dfs(v,u);
	}
	if(deps[u] > mx)
	{
		mx = deps[u];
		root = u;
	}
	deps[u] = 0;
}

void dfs2(int u,int fa,int now)
{	dep[now].push_back(u);
	for(auto v:e[u])
	{
		if(v==fa)
			continue ;
		dfs2(v,u,now+1);
	}
}
void cal(int i)
{	
	mx = 0;
	root = 0;
	dfs(i,i);
	int now = root;
	mx = 0;
	dfs(root,root);
	
	if(ls == -1)
		ls = root;
	else 
	{
		e[ls].push_back(root);
		e[root].push_back(ls);
		ls = now;
	}
	
	
	
}
void sol(int i)
{	
	mx = 0;
	root = 0;
	dfs(i,i);

	dfs2(root,root,1);
}
void solve()
{	ls = -1;

	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		e[i].clear();
		dep[i].clear();
		vis[i] = 0;
		
		
	}
	if(n==3)
	{
		if(m==1)
		{
			int u,v;
			cin>>u>>v;
			if(u<v)
				swap(u,v);
			// u>v
			int a = (u==3?(v==2?1:2):3);
			cout<<u<<' '<<a<<' '<<v<<endl;
			return ;
			
		}
	}
	for(int i=1;i<=m;i++)
	{
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}

	
	for(int i=1;i<=n;i++)
	{
		if(e[i].size() == (n-1) )
		{	cout<<"-1"<<endl;
			return ;
		}
	}

	for(int i=1;i<=n;i++)
	if(!vis[i])
		cal(i);
	//cout<<endl;
// 	
	// for(int i=1;i<=n;i++)
	// {	
		// for(auto v:e[i])
			// cout<<v<<' ';
		// cout<<endl;
// 		
	// }

	
	sol(1);


	for(int i=2;i<=n;i+=2)
	{	
		for(auto c:dep[i])
		{
			cout<<c<<" ";
		}
	}
	for(int i=1;i<=n;i+=2)
		for(auto c:dep[i])
		{
			cout<<c<<' ';
		}
	cout<<endl;
	
	
	
	
	
}  
signed main (){
	std::ios::sync_with_stdio(false);  
	cin.tie(NULL); 
	cout.tie(NULL);
	int t;
	cin>>t;
	while(t--)
		solve();
} 

I-Intersecting Intervals_2024牛客暑期多校训练营6 (nowcoder.com)

该题其实看的时候,会发现无后效性,而且对于某一行,他仅仅和上一行有关系,就很容易联想到 动态规划,那么如何设立状态呢?
我们会发现每一个 ( i , j ) (i,j) (i,j) 位置其实仅和他上一个 ( i − 1 , j ) (i-1,j) (i1,j) 相交的区间有关,显然但是对于当前点,我们比可能去枚举上一层的任意区间,这样显然一个转移都 O ( m 2 ) O(m^2) O(m2) 会 TLE。那么如何转移呢。其实我们可以强制一下,我们选当前点其实不关注于上面是哪一段区间,其实只关注于上面一个区间是否包含我正上方的那个点。那我们可以定义 d p i j dp_{ij} dpij 表示为第 i i i 行第 j j j 列我们强制选时候可以达到的 ∑ r o w = 1 i ∑ c o l = 1 j A r o w , c o l \sum_{row=1}^i \sum _{col = 1} ^j A_{row,col} row=1icol=1jArow,col 的最大值 .

然后如何转移呢?

我们肯定要选看包含当前点的区间最大值为多少,这个只要暴力维护前缀和 p r e j pre_j prej max 和后缀和 p o s j pos_j posj max 然后转移
d p [ i ] [ j ] = m a x ( ∑ k = 1 j d p [ i − 1 ] [ k ] + s u m [ k . . j ] + p r e k + p o s j , ∑ k = j + 1 n d p [ i − 1 ] [ k ] + s u m [ j . . . k ] + p r e j + p o s k ) dp[i][j] =max( \sum_{k=1}^j dp[i-1][k] + sum[k..j] +pre_k + pos_j,\\ \sum_{k=j+1}^n dp[i-1][k] + sum[j...k] +pre_j + pos_k) dp[i][j]=max(k=1jdp[i1][k]+sum[k..j]+prek+posj,k=j+1ndp[i1][k]+sum[j...k]+prej+posk)
求和符号我们可以维护前后缀最大值来解决。

#include<bits/stdc++.h>
using namespace std;
#define int long long
void solve()
{
	int n,m;
	cin>>n>>m;
	vector<vector<int> > a(n+1,vector<int> (m+1)),dp(n+1,vector<int> (m+1));
	// dp[i][j] 表示在 Row i 处选定 j 的最大区间和。
	// 那么我们转移就是 dp[i][j] = \sum_{k=1}^j dp[i-1][k] + sum[k..j] +pre_k + pos_j
	// 以及从后往前的   dp[i][j] = \sum_{k=j+1}^n dp[i-1][k] + sum[j...k] +pre_j + pos_k
	// 我们可以维护 dp[i-1][k] + a[k] 这样的max
	
	//对于后面两个式子我们可以用顺着加 和逆着加 即可
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			cin>>a[i][j];
	for(int i=1;i<=n;i++)
	{	vector<int> pre(m+2);
		vector<int> pos(m+2);
		vector<int> dpre(m+2);
		vector<int> dpos(m+2);
		for(int j=1;j<=m;j++)
		{
			pre[j] += pre[j-1] + a[i][j];
			pre[j] = max(pre[j],a[i][j]);
		}
		for(int j=m;j>=1;j--)
		{
			pos[j] += pos[j+1] + a[i][j];
			pos[j] = max(pos[j],a[i][j]); 
		}
		dpre[0] = -1e18;
		dpos[m+1] = -1e18;
		for(int j=1;j<=m;j++)
		{	
			dpre[j] = max(dpre[j-1]+a[i][j],dp[i-1][j] + pre[j]);
			dp[i][j] = dpre[j] +  pos[j] - a[i][j];	
		}
		for(int j=m;j>=1;j--)
		{
			dpos[j] = max(dpos[j+1] + a[i][j],dp[i-1][j] + pos[j]);
			dp[i][j] = max(dpos[j] + pre[j] - a[i][j],dp[i][j]);
		}
		// cout<<"case "<<i<<':'<<endl;
		// for(int j=1;j<=m;j++)
			// cout<<pre[j]<<' ';
		// cout<<endl;
		// for(int j=1;j<=m;j++)
			// cout<<pos[j]<<' ';
		// cout<<endl;
		
	}
	// for(int i=1;i<=n;i++)
	// {	for(int j=1;j<=m;j++)
			// cout<<dp[i][j]<<' ';
		// cout<<endl;
// 		
	// }		
	int ans = -LLONG_MAX;
	for(int i=1;i<=m;i++)
	{
		ans = max(dp[n][i],ans);
	}
	cout<<ans<<endl;
	
	
}  
signed main (){
	std::ios::sync_with_stdio(false);  
	cin.tie(NULL); 
	cout.tie(NULL);
	int t;
	cin>>t;
	while(t--)
		solve();
} 

J-Stone Merging_2024牛客暑期多校训练营6 (nowcoder.com)

对于不成立情况,其实很显然就是 2 → k 2 \rightarrow k 2k 个数字都在石头上出现,否则就会有答案。(但是如果仅有 2 2 2 2 2 2 是可以的,需要特判)

剩下的就是合法方案,但是如何去构造?

我们假设我们找到其中一个不存在的数字为 x x x ,那么我们其实是想把我们的堆的个数降到 x x x 这样一次就可以实现。那么我们看一下我们需要合并多少石头呢?

r e s = ( n − 1 ) % ( x − 1 ) + 1 res = (n-1)\%(x-1) +1 res=(n1)%(x1)+1 ,即除了最后的一个我们要保留的,剩下 n − 1 n-1 n1 个石头看每次用 x x x 合并其实等价于每次减少 x − 1 x-1 x1 ,因为合成的石头也算一个。

然后我们看这样 n − 1 n-1 n1 需要减去多少才能被整除,即一次消去。

此时我们可能想从后面往前面暴力,然后找到某个可以使用的机器,然后使用。但是其实没有这么麻烦。

我们可以想一下我们在学 欧几里得的时候,是不是每次取余其实都是减去原来的一半以上,那么其实对于当前的 r e s res res 由抽屉原理可以知道要么有 r e s res res 个相同的,要么有 r e s res res 个不同的。那么我们就可以使用 r e s res res 机器来处理即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long 
void solve()
{
	int n,k;
	cin>>n>>k;
	vector<int> a(n+1);
	vector<int> ton(n+1);
	for(int i=1;i<=n;i++)
	{	
		cin>>a[i];
		ton[a[i]] ++;
	}	

	if(n==2 && ton[2] == 2)
	{	
		cout<<1<<endl;
		cout<<n<<' ';
		for(int i=1;i<=n;i++)
		{
			cout<<i<<' ';
		}
		cout<<endl;
		return ;
	}
	int x = 0;
	
	for(int i=2;i<=k;i++)
	{
		if(!ton[i])
		{
			x = i;
			break;
		}
	}
	if(!x)
	{
		cout<<"-1"<<endl;
		return ;
	}
	int res = (n-1) %(x-1) +1;
	vector<int> sto ;
	vector<int> id(4*n+10,0);
	for(int i=1;i<=n;i++)
		id[i] = 1;
		
	int t = (n-1)/(x-1);
	
	//cout<<n-1 + x-2<<' '<<x<<endl;
	
	int len = n;
	
	
	if(res == 1)
	{
		;
	}
	else if(ton[res] >= res )
	{	
		len+=2;
		id[len-1] = 1;
		
		for(int i=1;i<=n;i++)
		{	
			if(a[i]==res && sto.size() < res)
			{
				sto.push_back(i);
				id[i] = 0;
			}
		}
	}
	else 
	{	
	
		len+=2;
		id[len] = 1;
		
		for(int i=1;i<=n;i++)
		{
			if(a[i]!=res && sto.size() < res)
			{
				sto.push_back(i);
				id[i] = 0;
			}
		}
	}
	//cout<<"case"<<res<<endl;
	
	cout<<t + (sto.size()==0?0:1)<<endl;
	
	if(sto.size()){
	cout<<sto.size()<<' ';
	for(auto c:sto)
		cout<<c<<' ';
	cout<<endl;
	}
	int l = 1;
	
	for(int i=1;i<t;i++)
	{
		len +=2;
		id[len] = 1;
	}
	// for(int i=1;i<=len;i++)
		// cout<<id[i]<<' ';
	// cout<<endl;
// 	
	//cout<<len<<endl;
	
	while(l<=len-2 )
	{	int num = x;
		cout<<x<<' ';
		
		while(num)
		{	//cout<<l<<' '<<a[l]<<endl;
			
			if(!id[l])
				;
			else 
			{	cout<<l<<' ';
				num--;
			}
			l++;
		}
		cout<<endl;
		
		
	}

} 
signed main (){
	std::ios::sync_with_stdio(false);  
	cin.tie(NULL); 
	cout.tie(NULL);
	int t;
	cin>>t;
	while(t--)
		solve();
		
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值