【考题详解】数学推导与枚举优化类考题

本文通过分析超级英雄战斗问题,探讨如何利用数学推导和枚举优化来解决问题。具体涉及前缀和计算,寻找满足特定比例的最长连续时间段,以及通过曼哈顿距离计算方案数量的方法。通过实例解释了枚举技巧和优化策略。
摘要由CSDN通过智能技术生成

超级英雄的战斗(fight)

【 问题描述】
美国队长和钢铁侠正在与很多外星人战斗。 这场战斗的持续时间是 t
分钟, 每分钟美国队长和钢铁侠都可能消灭 0 个或 1 个外星人。 我们用一
个长度为 t 的字符串 S 来描述这场战斗, 每个字符代表一分钟内发生的情
况: M 表示只有美国队长消灭了 1 个外星人, G 表示只有钢铁侠消灭了 1
个外星人, T 表示两人同时消灭了 1 个外星人, 不存在某 1 分钟两人都没
有消灭外星人。
钢铁侠知道一般情况下他们两个的战斗能力比是 m:g, 表示在相同时
间内如果美国队长消灭了 mk 个敌人, 钢铁侠应当能消灭 gk 个。 战斗之
后, 钢铁侠发现, 并不是所有连续时间段都符合一般情况的。 用 S 的一个
子串 S2=S[l…r]表示一个连续时间段, 我们称 S2 是“好” 的, 当且仅当在
第 l 分钟到第 r 分钟( 两端均包含) 美国队长消灭的敌人数和钢铁侠消灭
的敌人数之比恰好等于 m:g。
钢铁侠需要计算最长的“好” 的 S 的非空子串, 最长的“ 好” 的子串
的数量, 以及 S 的所有“好” 的非空子串的数量。 如果字符串一样但位置
不同分开计算数量。
【 输入格式(fight.in)】
输入第一行包含 3 个非负整数: t,m 和 g。
第二行包含一个长度为 t 的字符串 S, 含义如前所说。
【 输出格式(fight.out)】
输出两行。 第一行包含 3 个整数 l,r 和 cnt, 表示 S 的子串 S[l…r]是最
长的“好” 子串( S 的编号从 1 开始) , 如果有多个输出最左边的子串,
以及与它等长的“ 好”的子串的数量。 第二行包含 1 个整数, 表示 S 的“好”
的非空子串数量。 如果不存在“好” 的非空子串, 只输出”A weird fight”( 不
包含引号) 。
【 样例输入 1】
6 1 2
GTGGTT
【 样例输出 1】
1 6 1
6
【 样例输入 2】
2 3 2
TMG
【 样例输出 2】
A weird fight

显然,暴力枚举,就是预处理一个前缀和枚举左区间有区间统计即可。
钢铁侠的前缀和为G[i],美国队长的前缀和为M[i]。
如果要满足条件,对于左右区间,一定满足下列等式: g / m = ( G [ r ] − G [ l − 1 ] ) / ( M [ r ] − M [ l − 1 ] ) g/m=(G[r]-G[l-1])/(M[r]-M[l-1]) g/m=(G[r]G[l1])/(M[r]M[l1])
变形,就是: g ∗ ( M [ r ] − M [ l − 1 ] ) = m ∗ ( G [ r ] − G [ l − 1 ] ) g*(M[r]-M[l-1])=m*(G[r]-G[l-1]) g(M[r]M[l1])=m(G[r]G[l1])
展开,得: g ∗ M [ r ] − g ∗ M [ l − 1 ] = m ∗ G [ r ] − m ∗ G [ l − 1 ] g*M[r]-g*M[l-1]=m*G[r]-m*G[l-1] gM[r]gM[l1]=mG[r]mG[l1]
移项,得: g ∗ M [ r ] − m ∗ G [ r ] = g ∗ M [ l − 1 ] − m ∗ G [ l − 1 ] g*M[r]-m*G[r]=g*M[l-1]-m*G[l-1] gM[r]mG[r]=gM[l1]mG[l1]
显然,我们可以先求出每一个l值,在枚举r的时候直接查找用数组标记的l值即可。
程序注意:枚举i时,对于r是i,对于l是l+1,所以作为r的i不能直接调用当前i的l。
代码如下:

#include<bits/stdc++.h>
using namespace std;
#define maxn 120000
#define LL long long

int t,m,g;
char a[maxn];
int G[maxn],M[maxn];
int ans=0,maxcnt=0,ans_left=0,maxlen=0;
map<int,int> first,cnt;

int main()
{
	freopen("fight.in","r",stdin);
	freopen("fight.out","w",stdout);
	cin>>t>>m>>g;
	for (int i=1;i<=t;++i)
	{
		cin>>a[i];
		G[i]=G[i-1],M[i]=M[i-1];
		if (a[i]=='T') G[i]++,M[i]++;
		else if (a[i]=='M') M[i]++;
		else if (a[i]=='G') G[i]++;
	}
	first[0]=cnt[0]=1;
	for (int i=1;i<=t;++i) 
	{
	    LL val=(LL)m*G[i]-g*M[i];//计算Right的值 
		if (!first[val]) first[val]=i+1,cnt[val]=1;
		//同时i也作为i的值进行运算 实际的含义是left=1 
		else
		{
			ans+=cnt[val];
			if (i-first[val]>maxlen)
			{
				maxlen=i-first[val];
				ans_left=first[val];
				maxcnt=1;
			} 
			else if (i-first[val]==maxlen) maxcnt++;
			cnt[val]++;
			//注意顺序不能乱:Right(i)使用过才能使用Left(i+1),该left状态在Right下不合法 
		}
	} 
	if (!maxlen) cout<<"A weird fight\n"; 
	else cout<<ans_left<<' '<<ans_left+maxlen<<' '<<maxcnt<<'\n'<<ans<<'\n';
	return 0;
}

方方方

我们知道,对于三个点的曼哈顿距离只和为:左上角和右下角组成的矩形。
对于每一个矩形,若长度为x,y,则移动有 ( n − x + 1 ) ( m − y + 1 ) (n-x+1)(m-y+1) (nx+1)(my+1)
证明:行列。显而易见。
若长度为x,y,则一共选择的方案数就是 6 ∗ ( n − 2 ) ( m − 2 ) 6*(n-2)(m-2) 6(n2)(m2)
证明:
1.若一个是左上角,一个是右下角,合法的第三个点就是中间矩形部分;左下角和右上角同样。故总方案数为: 2 ∗ ( x − 2 ) ∗ ( y − 2 ) 2*(x-2)*(y-2) 2(x2)(y2)
2.若只有一个是角落的,假设是左上角,且另外亮点一定不再角落上,同样是在右边的和下面的边中去掉两个端点的位置;右上角,右下角,左下角同理。故总方案数为: 4 ∗ ( x − 2 ) ∗ ( y − 2 ) 4*(x-2)*(y-2) 4(x2)(y2)
因此x
y的矩形拥有: ( n − x + 1 ) ∗ ( n − y + 1 ) ∗ 6 ∗ ( x − 2 ) ∗ ( y − 2 ) (n-x+1)*(n-y+1)*6*(x-2)*(y-2) (nx+1)(ny+1)6(x2)(y2)
直接枚举即可。

#include<bits/stdc++.h>
using namespace std;
#define maxn
#define P 1000000007
inline void read(long long &readnum)
{
	long long s=0,w=1;char c=getchar();
	while (c<'0' || c>'9') {if (c=='-') w=-1;c=getchar();}
	while (c>='0' && c<='9') s=s*10+c-48,c=getchar();
	readnum=s*w;
}

long long n,m,l,r;
long long ans=0;

inline long long num(long long x,long long y) {
	return (n-x+1)*(m-y+1)%P;
}

inline long long sum(long long x,long long y) {
	return 6*(x-2)*(y-2)%P;
}

int main(void)
{
	freopen("orzfang.in","r",stdin);
	freopen("orzfang.out","w",stdout);
	read(n);
	read(m);
	read(l);
	read(r);
	for (long long i=3;i<=n;++i)
	    for (long long j=3;j<=m;++j)
	        if ((i+j-2)*2>=l && (i+j-2)*2<=r)
	            ans=(ans+(num(i,j)*sum(i,j))%P)%P;
	cout<<ans<<endl;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值