『模拟赛』CSP-S模拟2

Rank

非常好数据,使我成为 Rank1(雾

image

数据换源后的

image

狂流——齐秦

北风在吹着清冷的街道
街灯在拉开长长的影子
走过的路 想过的事
仿佛越来越远越来越长
越来越多越难以抛开
多少平淡日子以来的夜晚
你曾是我渴望拥有的企盼
太多分手的记忆
仿佛越来越远越来越长
越来越多越难以抛开
没有人能挽回时间的狂流
没有人能誓言相许永不分离
是我的错
是你错过 喔...
没有人能挽回时间的狂流
没有人能了解聚散之间的定义
太多遗憾 太多伤感
留在心中 像一道狂流
没有人能挽回时间的狂流
没有人能誓言相许永不分离
是我的错
是你错过 喔...
没有人能挽回时间的狂流
没有人能了解聚散之间的定义
太多遗憾 太多伤感
留在心中 像一道狂流
多少平淡日子以来的夜晚
你曾是我渴望拥有的企盼
太多分手的记忆
仿佛越来越远越来越长
越难以抛开
没有人
没有人
没人了解
没人了解
没有人
没有人
没有人
没有人
北风在吹着冰冷的街道

Upd on 9.8 9:00 :更新了 T3 正解


A. 不相邻集合

签。

用 set 维护的,不仅跑得慢讲的时候还被 hack 了。

考虑正解,并查集维护,我们将当前状态下的序列中连续的数合并在一起,设全部集合为 \(T\),大小分别为 \(s_i\),则显然有 \(ans=\sum_{i\in T} \lceil {\frac{s_i}{2}} \rceil\)。从 \(1\)\(n\) 遍历时遇到已经有过的元素直接跳过,否则标记并判断相邻元素是否出现过,是则合并二者。合并操作只要更新 \(fa\) 数组,然后先减去二者的贡献再加上合并后的贡献即可。

点击查看代码
#include<bits/stdc++.h>
#define fo(x,y,z) for(register int (x)=(y);(x)<=(z);(x)++)
#define fu(x,y,z) for(register int (x)=(y);(x)>=(z);(x)--)
using namespace std;
typedef long long ll;
#define lx ll
inline lx qr()
{
	char ch=getchar();lx x=0,f=1;
	for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<3)+(x<<1)+(ch^48);
	return x*f;
}
#undef lx
#define qr qr()
#define fi first
#define se second
const int Ratio=0;
const int N=3e5+5;
const int mod=1e9+7;
const int inf=1e9;
int n,maxn,ans;
int a[N],fx[N<<1],siz[N<<1];
bool yz[N<<1];
namespace Wisadel
{
    int Wfind(int x)
    {
        if(x==fx[x]) return x;
        return fx[x]=Wfind(fx[x]);
    }
    short main()
    {
        // freopen(".in","r",stdin),freopen(".out","w",stdout);
        n=qr;
        fo(i,1,n) a[i]=qr;
        fo(i,1,500000) fx[i]=i,siz[i]=1;
        fo(i,1,n)
        {
            if(!yz[a[i]])
            {
                yz[a[i]]=1;
                ans++;
                if(yz[a[i]-1])
                {
                    int _=Wfind(a[i]),__=Wfind(a[i]-1);
                    if(_!=__)
                    {
                        fx[_]=__;
                        ans-=(siz[__]+1)/2+(siz[_]+1)/2;
                        siz[__]+=siz[_];
                        ans+=(siz[__]+1)/2;
                    }
                }
                if(yz[a[i]+1])
                {
                    int _=Wfind(a[i]),__=Wfind(a[i]+1);
                    if(_!=__)
                    {
                        fx[_]=__;
                        ans-=(siz[__]+1)/2+(siz[_]+1)/2;
                        siz[__]+=siz[_];
                        ans+=(siz[__]+1)/2;
                    }
                }
            }
            printf("%d ",ans);
        }
        return Ratio;
    }
}
int main(){return Wisadel::main();}

B. 线段树

算是签,赛时思路跟正解压根不沾边,只有 20pts。

正解是记搜。一个显然的结论:长度相同的区间的子树标号和是有共性的,即若当前区间的编号为 \(x\),则子树编号和都能写作 \(kx+b\) 的形式。那么就能得出做法:正常区间求和,到目标区间返回该区间对应的 \(kx+b\) 值,若未求过该长度的 \(k\)\(b\) 就记搜搜一下就行。

点击查看代码
#include<bits/stdc++.h>
#define fo(x,y,z) for(register int (x)=(y);(x)<=(z);(x)++)
#define fu(x,y,z) for(register int (x)=(y);(x)>=(z);(x)--)
using namespace std;
typedef long long ll;
#define lx ll
inline lx qr()
{
	char ch=getchar();lx x=0,f=1;
	for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<3)+(x<<1)+(ch^48);
	return x*f;
}
#undef lx
#define qr qr()
#define fi first
#define se second
const int Ratio=0;
const int N=3e5+5;
const int mod=1e9+7;
const int inf=1e9;
ll n,x,y;
unordered_map<ll,ll>k,b;
namespace Wisadel
{
    #define ls (rt<<1)
    #define rs (rt<<1|1)
    #define mid ((l+r)>>1)
    ll Wqk(ll len)
    {
        if(k.find(len)!=k.end()) return k[len];
        int mi=(len+1)>>1;
        return k[len]=(2*Wqk(mi)+2*Wqk(len-mi)+1)%mod;
    }
    ll Wqb(ll len)
    {
        if(b.find(len)!=b.end()) return b[len];
        int mi=(len+1)>>1;
        return b[len]=(Wqb(mi)+Wqb(len-mi)+Wqk(len-mi))%mod;
    }
    ll Wq(ll rt,ll l,ll r,ll x,ll y)
    {
        if(x<=l&&r<=y) return (rt%mod*Wqk(r-l+1)%mod+Wqb(r-l+1))%mod;
        ll res=0;
        if(x<=mid) res=Wq(ls,l,mid,x,y);
        if(y>mid) res=(res+Wq(rs,mid+1,r,x,y))%mod;
        return res;
    }
    short main()
    {
        // freopen(".in","r",stdin),freopen(".out","w",stdout);
        int T=qr;
        k[1]=1,b[1]=0;
        while(T--)
        {
            n=qr,x=qr,y=qr;
            printf("%lld\n",Wq(1,1,n,x,y));
        }
        return Ratio;
    }
}
int main(){return Wisadel::main();}

C. 魔法师

神秘题,数据更神秘。

由于赛时被 T2 卡了太久,一眼看出这题不太可做,于是打了 \(n^2\) 的 30pts 暴力和 20pts 无删的部分分,然后翻了翻大样例,一看全是 \(0\),也没咋细研究,就把剩下不会的部分全输出 \(0\),然后喜提 25pts,由于数据出锅打的暴力分只有 15pts。

考虑正解。我们发现带一个 \(\max\) 非常不好转移或维护,所以用分讨将其去掉。设法杖为 \(p\),咒语为 \(q\),若有 \(a_p+a_q\lt b_p+b_q\),则有 \(a_p-b_p\lt b_q-a_q\)。因此我们记 \(u_p=a_p-b_p\)\(u_q=b_q-a_q\),则 \(a_p+a_q\)\(b_p+b_q\) 的大小就取决于 \(u_p\)\(u_q\) 的大小。

学习了 GGrun 用 multiset 模拟开四棵线段树的逃课做法正解。插入时因为 \(u\) 的值不确定正负,为保证其下标在线段树中为正数,因为最小的 \(u\) 不小于 \(-2.5\times 10^5\),所以值域开 \(5\times 10^5\) 即可。然后插入就是简单的 insert,删除用 lower_bound 搜一下然后删就行,最小值直接取 *s.begin(),然后更新答案就行了。代码极其好写好理解。

不过代价是时间空间不够优秀,但是拼接 50pts 暴力可以拿到目前能看到的最优解 5854ms。

点击查看代码
#include<bits/stdc++.h>
#define fo(x,y,z) for(register int (x)=(y);(x)<=(z);(x)++)
#define fu(x,y,z) for(register int (x)=(y);(x)>=(z);(x)--)
using namespace std;
typedef long long ll;
#define lx ll
inline lx qr()
{
	char ch=getchar();lx x=0,f=1;
	for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<3)+(x<<1)+(ch^48);
	return x*f;
}
#undef lx
#define qr qr()
#define fi first
#define se second
const int Ratio=0;
const int N=1e6+2,B=25e4;
const int mod=1e9+7;
const int inf=1e9;
int q,T,ans;
namespace Wisadel
{
    int sum[N][2][2],anss[N];
    multiset<int> s[N][2][2];
    multiset<int>::iterator it;
    #define ls (rt<<1)
    #define rs (rt<<1|1)
    #define mid ((l+r)>>1)
    void Wpushup(int rt)
    {
        sum[rt][0][0]=min(sum[ls][0][0],sum[rs][0][0]);
        sum[rt][0][1]=min(sum[ls][0][1],sum[rs][0][1]);
        sum[rt][1][0]=min(sum[ls][1][0],sum[rs][1][0]);
        sum[rt][1][1]=min(sum[ls][1][1],sum[rs][1][1]);
        anss[rt]=min(min(anss[ls],anss[rs]),min(sum[ls][0][1]+sum[rs][1][1],sum[ls][1][0]+sum[rs][0][0]));
    }
    void Wupd(int rt,int l,int r,int x,int va,int vb,int op)
    {
        if(l==r)
        {
            if(!s[l][0][0].size()) s[l][0][0].insert(1e8),s[l][0][1].insert(1e8),s[l][1][0].insert(1e8),s[l][1][1].insert(1e8);
            if(op<0)
            {
                it=s[l][-op-1][0].lower_bound(va);
                if(*it==va) s[l][-op-1][0].erase(it);
                it=s[l][-op-1][1].lower_bound(vb);
                if(*it==vb) s[l][-op-1][1].erase(it);
            }
            else s[l][op-1][0].insert(va),s[l][op-1][1].insert(vb);
            sum[rt][0][0]=*s[l][0][0].begin(),sum[rt][0][1]=*s[l][0][1].begin(),
            sum[rt][1][0]=*s[l][1][0].begin(),sum[rt][1][1]=*s[l][1][1].begin();
            anss[rt]=min(sum[rt][0][0]+sum[rt][1][0],sum[rt][0][1]+sum[rt][1][1]);
            return;
        }
        if(x<=mid) Wupd(ls,l,mid,x,va,vb,op);
        else Wupd(rs,mid+1,r,x,va,vb,op);
        Wpushup(rt);
    }
    short main()
    {
        // freopen(".in","r",stdin),freopen(".out","w",stdout);
        q=qr,T=qr;
        memset(sum,0x3f,sizeof sum);
        memset(anss,0x3f,sizeof anss);
        fo(i,1,q)
        {
            int op=qr,tt=qr,aa=qr,bb=qr;
            if(T) aa^=ans,bb^=ans;
            if(op==1)
            {
                int u=tt?bb-aa:aa-bb;
                Wupd(1,1,5e5,u+B,aa,bb,tt+1);
                ans=anss[1]>1e6?0:anss[1];
                printf("%d\n",ans);
            }
            else
            {
                int u=tt?bb-aa:aa-bb;
                Wupd(1,1,5e5,u+B,aa,bb,-tt-1);
                ans=anss[1]>1e6?0:anss[1];
                printf("%d\n",ans);
            }
        }
        return Ratio;
    }
}
int main(){return Wisadel::main();}

D. 园艺

一眼可做,再一眼不可做,再再一眼可做。

根据样例得出一个假结论:最优解在整个过程非边界处最多拐一次。然后 5min 写出了式子,10min 打出代码。过一会发现暴力 \(d_i=1\) 和上面结论对不上,在大样例的信心加持下选择了相信假结论,然后就过了。

\(dis_i\)\(i\) 距起点的距离,\(sum_i\) 为起点到 \(i\) 的路径上的点的距离和。

\(t\) 为枚举的拐点,以 \(t\lt k\) 举例,则答案为:

\[sum_t+sum_n+2dis_t\left(n-k\right)+sum_1-sum_t+2\left(dis_t+dis_n\right)\left(t-1\right) \]

\(t\gt k\) 同理,换一下位置就行。

过了之后尝试证明了一下结论,发现如果存在:

\[2dis_{t_1}\left(n-k\right)>2dis_{t_2}\left(k-t_1\right)+\left(3dis_{t_2}+2dis_{t_1}\right)\left(n-t_2\right)+\left(dis_{t_1}+2dis_{t_2}\right)\left(t_1-1\right) \]

那么会出现拐两次比拐一次优的情况。但是一眼左边一小点右边一大坨,本能告诉我结论是正确的的,然后水水的数据也是这样的,于是过了。

下午 int_R 送来一组 hack 数据:

5 3
100000 10 100 1000000

又研究了一下,发现如果满足:

\[dis_{t_1}\left(2t_2-2k-t_1+1\right)>dis_{t_2}\left(2k+3n-3t_2-2\right) \]

即可卡掉,看着很复杂,其实手造还是很容易卡掉的。

错误的满分代码(真的很短)
#include<bits/stdc++.h>
#define fo(x,y,z) for(register int (x)=(y);(x)<=(z);(x)++)
#define fu(x,y,z) for(register int (x)=(y);(x)>=(z);(x)--)
using namespace std;
typedef long long ll;
#define lx ll
inline lx qr()
{
	char ch=getchar();lx x=0,f=1;
	for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<3)+(x<<1)+(ch^48);
	return x*f;
}
#undef lx
#define qr qr()
#define fi first
#define se second
const int Ratio=0;
const int N=2e6+5;
const int mod=1e9+7;
const int inf=1e9;
int n,k;
ll d[N];
ll ans9223372036854775807,dis[N],sum[N];
namespace Wisadel
{
    short main()
    {
        // freopen("cut2.in","r",stdin),freopen(".out","w",stdout);
        n=qr,k=qr;
        fo(i,1,n-1) d[i]=qr;
        fu(i,k-1,1) dis[i]=dis[i+1]+d[i],sum[i]=sum[i+1]+dis[i];
        fo(i,k+1,n) dis[i]=dis[i-1]+d[i-1],sum[i]=sum[i-1]+dis[i];
        fu(i,k-1,1) ans=min(ans,sum[i]+sum[n]+(n-k)*2*dis[i]+sum[1]-sum[i]+2*(dis[i]+dis[n])*(i-1));
        fo(i,k+1,n) ans=min(ans,sum[i]+sum[1]+(k-1)*2*dis[i]+sum[n]-sum[i]+2*(dis[i]+dis[1])*(n-i));
        printf("%lld\n",ans);
        return Ratio;
    }
}
int main(){return Wisadel::main();}

Rank 1拿得挺意外的。本来卡 T2 1.5h 觉得自己跟昨天一样又寄寄了,结果神秘数据直接给我错解全放过了,挺牛的。

那么也反映一个问题,数据强的话又能打多少分呢?所以能过的题不抓住还是挺伤的,比如 T2。

T4 说实话开始猜结论时预期就拿个 50pts 左右,过了大样例给我震惊了,一时间以为把正解按出来了,最后倒计时结束时看到两个绿勾还是很高兴的。

就把这次当做一次激励吧,激励我继续冲击 Rank1 的助推剂,也希望能在这最后纯粹的 OI 时光里留下点什么拼搏的回忆。

Upd on 数据换源后:T3 起码该拿的拿到了,虽然是 Rank2,但起码知道了自己没挂分不是吗?

还有 %%% GGrun。


完结撒花~

posted @ 2024-09-08 08:58  DrRatio  阅读(51)  评论(10编辑  收藏  举报