【 问题描述】
美国队长和钢铁侠正在与很多外星人战斗。 这场战斗的持续时间是 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
【 解释】
样例 1 中, GT,TG,GGTT,TGGT,GTGGTT 都是“ 好” 的子串, 其中 GT 出现
了 2 次, 所以总的个数是 6。
【 数据规模与约定】
保证 m 和 g 至少有一个非 0。 不保证 m:g 是最简比。
保证 S 中的字符只会是 M,G,T 之一。
数据可能有 3 种特殊限定之一。
限定 1: S 只出现 1 种字母。
限定 2: m*g=0
限定 3: S 中没有字符’T’, 且所有的’M’都在所有的’G’之前。
测试点编号 | t 的上界 | 特殊限定 |
---|---|---|
1 | 100 | 1 |
2~5 | 100 | 无 |
6 | 2000 | 2 |
7 | 2000 | 3 |
8~12 | 2000 | 无 |
13,14 | 100000 | 2 |
15,16 | 100000 | 3 |
17~20 | 100000 | 无 |
保证 m,g<=10^9。
显然的,方便区间统计,我们先要做两个前缀和数组: s u m M [ i ] sumM[i] sumM[i]代表前i个位置美队消灭的敌人数, s u m G [ i ] sumG[i] sumG[i]代表前i个位置钢铁侠消灭的敌人数。题目中要求“好”的字符串中钢铁侠消灭的敌人数和美队消灭的敌人数成比例,也就是说:
s u m G [ r ] − s u m G [ l − 1 ] s u m M [ r ] − s u m M [ l − 1 ] = g m \frac{sumG[r]-sumG[l-1]}{sumM[r]-sumM[l-1]}=\frac{g}{m} sumM[r]−sumM[l−1]sumG[r]−sumG[l−1]=mg
那么子串[l,r]就是“好”的。
那么,接下来就是对这个式子进行推导(xiagao)了:
s u m G [ r ] − s u m G [ l − 1 ] s u m M [ r ] − s u m M [ l − 1 ] = g m \frac{sumG[r]-sumG[l-1]}{sumM[r]-sumM[l-1]}=\frac{g}{m} sumM[r]−sumM[l−1]sumG[r]−sumG[l−1]=mg
( s u m G [ r ] − s u m G [ l − 1 ] ) ∗ m = ( s u m M [ r ] − s u m M [ l − 1 ] ) ∗ g (sumG[r]-sumG[l-1])*m=(sumM[r]-sumM[l-1])*g (sumG[r]−sumG[l−1])∗m=(sumM[r]−sumM[l−1])∗g
m ∗ s u m G [ r ] − m ∗ s u m G [ l − 1 ] = g ∗ s u m M [ r ] − g ∗ s u m M [ l − 1 ] m*sumG[r]-m*sumG[l-1]=g*sumM[r]-g*sumM[l-1] m∗sumG[r]−m∗sumG[l−1]=g∗sumM[r]−g∗sumM[l−1]
m ∗ s u m G [ r ] − g ∗ s u m M [ r ] = m ∗ s u m G [ l − 1 ] − g ∗ s u m M [ l − 1 ] m*sumG[r]-g*sumM[r]=m*sumG[l-1]-g*sumM[l-1] m∗sumG[r]−g∗sumM[r]=m∗sumG[l−1]−g∗sumM[l−1]
接下来神奇的事情就发生了:我们发现变形后的式子左右两边的形式完全相同,而且数组下标只分别和l-1,r有关。那么我们就可以用值来当下标,做一个桶了。如果检查到这个字已经出现,那就说明一定存在一个符合要求的“好”子串。所以我们用map开两个桶first,cnt分别记录等式一边等于为某个值时,这个子串第一次出现的位置和相同长度子串的个数。这样一重循环扫描就可以统计,累加得到答案了。
#include
using namespace std;
#define INF 0x3f3f3f3f
inline void read(int &k)
{
int x=0,w=0;char ch;
while(!isdigit(ch))w|=ch=='-',ch=getchar();
while(isdigit(ch))x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
k=(w?-x:x);return;
}
int t,m,g,Maxlen=-1,Maxcnt=0,Allcnt=0,L,R,sumG[208000],sumM[208000];
char s[200080];map< int,int >first,cnt;
inline int gcd(int a,int b){return b==0?a:gcd(b,a%b);}
inline void input(void)
{
read(t),read(m),read(g);
int temp=gcd(m,g);
m/=temp,g/=temp;
for(int i=1;i<=t;i++)
{
cin>>s[i];
sumG[i]=sumG[i-1],sumM[i]=sumM[i-1];
if(s[i]=='G')sumG[i]+=1;
if(s[i]=='M')sumM[i]+=1;
if(s[i]=='T')sumG[i]+=1,sumM[i]+=1;
}
}
inline void solve()
{
first[0]=cnt[0]=1;
for(int i=1;i<=t;i++)
{
int value=m*sumG[i]-g*sumM[i];
if(cnt.count(value))
{
if(i-first[value]+1>Maxlen)
{
Maxlen=i-first[value]+1;
L=first[value];
R=i;
Maxcnt=1;
}
else if(i-first[value]+1==Maxlen)
{
Maxcnt++;
}
Allcnt+=cnt[value];
}
else first[value]=i+1;
cnt[value]++;
}
}
int main(void)
{
freopen("fight.in","r",stdin);
freopen("fight.out","w",stdout);
input();
solve();
if(!Allcnt)printf("A weird fight\n");
else
{
printf("%d %d %d\n",L,R,Maxcnt);
printf("%d\n",Allcnt);
}
return 0;
}
【问题描述】
在一个n*m的方格图中,选中三个单元格,需满足以下两个条件:
(1)任意两个单元格都不在同一行。
(2)任意两个单元格都不在同一列。
选择的花费是三个格子两两之间曼哈顿距离的和(如(x1,y1)和(x2,y2)的曼哈顿距离为|x1-x2|+|y1-y2|),问花费在l到r之间的方案数有多少。
所谓的两种不同方案是指:只要它选中的单元格有一个不同,就认为是不同的方案。答案模1000000007。
【输入格式】
第一行4个整数n,m,l,r。
【输出格式】
一个整数,表示不同的选择方案数量模1000000007后的结果。
orzfang.in orzfang.out
3 3 1 20000 6
3 3 4 7 0
4 6 9 12 264
7 5 13 18 1212
4000 4000 4000 14000 859690013
【输入输出样例】
【数据规模】
对于30%的数据,3≤n,m≤70。
对于100%的数据 3≤n,m≤20000。
由于三个点的横坐标,纵坐标各不相同,且他们是无法辨识的,我们不妨设 x 1 < x 2 < x 3 , y 1 < y 2 < y 3 x_1<x_2<x_3,y_1<y_2<y_3 x1<x2<x3,y1<y2<y3,则他们的花费就一定是 2 ∣ x 3 − x 1 ∣ + 2 ∣ y 3 − y 1 ∣ 2|x_3-x_1|+2|y_3-y_1| 2∣x3−x1∣+2∣y3−y1∣,我们很容易就能发现这三个点的排列顺序互换时,他们的两两间曼哈顿距离之和是不变的,所以,当我们确切的找到三个点的坐标时,它其实对答案贡献了 3 ! = 6 3!=6 3!=6种方案,然而我们其实只需要确定最左下角和最右上角的点,中间的点是可以随意移动,构成多种方案的,所以,我们枚举 x 1 , y 1 , x 3 , y 3 x_1,y_1,x_3,y_3 x1,y1,x3,y3且保证 x 3 − x 1 > 1 , y 3 − y 1 > 1 x_3-x_1>1,y_3-y_1>1 x3−x1>1,y3−y1>1,那么在代价符合范围条件是,它就对答案造成了 6 ∗ ( x 3 − x 1 − 1 ) ∗ ( y 3 − y 1 − 1 ) 6*(x_3-x_1-1)*(y_3-y_1-1) 6∗(x3−x1−1)∗(y3−y1−1)种方案的贡献(因为中间点是可以在内部随意移动的),四重循环枚举,这就是30分的写法。
换个思路,我们可以枚举最左下角的点和最右上角的点构成的矩形,即枚举它的长(x)和宽(y)。这时它的花费是 2 ∗ ( x + y − 2 ) 2*(x+y-2) 2∗(x+y−2),它的贡献是 6 ∗ ( x − 2 ) ∗ ( y − 2 ) 6*(x-2)*(y-2) 6∗(x−2)∗(y−2),这时,这样枚举的好处就体现出来了,我们可以直接计算出这样的矩形有几个: ( n − x + 1 ) ∗ ( m − y + 1 ) (n-x+1)*(m-y+1) (n−x+1)∗(m−y+1)个,那么这种情况下的所有方案也就算出来了:当 l ≤ 2 ∗ ( x + y − 2 ) ≤ r l≤2*(x+y-2)≤r l≤2∗(x+y−2)≤r时,边长为 x , y x,y x,y的矩形总贡献为 6 ∗ ( x − 2 ) ∗ ( y − 2 ) ∗ ( n − x + 1 ) ∗ ( m − y + 1 ) 6*(x-2)*(y-2)*(n-x+1)*(m-y+1) 6∗(x−2)∗(y−2)∗(n−x+1)∗(m−y+1),那么 O ( n 2 ) O(n^2) O(n2)枚举x,y就能得到答案。
#include
using namespace std;
const long long P=1000000007;
inline void read(long long &k)
{
long long x=0,w=0;char ch;
while(!isdigit(ch))w|=ch=='-',ch=getchar();
while(isdigit(ch))x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
k=(w?-x:x);return;
}
long long n,m,l,r,ans=0;
inline void input(void)
{
read(n),read(m);
read(l),read(r);
}
inline void work(void)
{
for(int x=3;x<=n;x++)
{
for(int y=3;y<=m;y++)
{
if(2*(x+y-2)>=l&&2*(x+y-2)<=r)ans+=(((n-x+1)%P*(m-y+1)%P*(x-2)%P*(y-2)%P*6%P)%P)%P,ans%=P;
}
}
}
int main(void)
{
freopen("orzfang.in","r",stdin);
freopen("orzfang.out","w",stdout);
input();
work();
printf("%d\n",ans%P);
return 0;
}