Codeforces Round #645 (Div. 2)补题

好久没写题了……
最近好像能做出2D了,不过上分还是困难。
比赛传送门
这场出题人作的要死,对于题面内容这里不再提。

本场关键词

贪心、找规律、前缀和、二分

A.Park Lighting

题意

给你 n × m n\times m n×m的网格,你可以在两个网格之间的边上放一个灯,这个灯将会照亮这两个网格。
求出照亮所有的 n × m n\times m n×m个格子的最小需要的灯数。

思路

贪心,只要 n n n m m m中间有一个是偶数,那么答案就是总格子数除以二;如果都为奇数,那么先暂时去掉一行,将这部分如上计算,再对剩下这一行贪心考虑。

代码

#include
using namespace std;
typedef long long ll;
const int maxn=2e5+10,inf=0x3f3f3f3f,mod=1000000007;
signed main()
{
	int t,n,m;
	cin>>t;
	while(t--)
	{
		cin>>n>>m;
		ll ans=0;
		if(n%2==0)
			ans=n/2*m;
		else if(m%2==0)
			ans=m/2*n;
		else{
			ans=n/2*m;
			ans+=m/2+1;
		}
		cout<<ans<<endl;
	}
	return 0;
}

B.Maria Breaks the Self-isolation

题意

小M在办聚会,他现在想邀请 n n n个人,第 i i i个人有一个数值 a i a_i ai,表示当他到小M家时,如果在场的人(不包括他自己)达到 a i a_i ai,这个人就会留下。
求这场聚会最多可以邀请多少人,人数包括小M。

思路

排序之后贪心,如果 a i a_i ai被邀请了,那么数值小于等于 a i a_i ai的人一定也会被邀请。

代码

#include
using namespace std;
typedef long long ll;
const int maxn=2e5+10,inf=0x3f3f3f3f,mod=1000000007;
ll a[maxn];
signed main()
{
	int t,n;
	cin>>t;
	while(t--)
	{
		cin>>n;
		for(int i=1;i<=n;i++)
			cin>>a[i];
		ll ans=0,tot=0;
		sort(a+1,a+n+1);
		for(int i=1;i<=n;i++)
		{
			tot++;//把这一个叫过来
			if(a[i]<=tot)
				ans=tot;
		}
		cout<<ans+1<<endl;
	}
	return 0;
}

C.Celex Update

题意

Codeforces Round #645 (Div. 2)补题_第1张图片
给你一个按照上图规律无限扩展的矩阵。
t t t组询问,每组 x 1 , y 1 , x 2 , y 2 x_1,y_1,x_2,y_2 x1,y1,x2,y2四个整数,保证 x 1 ≤ x 2   , y 1 ≤ y 2 x_1\le x_2\ ,y_1\le y_2 x1x2 ,y1y2,输出 ( x 1 , y 1 ) (x_1,y_1) (x1,y1) ( x 2 , y 2 ) (x_2,y_2) (x2,y2)的所有路径中,权值和不同的路径个数。
( x , y ) (x,y) (x,y)表示在第 x x x y y y列,只能向下或向右行走。

思路

找规律,组合数学
猜一发所有合法路径不会有权值和相同的情况,那么的话单纯计算路径数量只和 x 2 − x 1 x_2-x_1 x2x1 y 2 − y 1 y_2-y_1 y2y1有关, a n s = ( x 2 − x 1 ) × ( y 2 − y 1 ) + 1 ans=(x_2-x_1)\times (y_2-y_1)+1 ans=(x2x1)×(y2y1)+1
我也不知道怎么证明……

代码

#include
using namespace std;
typedef long long ll;
signed main()
{
	ll t,x1,x2,y1,y2;
	cin>>t;
	while(t--)
	{
		cin>>x1>>y1>>x2>>y2;
		ll n=x2-x1,m=y2-y1,ans;
		cout<<n*m+1<<endl;
	}
	return 0;
}

D.The Best Vacation

题意

你将选择一段时间和小C连续在一起度过 x x x天假期。
这里一年有 n n n个月,第 i i i个月有 d i d_i di天,当你在每个月第 j j j日和小C在一起时,他将给你 j j j个拥抱。
求你最多能得到多少拥抱数。
保证 1 ≤ x ≤ d 1 + d 2 + ⋯ + d n 1\le x\le d_1+d_2+\dots +d_n 1xd1+d2++dn

思路

贪心+前缀和+二分
首先 n n n已经达到了2e5,所以我们不可能枚举每一天作为起始点或者结束点。考虑枚举每个月时,可以发现将每个月最后一天作为结束点时是最优的,而因为 x ≤ d 1 + d 2 + ⋯ + d n x\le d_1+d_2+\dots +d_n xd1+d2++dn,所以度假期最多只可能跨一次年。
所以这里将 n n n翻倍,将相邻两年拼接起来,记 s u m 2 [ i ] sum2[i] sum2[i]为第 1 1 1月到第 i i i月的累积贡献, s u m 3 [ i ] sum3[i] sum3[i]为第 1 1 1月到第 i i i月的累积天数,当确定了结尾的月份时,在 s u m 3 sum3 sum3上二分出起始点所在月份,中间的月份的贡献通过 s u m 2 sum2 sum2查询,在起始点所在月份计算剩余天数的贡献。
记录其中出现的最大值即可。

代码

#include
using namespace std;
typedef long long ll;
#define int ll
const int maxn=1e6+10,inf=0x3f3f3f3f,mod=1000000007;
int d[maxn],sum[maxn],sum2[maxn],sum3[maxn];
int get(int a,int b,int n)
{//从a月b日开始待n天的贡献
	return sum[b+n-1]-sum[b-1];//待最后n天的贡献
}
int getm(int a,int x,int n)
{//从a月结束,查找起始所在月份
	int l=1,r=a,ans=0;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(sum3[a]-sum3[mid-1]>=x)
			l=mid+1,ans=mid;
		else
			r=mid-1;
	}
	return ans;
}
signed main()
{
	std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	#ifdef DEBUG
		freopen("input.in", "r", stdin);
	//	freopen("output.out", "w", stdout);
	#endif
	for(int i=1;i<maxn;i++)
		sum[i]=sum[i-1]+i;
	int n,x,ans=0;
	cin>>n>>x;
	for(int i=1;i<=n;i++)
		cin>>d[i];//
	for(int i=1;i<=2*n;i++)
	{//月份的累计贡献前缀和
		sum2[i]=sum2[i-1];
		sum3[i]=sum3[i-1];
		if(i<=n)
			sum2[i]+=get(i,1,d[i]),sum3[i]+=d[i];
		else
			sum2[i]+=get(i-n,1,d[i-n]),sum3[i]+=d[i-n];
	}
	for(int i=n+1;i<=2*n;i++)
	{//枚举每个月
		int now=get(i,max(1ll,d[i-n]-x+1),min(x,d[i-n])),ret,tar;
		if(d[i-n]>=x)
		{//该月天数大于x
			ans=max(ans,now);
			continue;
		}
		else
			ret=x-d[i-n];
		tar=getm(i-1,ret,n);//起始月份
		now+=sum2[i-1]-sum2[tar];
		ret-=sum3[i-1]-sum3[tar];//剩余天数
		if(tar<=n)
			now+=get(tar,d[tar]-ret+1,ret);
		else
			now+=get(tar,d[tar-n]-ret+1,ret);
		ans=max(ans,now);
	}
	cout<<ans<<endl;
	return 0;
}

E.Are You Fired?

哇这题好难搞,看了题解也不是很理解。

题意

给定一个长度为 n n n的数组,其中前 ⌈ n 2 ⌉ \left\lceil\dfrac{n}{2}\right\rceil 2n 项中第 i i i项的值为 a i a_i ai,后面所有项值均为 x x x
现在要你确定一个整数 k k k,使得数组中任意一个长度为 k k k的子段和 > 0 >0 >0,若不存在则输出 − 1 -1 1

思路

当有一个 k ′ ≤ ⌊ 1 n ⌋ k'\le \left\lfloor\dfrac{1}{n}\right\rfloor kn1符合条件时,可以确定 k = 2 k ′ k=2k' k=2k一定同样成立,这就说明假如存在解,就一定存在 k > n 2 k>\dfrac {n}{2} k>2n成立。
x ⩾ 0 x\geqslant 0 x0时,可以确定最优情况为 k = n k=n k=n,判断即可。
否则接着深入研究,尝试找出对每个长度 k k k,出现的最小 k k k长度子段和。
p p p为长度为 k k k的字段和 s s s差分数组 s 1 = a 1 + a 2 + ⋯ + a k s_1=a_1+a_2+\dots +a_k s1=a1+a2++ak
s i + 1 = s i + a i + k − a i s_{i+1}=s_{i}+a_{i+k}-a_i si+1=si+ai+kai,因为 k > n 2 k>\dfrac {n}{2} k>2n,则 a i + k = x a_{i+k}=x ai+k=x
p = [ s 1 , x − a 1 , x − a 2 , … , x − a n − k ] p=[s_1,x-a_1,x-a_2,\dots ,x-a_n-k] p=[s1,xa1,xa2,,xank]
k k k增大了 1 1 1时,差分数组 p p p的首项 + x +x +x,并去掉最后一项。
任意 k k k长度的首项都可以用前缀和 s u m sum sum求出。
另开一个 d p dp dp数组, d p [ i ] dp[i] dp[i]表示对于 ∑ j = 1 i x − a j \sum\limits_{j=1}^ix-a_j j=1ixaj过程中出现的最小和,用来表示 k = n − i k=n-i k=ni的差分数组中出现的最小差分前缀和。
然后枚举下 k k k,检查一下 s u m [ k ] + d p [ n − k ] > 0 sum[k]+dp[n-k]>0 sum[k]+dp[nk]>0,存在则直接输出。

代码

#include
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
#define int ll
const int maxn=5e5+10,inf=0x3f3f3f3f,mod=1000000007;
//#define DEBUG//#define lowbit(x) ((x) & -(x))//<
void read(){}
template<typename T,typename... T2>inline void read(T &x,T2 &... oth) {
	x=0; int ch=getchar(),f=0;
	while(ch<'0'||ch>'9'){if (ch=='-') f=1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	if(f)x=-x;
	read(oth...);
}
int a[maxn],sum[maxn];
signed main()
{
	std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	#ifdef DEBUG
		freopen("input.in", "r", stdin);
	//	freopen("output.out", "w", stdout);
	#endif
	int n,lim,x;
	cin>>n;
	lim=n/2+(n%2);
	for(int i=1;i<=lim;i++)
		cin>>a[i],sum[i]=sum[i-1]+a[i];
	cin>>x;
	for(int i=lim+1;i<=n;i++)
		sum[i]=sum[i-1]+(a[i]=x);
	if(x>=0)
	{
		cout<<((sum[n]>0)?n:-1)<<endl;
		return 0;
	}
	vector<int>dp(maxn,0);
	for(int i=1,tot=0;i<=lim;i++)
	{//dp[i]表示对长度为n-i的串,进行差分叠加过程中出现的最小和
		tot+=x-a[i];//差分前缀和
		dp[i]=min(dp[i-1],tot);
	}
	for(int k=lim;k<=n;k++)//枚举长度,首项[1,k]的和
	{//k增大1时,右面有一个x并到了首项中
		if(sum[k]+dp[n-k]>0)//前缀和,作为s的首项
		{//如果其中最小和和
			cout<<k<<endl;
			return 0;
		}
	}
	cout<<-1<<endl;
	return 0;
}

你可能感兴趣的:(OJ上的做题经验)