长乐集训 Day2

A.butterfly

题意

给定两个长度为 $n$ 的序列 $a,b$。

集合 $S$ 初始为空,当前操作者选择 $1$ 到 $n$ 的不属于 $S$ 的整数 $x$,满足 $a_x\gt\max\limits_{i\in S}(a_i)$ 或 $b_x\gt\max\limits_{i\in S}(b_i)$,然后将 $x$ 加入 $S$,不能操作者输。

问先手是否必胜。若是则输出 YES,否则输出 NO

Solution

对于一个序列,如果 $\exists\ i\in [1,n],\forall j\in[1,n],a_i\gt a_j$ 且 $b_i\gt b_j$ 即 $a_i,b_i$ 分别为 $a,b$ 序列的最大值,则此时先手取 $i$ 必胜。若不存在这样的 $i$,那么容易发现对于 $a,b$ 两个序列的最大值,先取其一者必败。 递归下去,对于子序列是等价的,排序之后双指针模拟即可。

Code

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
    int res=0,flag=1;
    char ch=getchar();
    while(!isalnum(ch)) (ch=='-')?flag=-1:1,ch=getchar();
    while(isalnum(ch)) res=res*10+ch-'0',ch=getchar();
    return res*flag;
}
bool used[100010];
pair<int,int> a[100010],b[100010],val[100010];
string solve()
{
    memset(used,0,sizeof used);
    int n=read();
    for(int i=1;i<=n;i++)
    {
        a[i].first=read(),a[i].second=i;
        b[i].first=read(),b[i].second=i;
        val[i]=make_pair(a[i].first,b[i].first);
    }
    std::sort(a+1,a+n+1,[](pair<int,int> a,pair<int,int> b){return a.first>b.first;});
    std::sort(b+1,b+n+1,[](pair<int,int> a,pair<int,int> b){return a.first>b.first;});
    for(int i=1,j=1;i<=n&&j<=n;)
    {
        int nowa=a[i].first,nowb=b[j].first;
        while(a[i].first==nowa&&i<=n)
        {
            int id=a[i].second;
            used[id]=true;
            if(val[id].second==nowb)
                return "YES\n";
            i++;
        }
        while(b[j].first==nowb&&j<=n)
        {
            int id=b[j].second;
            used[id]=true;
            if(val[id].first==nowa)
                return "YES\n";
            j++;
        }
        while(used[a[i].second]==true&&i<=n) i++;
        while(used[b[j].second]==true&&j<=n) j++;
        if(i==n+1) return ((n-j)%2==0)?"YES\n":"NO\n";
        if(j==n+1) return ((n-i)%2==0)?"YES\n":"NO\n";
    }
    return "error";
}
int main(int argc,const char *argv[])
{
    freopen("butterfly.in","r",stdin);
    freopen("butterfly.out","w",stdout);
    int T=read();
    while(T--)
        cout<<solve();
    return 0;
}

B.cdq

题意

给定三个排列 $a,b,c$,求满足 $a_i\lt a_j,b_i\lt b_j,c_i\lt c_j$ 的有序对 $(i,j)$ 个数。

Solution

一眼 cdq 分治。然后发现出题人数据开到了 $2\times 10^6$,而 cdq 分治是 $\mathcal{O}(n\log^2n)$ 的。

考虑此题和普通的三位偏序有什么不同,显然要利用 $a,b,c$ 都是排列的性质。

分别对于 $a,b$ 和 $a,c$ 和 $b,c$ 做二维偏序,发现满足三维偏序的 $(i,j)$ 被计算了 $3$ 次,不满足三维偏序的 $(i,j)$ 被计算了 $1$ 次。故答案为$$ \dfrac{\Sigma_{i,j}[a_i\lt a_j][b_i\lt b_j]+\Sigma_{i,j}[a_i\lt a_j][c_i\lt c_j]+\Sigma_{i,j}[b_i\lt b_j][c_i\lt c_j]-\frac{n\times(n-1)}{2}}{2} $$ 用树状数组维护即可。

或者也可以考虑容斥,有:

  1. 满足 $a_i\lt a_j,b_i\lt b_j$ 的数对 $(i,j)$
  2. 满足 $a_i\lt a_j,c_i\lt c_j$ 的数对 $(i,j)$
  3. 满足 $b_i\lt b_j,c_i\lt c_j$ 的数对 $(i,j)$

设满足条件 $1,2,3$ 的数对数量分别为 $sum_1,sum_2,sum_3$,设全集为 $\mathcal{U}$,则有:$$ \mathcal{U}=sum_1+sum_2+sum_3-sum_{12}-sum_{13}-sum_{23}+sum_{123} $$ 容易发现,$sum_{12},sum_{13},sum_{23}$ 等价于 $sum_{123}$。故可以得出答案为上面那个式子。

Code

#include<bits/stdc++.h>
inline unsigned int read()
{
    unsigned int res=0,flag=1;
    char ch=getchar();
    while(!isalnum(ch)) (ch=='-')?flag=-1:1,ch=getchar();
    while(isalnum(ch)) res=res*10+ch-'0',ch=getchar();
    return res*flag;
}
struct data
{
    int a,b,c;
};
struct data tmp[2000010];
int n;
long long ans=0;
int data[2000010];
int a[2000010],b[2000010],c[2000010];
unsigned int seedA,seedB,seedC;
unsigned int Rand()
{
    seedA^=seedA<<16;
    seedA^=seedA>>5;
    seedA^=seedA<<1;
    unsigned int tmp=seedA;
    seedA=seedB,seedB=seedC;
    seedC^=tmp^seedA;
    return seedC;
}
void init()
{
    seedA=read(),seedB=read(),seedC=read();
    for(int i=1;i<=n;i++) a[i]=b[i]=c[i]=i;
    for(int i=1;i<=n;i++) std::swap(a[i],a[1+Rand()%n]);
    for(int i=1;i<=n;i++) std::swap(b[i],b[1+Rand()%n]);
    for(int i=1;i<=n;i++) std::swap(c[i],c[1+Rand()%n]);
    return ;
}
inline int lowbit(int x)
{
    return x&(-x);
}
void modify(int pos)
{
    for(int i=pos;i<=n;i+=lowbit(i))
        data[i]++;
    return ;
}
int query(int pos)
{
    int res=0;
    for(int i=pos;i>0;i-=lowbit(i))
        res+=data[i];
    return res;
}
int main(int argc,const char *argv[])
{
    freopen("cdq.in","r",stdin);
    freopen("cdq.out","w",stdout);
    n=read();
    init();
    for(int i=1;i<=n;i++)
        tmp[i]={a[i],b[i],c[i]};
    std::sort(tmp+1,tmp+n+1,[](struct data a,struct data b){return a.a<b.a;});
    for(int i=1;i<=n;i++) ans+=query(tmp[i].b),modify(tmp[i].b);
    memset(data,0,sizeof data);
    for(int i=1;i<=n;i++) ans+=query(tmp[i].c),modify(tmp[i].c);
    memset(data,0,sizeof data);
    std::sort(tmp+1,tmp+n+1,[](struct data a,struct data b){return a.b<b.b;});
    for(int i=1;i<=n;i++) ans+=query(tmp[i].c),modify(tmp[i].c);
    printf("%lld",(ans-(long long)n*(n-1)/2ll)>>1);
    return 0;
}

C.seq

题意

给定一个长度为 $n$ 的数列。可以把这段数列切成几段数列,然后重新将各段数列拼接成新的长度为 $n$ 的数列(拼接过程中不可以将数列翻转)。求最少切分次数,使得拼接成的新数列是单调不降的数列。

Solution

首先把每个数都看作一段,把最小化切开次数转化为最大化合并次数。连续一段相同的数显然是可以合并起来的,下面默认序列中没有相邻的相同元素。

一个段内的元素是单调递增的且是一段连续的值域,出现多次的元素不能处于一个段的中间(除去第一个和最后一个),相同值的元素最多只能有一个参与一次合并。

于是考虑按值域设计状态 $f_{i,j}$ 为考虑了值域 $1$ 到 $i$ 的元素,最后一次合并操作是合并 $(j-1,j)$。

转移是 $f_{i,k} \gets f_{i-1,j}+1$,转移条件是 $a_k=i$ 且 $a_{k-1}=i-1$,若 $j=k-1$ 还要满足 $j$ 在序列中出现次数为 $1$。

至此时间复杂度 $\mathcal{O}(n^2)$。

考虑优化,不难发现对于一个 $i$,我们只需要保存 DP 值最大的和次大的两个 $j$。若最大值的位置能转移,就从最大值转移;否则就从次大值转移。

时间复杂度 $\mathcal{O}(n)$。

Code

D.一统天下

题意

Solution

Code

result

A 题傻逼了写了两个小时,B 题先想到三维偏序然后发现容斥可以降掉一个 log,预计得分 100+100+0+0=200pts。

实际得分 100+100+0+0=200pts rk1。

posted @ 2023-11-07 20:07  Che_001  阅读(18)  评论(0)    收藏  举报  来源