NOIP提高组模拟赛13


A. 送花

考虑选择一段以\(i\)为结尾的区间,记录\(las[i]\)为上一个与\(c[i]\)颜色相同的花的位置,发现只有在\(las[i]+1-i\)这个区间才会有\(d[c[i]]\)的贡献,而之前有贡献的\(pos[pos[i]]+1-pos[i]\),没有了\(d[c[i]]\)的贡献,用线段树维护区间最值,支持区间修改即可

code
#include 
#include 
using namespace std;
const int maxn=1000005;
long long max(long long x,long long y){return x>y?x:y;}
int n,m,c[maxn],d[maxn],pos[maxn],las[maxn];

struct tree{
    struct node{
        long long max,tag;
    };
    node t[maxn<<2|1];
    void push_down(int x){
        t[x<<1].tag+=t[x].tag;
        t[x<<1|1].tag+=t[x].tag;
        t[x<<1].max+=t[x].tag;
        t[x<<1|1].max+=t[x].tag;
        t[x].tag=0;
    }
    
    void push_up(int x){
        t[x].max=max(t[x<<1].max,t[x<<1|1].max);
    }
    
    void modify(int x,int l,int r,int L,int R,long long dt){
        if(L<=l&&r<=R){
            t[x].max+=dt;
            t[x].tag+=dt;
            return;
        }
        if(t[x].tag)push_down(x);
        int mid=(l+r)>>1;
        if(L<=mid)modify(x<<1,l,mid,L,R,dt);
        if(R>mid)modify(x<<1|1,mid+1,r,L,R,dt);
        push_up(x);
    }

    long long query(int x,int l,int r,int L,int R){
        if(L<=l&&r<=R)return t[x].max;
        if(t[x].tag)push_down(x);
        int mid=(l+r)>>1;
        long long ans=0;
        if(L<=mid)ans=query(x<<1,l,mid,L,R);
        if(R>mid)ans=max(ans,query(x<<1|1,mid+1,r,L,R));
        return ans;
    }
}T;
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)scanf("%d",&c[i]);
    for(int i=1;i<=m;++i)scanf("%d",&d[i]);
    for(int i=1;i<=n;++i){pos[i]=las[c[i]];las[c[i]]=i;}
    long long ans=0;
    for(int i=1;i<=n;++i){
        T.modify(1,1,n,pos[i]+1,i,d[c[i]]);
        if(pos[i])T.modify(1,1,n,pos[pos[i]]+1,pos[i],-d[c[i]]);
        ans=max(ans,T.query(1,1,n,1,n));
    }
    printf("%lld\n",ans);
    return 0;
}

B. 星空

发现在\(y=x+k\)\(y=-x+k\)这条直线上的点直接距离为0,两点直接距离可以是一个点向另一个点的那两条直线作垂线的垂线段长度,考场想到这里就想不下去了,最后还是打的暴力最短路。。

旋转坐标轴,为\(y=x\)\(y=-x\),计算新的坐标\((x-y,x+y)\)

image

两点的直接距离就变成\(min(abs(x_x-x_y),abs(y_x-y_y))\)

距离为0的点可以看做一个集合,两个集合点对数就是两个集合\(size\)相乘,两个集合中任意两点最短距离就是集合中直接距离最小的两个点的距离

并查集维护集合,距离不为零尝试更新最短距离

算出最短距离后枚举为最短距离的两点,找到他们所在的集合,记录

对所有记录的两个集合去重,统计答案即可

code
#include 
#include 
#include 
using namespace std;
const int maxn=100005;
int abs(int x){return x<0?-x:x;}
int n;
struct node{
    int x,y;
}d[maxn];
struct st{
    int f[maxn],size[maxn];
    int fa(int x){if(f[x]!=x)return f[x]=fa(f[x]);return x;}
    void hb(int x,int y){x=fa(x);y=fa(y);if(x!=y){f[x]=y;size[y]+=size[x];size[x]=0;}}
    int gs(int x,int y){x=size[fa(x)];y=size[fa(y)];return x*y;}
}s;
int p1[maxn],p2[maxn];
bool cmpx(int x,int y){return d[x].x shuzu[maxn << 2];
int cnt;
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;++i)s.size[i]=1;
    for(int i=1;i<=n;++i)s.f[i]=i;
    for(int i=1;i<=n;++i)scanf("%d%d",&d[i].x,&d[i].y);
    for(int i=1;i<=n;++i){
        node ls;
        ls.x=d[i].x+d[i].y;
        ls.y=d[i].x-d[i].y;
        d[i]=ls;
    }
    for(int i=1;i<=n;++i)p1[i]=i;
	for(int i=1;i<=n;++i)p2[i]=i;
    sort(p1+1,p1+n+1,cmpx);
	sort(p2+1,p2+n+1,cmpy);
    for(int i=2;i<=n;++i)
		if(d[p1[i]].x==d[p1[i-1]].x)
			s.hb(p1[i],p1[i-1]);
    for(int i=2;i<=n;++i)
		if(d[p2[i]].y==d[p2[i-1]].y)
			s.hb(p2[i],p2[i-1]);
    int ans1=-1;
    for(int i=2;i<=n;++i){
        if(d[p1[i]].x!=d[p1[i-1]].x)
            if(d[p1[i]].x-d[p1[i-1]].xrp)swap(lp,rp);
        shuzu[++cnt] = make_pair(lp, rp);
    }
    for(int i=2;i<=n;++i){
        if(d[p2[i]].y-d[p2[i-1]].y!=ans1)continue;
      	int lp=s.fa(p2[i]),rp=s.fa(p2[i-1]);
      	if(lp>rp)swap(lp,rp);
        shuzu[++cnt] = make_pair(lp, rp);
    }
    int ans2=0;
    sort(shuzu + 1, shuzu + cnt + 1);
    cnt=unique(shuzu + 1,shuzu + cnt + 1) - shuzu - 1;
    for(int i=1;i<=cnt;++i){
    	ans2+=s.gs(shuzu[i].first,shuzu[i].second);
	}
    printf("%d\n",ans2);
    return 0;
}


C. 零一串

很妙的一道题。。。

考虑第\(i\)个1如果能在第\(t\)轮向前一步,那么第\(i+1\)个1最早在\(t+1\)轮向前一步

生成一个序列表示第\(i\)个1在第\(j\)轮是否能向前走一步

因为上面所说的性质,那么初始序列为\(i-1\)的序列右移一位并且在开头补0

\(i-1\)个1和第\(i\)个1之间有\(x\)个0,就把操作序列的前\(x\)个0改成1,解释一下大概是,当前这个1需要多走\(x\)步才能赶上前面的那个1,然后跟在前面那个1后面走

关于逆序对,可以发现第\(x\)轮的操作序列有几个1那么逆序对个数就相对上一轮加几,而分开考虑每个1,他的贡献是一个区间,那么差分维护就可以了

双端队列维护操作序列中0的位置,每加入一个1找出他对逆序对有贡献的区间,维护差分,最后对这个差分的前缀和进行前缀和可以得到该轮的逆序对数

实现不难,但是思路就难想到了

code
#include 
#include 
#include 
using namespace std;
const int maxn=5000005;
const int mod=998244353;
const int base=233;
char c[maxn];
int n,T,fr[maxn];
long long cf[maxn],nxd,gs1;
dequeq;
void pre_work(){
    for(int i=1;i<=T;++i)q.push_back(i);
    int ls=0;for(int i=n;i>=1;--i)if(c[i]=='0')++ls;else nxd+=ls,++gs1;
}
void work(){
   int ls=0,ll=0;
   for(int i=1;i<=n;++i)
       if(c[i]=='1'){
           ++ls;q.push_front(1-ls);
           while(!q.empty()&&q.back()+ls>T)q.pop_back();
           for(int j=1;j<=ll;++j){
               if(q.empty())break;
                int x=q.front();
                if(x+ls

相关