Codeforces Round #654 (Div. 2) E F
E1 Asterism (Easy Version)
题意:
有n个敌人,编号从1到n,每个人手中有ai个糖果。yuzhu手中一开始有x个糖果,他会先决定一个从1到n的排列P,然后他会按照排列P的顺序分别与编号为Pi的敌人决斗,如果yuzhu手中的糖果数大于等于敌人手中的糖果数,那么yuzhu获得胜利,并且获得一个糖果,否则他会失败,并且什么也不会获得,能够使yuzhu赢得所有决斗的排列称为有效排列。
Akari根据上述观点提出了以下问题:定义
F
(
x
)
F(x)
F(x)表示x的有效排列数。给你n,p以及ai
(
2
≤
p
≤
n
≤
2000
,
1
≤
a
i
≤
2000
)
(2 \le p \le n \le 2000,1 \le a_i \le 2000)
(2≤p≤n≤2000,1≤ai≤2000),其中p是一个质数,要求你找出满足
F
(
x
)
%
p
≠
0
F(x)\%p\neq0
F(x)%p=0,即
F
(
x
)
F(x)
F(x)和p互质这一条件的x的数量。
思路:
首先,对于任意的x和ai,将敌人编号按照ai大小递增排序的排列P是最有可能是有效排列的。在这个排列的基础上,如果要赢得第i次决斗(i从0开始)那就有
x
+
i
≥
a
i
x+i \ge a_i
x+i≥ai,对这个式子变形就可以得到
i
≥
a
i
−
x
i \ge a_i-x
i≥ai−x,也就是说,在满足
x
+
i
≥
a
i
x+i \ge a_i
x+i≥ai,即能够成功进行到第i次决定的条件下,原本i位置上的这个数字是可以变动到
a
i
−
x
a_i-x
ai−x到i-1这些位置上去的。于是就可以开一个数组b来记录每个位置上的能够出现的数字的数目,最后符合要求的排列总数就是
∏
b
i
\prod b_i
∏bi。
显然当
x
≥
max
(
a
i
)
x \ge \max(a_i)
x≥max(ai)时,
F
(
x
)
=
n
!
F(x)=n!
F(x)=n!,由于
p
≤
n
p \le n
p≤n,此时
F
(
x
)
%
p
=
0
F(x)\%p=0
F(x)%p=0必然成立。由于ai的范围不大(1
≤
a
i
≤
2000
\le a_i \le 2000
≤ai≤2000),于是可以直接暴力枚举x(1
≤
x
≤
2000
\le x \le 2000
≤x≤2000)来求每个
F
(
x
)
F(x)
F(x)。先对ai序列按升序排序,从前往后遍历,如果不满足
x
+
i
≥
a
i
x+i \ge a_i
x+i≥ai则说明
F
(
x
)
=
0
F(x) =0
F(x)=0,即不存在一个有效序列,自然也不可能与p互质,直接看x+1。对于每个i,求出
a
i
−
x
a_i-x
ai−x,在
b
[
a
i
−
x
]
b[a_i-x]
b[ai−x]上加1,若
x
>
a
i
x \gt a_i
x>ai,则在
b
[
0
]
b[0]
b[0]上加1。通过前面的思路可以发现,我们现在的bi并不是我上一段中所说的那个bi,要得到上一段定义的bi,应该是求
b
[
i
]
b[i]
b[i]的累加和,然后减去多加的一部分,即i前面位置的那些数,共i个,于是第i位上能够出现的数字数目为
∑
0
i
b
[
i
]
−
i
\sum_0^ib[i]-i
∑0ib[i]−i。之后从前往后遍历一遍b数组,因为p是质数,故不需要求出总数目,只需看乘数中是否出现了p的倍数,即判断
(
∑
0
i
b
[
i
]
−
i
)
%
p
(\sum_0^ib[i]-i)\%p
(∑0ib[i]−i)%p是否等于0即可,每一位都与p互质的即为满足条件的,记录下来以便之后输出。
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<set>
#include<stack>
#include<cmath>
#include<string>
#define ll long long
#define ull unsigned long long
#define PI acos(-1.0)
#define pii pair<int,int>
#define fi first
#define se second
using namespace std;
const int N=2e3+100;
const int mod=1e9+7;
int n,p,a[N],b[N];
int main()
{
int T;
T=1;
// scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&p);
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
sort(a,a+n);
vector<int> res;
for(int x=1;x<=2000;x++)
{
int flag=0;
memset(b,0,sizeof(b));
for(int i=0;i<n;i++)
{
if(a[i]<=x)
b[0]++;
else
{
if(a[i]-x>=n)
{
flag=1;
break;
}
else b[a[i]-x]++;
}
}
if(flag) continue;
for(int i=0;i<n;i++)
{
if(i>0) b[i]+=b[i-1];
if((b[i]-i)%p==0)
{
flag=1;
break;
}
}
if(!flag) res.push_back(x);
}
printf("%d\n",res.size());
for(int i=0;i<res.size();i++)
printf("%d%c",res[i],i==res.size()-1?'\n':' ');
}
return 0;
}
E2 Asterism (Hard Version)
题意:
题意与E1相同,区别在于数据范围扩大了
(
2
≤
p
≤
n
≤
1
e
5
,
1
≤
a
i
≤
1
e
9
)
(2 \le p \le n \le 1e5,1 \le a_i \le 1e9)
(2≤p≤n≤1e5,1≤ai≤1e9)。
思路:
假设m是一个小于等于n的数,继承E1的思路可以推出,
F
(
x
)
F(x)
F(x)一定是m!乘上(n-m)个小于等于m的数得到的,于是如果
F
(
x
)
%
p
=
0
F(x)\%p=0
F(x)%p=0,那么
F
(
x
+
1
)
%
p
=
0
F(x+1)\%p=0
F(x+1)%p=0也一定成立(想一想,然后写几个例子就明白了)。那么就说明满足条件的
F
(
x
)
F(x)
F(x)一定是连续出现的,只要找到左右边界就可以直接输出了。左边界就是要对于任意的i满足
x
+
i
≥
a
i
x+i \ge a_i
x+i≥ai,则有
x
≥
a
i
−
i
x \ge a_i-i
x≥ai−i,即
x
m
i
n
=
max
(
a
i
−
i
)
x_{min}=\max(a_{i}-i)
xmin=max(ai−i)。对于xmax有从xmin到xmax都满足条件,从xmax+1到1e9都不满足条件,于是可以通过二分查找找出这个点,check的方法就可以参考上面E1的方法。
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<set>
#include<stack>
#include<cmath>
#include<string>
#define ll long long
#define ull unsigned long long
#define PI acos(-1.0)
#define pii pair<int,int>
#define fi first
#define se second
using namespace std;
const int N=1e5+100;
const int mod=1e9+7;
int n,p,a[N],b[N];
int main()
{
int T;
T=1;
// scanf("%d%*c",&T);
while(T--)
{
scanf("%d%d",&n,&p);
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
sort(a,a+n);
vector<int> res;
int mmin=a[0];
for(int i=1;i<n;i++)
mmin=max(mmin,a[i]-i);
int l=mmin,r=1e9,x;
while(l<r)
{
x=l+r>>1;
int flag=0;
memset(b,0,sizeof(b));
for(int i=0;i<n;i++)
{
if(a[i]<=x)
b[0]++;
else b[a[i]-x]++;
}
for(int i=0;i<n;i++)
{
if(i>0) b[i]+=b[i-1];
if((b[i]-i)%p==0)
{
flag=1;
break;
}
}
if(flag) r=x;
else l=x+1;
}
if(mmin>=r) printf("0\n");
else
{
printf("%d\n",r-mmin);
for(int i=mmin;i<r;i++)
printf("%d%c",i,i==r-1?'\n':' ');
}
}
return 0;
}
F Raging Thunder
题意:
有n个编号从1到n的传送带,每台输送机都有一个状态“<”或“>”。第i个传送带的初始状态取决给定的字符串的第i个字符。有n+1个编号从0到n的洞,第0号洞在1号传送带左边,其余所有洞i在传送带i的右边。
如果一个球在传送带i上,则它会按照如下规则移动。
如果传送带i的状态为"<":
1.如果i=1,球进入洞0;
2.如果传送带i-1状态为"<",球移动到传送带i-1;
3.如果传送带i-1状态为">",球进入洞i-1;
如果传送带i的状态为">":
1.如果i=n,球进入洞0;
2.如果传送带i+1状态为">",球移动到传送带i+1;
3.如果传送带i+1状态为"<",球进入洞i;
现在有n个询问,每个询问给出一个l,r,将区间[l,r]的传送带状态反转,然后在这些传送带上分别放一个球,问球最多的那个洞中的球的数量是多少,其中前面询问引起的传送带的反转会影响后面的询问,前面询问中的球不会累加到后面的询问中。
思路:
线段树分类讨论。这个问题的难点主要就在于区间合并和区间修改以及pushdown。
我的做法是将一段连续有关的区间看做一个块,如<<>><就可以看成<<和>><两个块。在线段树中记录最左块的长度,最右块的长度,最长中间块的长度(形如><的就叫中间块),最左块的朝向,最右块的朝向(向内走还是向外走),区间左右端点,区间是否朝向一致,区间块数量。
区间合并:
1.形如>|<
最长中间块=max(左右的中间块最大值,左区间最右块加右区间最左块)
如果左区间不全为>,则最左块=左区间最左块,否则最左块=左区间长度+右区间最左块长度
右区间同理
最左朝向=左区间最左朝向
最右朝向=右区间最右朝向
块数量=左块数量+右块数量-1
区间朝向肯定不一致,左右端点不用说了
2.形如>|>
如果右区间不全为>,则最长中间块=max(左右的中间块最大值,左区间最右块加右区间最左块),否则最长中间块=左右的中间块最大值
如果左区间不全为>,则最左块=左区间最左块,否则最左块=左区间长度+右区间最左块长度
如果右区间块数量=1,则最右块=左区间最右块+右区间长度,否则最右块=右区间最右块
最左朝向=左区间最左朝向
最右朝向=右区间最右朝向
块数量=左块数量+右块数量-1
左右区间朝向均一致则朝向一致,左右端点不用说了
3.形如<|<
与2同理
4.形如<|>
最简单的一种情况
最长中间块=左右的中间块最大值
最左块=左区间最左块
最右块=右区间最右块
最左朝向=左区间最左朝向
最右朝向=右区间最右朝向
块数量=左块数量+右块数量-1
区间朝向肯定不一致,左右端点不用说了
区间修改:
可以直接存正反两套信息,修改时就把两套信息互换即可,设一个lazy标记表示该区间是否需要反转,每次修改将指定区间的lazy异或1。
pushdown:
如果lazy标记为1则把该区间两套信息交换,再把标记向下传播,即把左右两个儿子的lazy标记异或1。
写的很麻烦,写着写着自己就晕了,肯定还有更简便的方法。
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<set>
#include<stack>
#include<cmath>
#include<string>
#define ll long long
#define ull unsigned long long
#define PI acos(-1.0)
#define pii pair<int,int>
#define fi first
#define se second
using namespace std;
const int N=5e5+100;
const int mod=1e9+7;
int n,q;
char a[N];
struct Node
{
int l,r,lazy,all[2],cnt[2],ln[2],rn[2],mn[2],fl[2],fr[2];
}tr[4*N];
void pushup(Node &u,Node &l,Node &r)
{
for(int i=0;i<=1;i++)
{
u.mn[i]=max(l.mn[i],r.mn[i]);
u.ln[i]=l.ln[i];
u.rn[i]=r.rn[i];
u.fl[i]=l.fl[i];
u.fr[i]=r.fr[i];
u.cnt[i]=l.cnt[i]+r.cnt[i];
//cout<<u.l<<' '<<u.r<<' '<<u.cnt[i]<<endl;
u.all[i]=0;
if(l.fr[i]&&r.fl[i])
{
u.all[i]=0;
u.cnt[i]--;
u.mn[i]=max(u.mn[i],l.rn[i]+r.ln[i]);
if(l.all[i]) u.ln[i]=l.rn[i]+r.ln[i];
if(r.all[i]) u.rn[i]=l.rn[i]+r.ln[i];
}
else if(l.fr[i]&&!r.fl[i])
{
u.cnt[i]--;
if(!r.all[i])
u.mn[i]=max(u.mn[i],l.rn[i]+r.ln[i]);
if(r.cnt[i]==1)
u.rn[i]=l.rn[i]+r.ln[i];
if(l.all[i]) u.ln[i]=l.rn[i]+r.ln[i];
if(l.all[i]&&r.all[i]) u.all[i]=1;
}
else if(!l.fr[i]&&r.fl[i])
{
u.cnt[i]--;
if(!l.all[i])
u.mn[i]=max(u.mn[i],l.rn[i]+r.ln[i]);
if(l.cnt[i]==1)
u.ln[i]=l.rn[i]+r.ln[i];
if(r.all[i]) u.rn[i]=l.rn[i]+r.ln[i];
if(l.all[i]&&r.all[i]) u.all[i]=1;
}
//cout<<"u:"<<u.l<<' '<<u.r<<' '<<u.ln[i]<<' '<<u.mn[i]<<' '<<u.rn[i]<<' '<<u.cnt[i]<<' '<<u.fl[i]<<' '<<u.fr[i]<<endl;
//cout<<"l:"<<l.l<<' '<<l.r<<' '<<l.ln[i]<<' '<<l.mn[i]<<' '<<l.rn[i]<<' '<<l.cnt[i]<<' '<<l.fl[i]<<' '<<l.fr[i]<<endl;
//cout<<"r:"<<r.l<<' '<<r.r<<' '<<r.ln[i]<<' '<<r.mn[i]<<' '<<r.rn[i]<<' '<<r.cnt[i]<<' '<<r.fl[i]<<' '<<r.fr[i]<<endl;
}
}
void pushup(int u)
{
pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void pushdown(int u)
{
if(tr[u].lazy)
{
tr[u].lazy=0;
tr[u<<1].lazy^=1;
tr[u<<1|1].lazy^=1;
swap(tr[u<<1].all[0],tr[u<<1].all[1]);
swap(tr[u<<1].cnt[0],tr[u<<1].cnt[1]);
swap(tr[u<<1].ln[0],tr[u<<1].ln[1]);
swap(tr[u<<1].rn[0],tr[u<<1].rn[1]);
swap(tr[u<<1].mn[0],tr[u<<1].mn[1]);
swap(tr[u<<1].fl[0],tr[u<<1].fl[1]);
swap(tr[u<<1].fr[0],tr[u<<1].fr[1]);
swap(tr[u<<1|1].all[0],tr[u<<1|1].all[1]);
swap(tr[u<<1|1].cnt[0],tr[u<<1|1].cnt[1]);
swap(tr[u<<1|1].ln[0],tr[u<<1|1].ln[1]);
swap(tr[u<<1|1].rn[0],tr[u<<1|1].rn[1]);
swap(tr[u<<1|1].mn[0],tr[u<<1|1].mn[1]);
swap(tr[u<<1|1].fl[0],tr[u<<1|1].fl[1]);
swap(tr[u<<1|1].fr[0],tr[u<<1|1].fr[1]);
}
}
void build(int u,int l,int r)
{
if(l==r)
{
tr[u]={l,r,0};
tr[u].all[0]=tr[u].all[1]=1;
tr[u].cnt[0]=tr[u].cnt[1]=1;
tr[u].ln[0]=tr[u].ln[1]=1;
tr[u].rn[0]=tr[u].rn[1]=1;
tr[u].mn[0]=tr[u].mn[1]=0;
if(a[l]=='<')
{
tr[u].fl[0]=1;
tr[u].fr[0]=0;
tr[u].fl[1]=0;
tr[u].fr[1]=1;
}
else
{
tr[u].fr[0]=1;
tr[u].fl[0]=0;
tr[u].fr[1]=0;
tr[u].fl[1]=1;
}
}
else
{
tr[u]={l,r,0};
int mid=l+r>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
pushup(u);
}
}
void modify(int u,int l,int r)
{
if(tr[u].l>=l&&tr[u].r<=r)
{
tr[u].lazy^=1;
swap(tr[u].all[0],tr[u].all[1]);
swap(tr[u].cnt[0],tr[u].cnt[1]);
swap(tr[u].ln[0],tr[u].ln[1]);
swap(tr[u].rn[0],tr[u].rn[1]);
swap(tr[u].mn[0],tr[u].mn[1]);
swap(tr[u].fl[0],tr[u].fl[1]);
swap(tr[u].fr[0],tr[u].fr[1]);
}
else
{
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid) modify(u<<1,l,r);
if(r>mid) modify(u<<1|1,l,r);
pushup(u);
}
}
Node query(int u,int l,int r)
{
if(tr[u].l>=l&&tr[u].r<=r) return tr[u];
else
{
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;
if(r<=mid) return query(u<<1,l,r);
else if(l>mid) return query(u<<1|1,l,r);
else
{
Node t1=query(u<<1,l,r);
Node t2=query(u<<1|1,l,r);
Node res={t1.l,t2.r,0};
pushup(res,t1,t2);
return res;
}
}
}
int main()
{
int T;
T=1;
// scanf("%d%*c",&T);
while(T--)
{
scanf("%d%d%*c",&n,&q);
scanf("%s",a+1);
build(1,1,n);
for(int i=1;i<=q;i++)
{
int l,r;
scanf("%d%d",&l,&r);
modify(1,l,r);
// for(int i=1;i<=9;i++)
// cout<<tr[i].ln[0]<<' '<<tr[i].mn[0]<<' '<<tr[i].rn[0]<<' '<<tr[i].cnt[0]<<endl;
Node t=query(1,l,r);
int res=t.mn[0];
if(t.fl[0])
res=max(res,t.ln[0]);
if(t.fr[0])
res=max(res,t.rn[0]);
printf("%d\n",res);
}
}
return 0;
}