2022杭电多校第八场题解

2022杭电多校第八场

在这里插入图片描述

Theramore(思维)

题意
给定一个01字符串,每次可以将一个奇数长度的区间翻转,求操作后字典序最小的字符串。

分析
翻转奇数长度的区间,元素位置的奇偶性不变,统计奇数位置和偶数位置1的个数,每次只使用长度为3的区间翻转,贪心的将1放到尽可能靠后的位置。时间复杂度 O ( n ) O(n) O(n)

AC代码

int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		string s;
		cin>>s;
		int a=0,b=0;
		for(int i=0;i<s.size();i++)
		{
			if(s[i]=='1')
			{
				if(i&1) a++;
				else b++;
			}
		}
		string ans(s.size(),'0');
		for(int i=(int)ans.size()-1;i>=0;i--)
		{
			if(i&1)
			{
				if(a>0)
				{
					ans[i]='1';
					a--;
				}
			}
			else
			{
				if(b>0)
				{
					ans[i]='1';
					b--;
				}
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}

Quel’Thalas(思维)

题意
给定一个二维平面,每个点的横纵坐标范围是 [ 0 , n ] [0,n] [0,n]内的整数,输出最少数量的直线,满足直线不经过 ( 0 , 0 ) (0,0) (0,0)且直线经过除 ( 0 , 0 ) (0,0) (0,0)以外所有的点。时间复杂度 O ( 1 ) O(1) O(1)

分析
同一条直线最多穿越两个边界上的点,总共有 4 n 4n 4n 个边界上的点,所以至少要 2 n 2n 2n 条直线。

AC代码

int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		int n;
		cin>>n;
		cout<<2*n<<endl;
	}
	return 0;
}

Ironforge(思维 数论)

题意
一条链,每个点上有一个数 a i a_i ai,每条边上有一个质数 b i b_i bi。一开始在某个点上,有一个空背包,走到一个点上可以把它的质因子放进背包,一条边如果背包里有那个质数就可以走。多组询问求从 x x x 出发能否走到 y y y (即求每个点能走到的最大范围)。

分析
先预处理每个质数在点上出现的位置,通过vector二分可以找到每条边左右两边最近的含有边上质数的位置。接下来需要预处理每个位置左右可以移动的最大范围,暴力扩展的时间复杂度是 O ( n 2 ) O(n^2) O(n2),但实际上如果扩展到之前处理的结点,可以继承它的扩展范围,对于每个点扩展的复杂度均摊是 O ( 1 ) O(1) O(1)的,预处理的复杂度是 O ( n ) O(n) O(n),复杂度的瓶颈在于处理质数。时间复杂度 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))

AC代码

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
const int N=2e5+10;
int a[N],b[N],vis[N],prime[N];
int L[N],R[N],l[N],r[N];
vector<int> v[N]; //存放每个质数出现的位置 
int n,m,k,cnt;

void get_primes(int n)
{
	for(int i=2;i<=n;i++) {
		if(vis[i]==0) { vis[i]=i; prime[++k]=i; }
		for(int j=1;j<=k;j++) {
			if(prime[j]>vis[i]||prime[j]>n/i) break;
			vis[i*prime[j]]=prime[j];
		}
	}
}

void solve(int &a,int &b)
{
	int x=a,y=b;
	while(true)
	{
		if(x>1)
		{
			if(R[x-1]<=y)
			{
				x--;
				y=max(y,r[x]);
				x=min(x,l[x]);
			}
		}
		if(y<n)
		{
			if(L[y]>=x) y++;
		}
		if(x==a&&y==b) break;
		a=x,b=y;
	}
}

bool check(int a,int b)
{
	if(a<b) return L[a]==a;
	else return R[a-1]==a;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	int T;
	cin>>T;
	get_primes(200000);
	while(T--)
	{
		cin>>n>>m;
		for(int i=1;i<=n;i++) cin>>a[i];
		for(int i=1;i<n;i++)  cin>>b[i];
		for(int i=1;i<=200000;i++) v[i].clear();
		for(int i=1;i<=n;i++)
		{
			int x=a[i];
			while(x>1)
			{
				v[vis[x]].push_back(i);
				int z=vis[x];
				while(x%z==0) x/=z;
			}
		}
		for(int i=1;i<n;i++)
		{
			int x=b[i];
			if(!v[x].size())
			{
				L[i]=0,R[i]=n+1;
				continue;
			}
			int t=upper_bound(v[x].begin(),v[x].end(),i)-v[x].begin();
			if(t>0) L[i]=v[x][t-1];
			else L[i]=0;
			if(t<v[x].size()) R[i]=v[x][t];
			else R[i]=n+1;
		}
		for(int i=1;i<=n;i++)
		{
			l[i]=r[i]=i;
			if(i==1)
			{
				solve(l[i],r[i]);
			}
			else
			{
				if(!check(i,i-1))
				{
					solve(l[i],r[i]);
				}
				else
				{
					l[i]=l[i-1];
					r[i]=max(r[i],r[i-1]);
					solve(l[i],r[i]);
				}
			}
		}
		while(m--)
		{
			int x,y;
			cin>>x>>y;
			if(x==y) cout<<"Yes"<<endl;
			else if(x<y)
			{
				if(r[x]>=y) cout<<"Yes"<<endl;
				else cout<<"No"<<endl;
			}
			else
			{
				if(l[x]<=y) cout<<"Yes"<<endl;
				else cout<<"No"<<endl;
			}
		}
	}
	return 0;
}

Orgrimmar(树形DP)

题意
求一棵树的最大分离集的大小。
分离集(dissociation set):一个顶点的集合,如果只保留这些顶点之间的边,那么该集合中的每个顶点最多只与一条边相连。

分析
f [ x ] [ 0 / 1 / 2 ] f[x][0/1/2] f[x][0/1/2]表示不选 x x x、选 x x x且选 x x x连向子结点的边、选 x x x但不选 x x x连向子结点的边分离集顶点数的最大值。状态转移方程如下,其中 x x x y y y的父结点。
{ f [ x ] [ 0 ] = ∑ m a x ( f [ y ] [ 0 ] , f [ y ] [ 1 ] , f [ y ] [ 2 ] ) f [ x ] [ 1 ] = 1 + ∑ f [ y ] [ 0 ] + m a x ( 0 , f [ y ] [ 2 ] − f [ y ] [ 0 ] ) f [ x ] [ 2 ] = 1 + ∑ f [ y ] [ 0 ] \left\{\begin{array}{l}f\lbrack x\rbrack\lbrack0\rbrack={\textstyle\sum}max(f\lbrack y\rbrack\lbrack0\rbrack,f\lbrack y\rbrack\lbrack1\rbrack,f\lbrack y\rbrack\lbrack2\rbrack)\\f\lbrack x\rbrack\lbrack1\rbrack={\textstyle1+\sum}f\lbrack y\rbrack\lbrack0\rbrack+{\textstyle max(0,f\lbrack y\rbrack\lbrack2\rbrack-f\lbrack y\rbrack\lbrack0\rbrack)}\\f\lbrack x\rbrack\lbrack2\rbrack={\textstyle1+\sum f\lbrack y\rbrack\lbrack0\rbrack}\end{array}\right. f[x][0]=max(f[y][0],f[y][1],f[y][2])f[x][1]=1+f[y][0]+max(0,f[y][2]f[y][0])f[x][2]=1+f[y][0]
对于 f [ x ] [ 1 ] f[x][1] f[x][1]来说,只能由子结点 0 / 2 0/2 0/2两种状态转移而来,且 2 2 2状态最多选一个,因此贪心的选择 f [ y ] [ 2 ] − f [ y ] [ 0 ] f[y][2]-f[y][0] f[y][2]f[y][0]值最大的结点。

AC代码

const int N=5e5+10;
const int M=2*N;
int head[N],e[M],ne[M],tot;
int f[N][3];

void add(int x,int y)
{
	e[++tot]=y,ne[tot]=head[x],head[x]=tot;
}

void dfs(int x,int p)
{
	int mx=0;
	for(int i=head[x];i;i=ne[i])
	{
		int y=e[i];
		if(y==p) continue;
		dfs(y,x);
		f[x][0]+=max({f[y][0],f[y][1],f[y][2]});
		f[x][2]+=f[y][0];
		f[x][1]+=f[y][0];
		mx=max(mx,f[y][2]-f[y][0]);
	}
	f[x][2]+=1;
	f[x][1]+=mx+1;
}

int main()
{
	int size(512<<20);  // 512M
    __asm__ ( "movq %0, %%rsp\n"::"r"((char*)malloc(size)+size));
	int T;
	cin>>T;
	while(T--)
	{
		int n;
		cin>>n;
		//初始化 
		for(int i=1;i<=n;i++) head[i]=0;
		for(int i=1;i<=n;i++) f[i][0]=f[i][1]=f[i][2]=0;
		tot=0;
		for(int i=1;i<n;i++)
		{
			int x,y; cin>>x>>y;
			add(x,y); add(y,x);
		}
		dfs(1,-1);
		cout<<max({f[1][0],f[1][1],f[1][2]})<<endl;
	}
	exit(0);
}

注意
本题需要手动开栈,否则会RE

int main() {
    int size(512<<20);  // 512M
    __asm__ ( "movq %0, %%rsp\n"::"r"((char*)malloc(size)+size));
    // YOUR CODE
   ...
    exit(0);
}

Stormwind(思维)

题意
有一个 n ∗ m n*m nm的矩形,现在要在这个矩形上画线,要满足以下条件:

  • 线的端点在矩形的边界上
  • 线至少与矩形的一条边界平行
  • 切割后每个小矩形的长和宽都是整数
  • 每个矩形的面积最少是 k k k
  • 两条直线不能重合

求最大画线的数量。

分析
枚举小矩形的长宽,由长宽可以确定两个方向上的直线数量,取最大值即可,枚举的小矩形的面积可以大于 k k k。时间复杂度 O ( T k ) O(Tk) O(Tk)

AC代码

int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		int n,m,k;
		cin>>n>>m>>k;
		int ans=0;
		for(int i=1;i<=k;i++)
		{
			int j=(k+i-1)/i;
			if(n<i||m<j) continue;
			int x=n/i-1;
			int y=m/j-1;
			ans=max(ans,x+y);
		}
		cout<<ans<<endl;
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值