Codeforces Round #654 (Div. 2) E F

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) (2pn2000,1ai2000),其中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+iai,对这个式子变形就可以得到 i ≥ a i − x i \ge a_i-x iaix,也就是说,在满足 x + i ≥ a i x+i \ge a_i x+iai,即能够成功进行到第i次决定的条件下,原本i位置上的这个数字是可以变动到 a i − x a_i-x aix到i-1这些位置上去的。于是就可以开一个数组b来记录每个位置上的能够出现的数字的数目,最后符合要求的排列总数就是 ∏ b i \prod b_i bi
  显然当 x ≥ max ⁡ ( a i ) x \ge \max(a_i) xmax(ai)时, F ( x ) = n ! F(x)=n! F(x)=n!,由于 p ≤ n p \le n pn,此时 F ( x ) % p = 0 F(x)\%p=0 F(x)%p=0必然成立。由于ai的范围不大(1 ≤ a i ≤ 2000 \le a_i \le 2000 ai2000),于是可以直接暴力枚举x(1 ≤ x ≤ 2000 \le x \le 2000 x2000)来求每个 F ( x ) F(x) F(x)。先对ai序列按升序排序,从前往后遍历,如果不满足 x + i ≥ a i x+i \ge a_i x+iai则说明 F ( x ) = 0 F(x) =0 F(x)=0,即不存在一个有效序列,自然也不可能与p互质,直接看x+1。对于每个i,求出 a i − x a_i-x aix,在 b [ a i − x ] b[a_i-x] b[aix]上加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
#include
#include
#include
#include
#include
#include
#include
#include
#define ll long long
#define ull unsigned long long
#define PI acos(-1.0)
#define pii pair
#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 res;
        for(int x=1;x<=2000;x++)
        {
            int flag=0;
            memset(b,0,sizeof(b));
            for(int i=0;i=n)
                    {
                        flag=1;
                        break;
                    }
                    else b[a[i]-x]++;
                }
            }
            if(flag) continue;
            for(int i=0;i0) 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

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) (2pn1e5,1ai1e9)

思路:
  假设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+iai,则有 x ≥ a i − i x \ge a_i-i xaii,即 x m i n = max ⁡ ( a i − i ) x_{min}=\max(a_{i}-i) xmin=maxaii。对于xmax有从xmin到xmax都满足条件,从xmax+1到1e9都不满足条件,于是可以通过二分查找找出这个点,check的方法就可以参考上面E1的方法。

代码:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ll long long
#define ull unsigned long long
#define PI acos(-1.0)
#define pii pair
#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 res;
        int mmin=a[0];
        for(int i=1;i>1;
            int flag=0;
            memset(b,0,sizeof(b));
            for(int i=0;i0) 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

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
#include
#include
#include
#include
#include
#include
#include
#include
#define ll long long
#define ull unsigned long long
#define PI acos(-1.0)
#define pii pair
#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<>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<

你可能感兴趣的:(codeforces,线段树,逻辑,思维,数据结构)