海亮寄 7.12

前言

业精于勤荒于嬉,行成于思毁于随

正文(模拟赛)

卦象:吉

(然而模拟赛进程并不吉利,分数还没有破百,还不如去年国庆的水平呢)

感受:难受啊,深深的无力感,就有一种啥都会但又啥都不会的无力感。一开始扫了一圈所有题目,T1 口胡了一个广搜做法,T2 疑似先拆绝对值再拆 \(\min\),T3 可能会静态问题,搞一个容斥,动态问题观测到不带修,以及数据范围很灵性,可能是根号相关(具体来说是莫队?),T4 大概扫了一眼直接放弃,着手准备暴力分数。本来规划的是一个小时搓 T1,一个半到两个小时搓 T3,然后看剩余时间搓 T2,T4 暴力可以作为换脑子的广告环节,反正 10min 内肯定能写完。规划非常清晰,但却没有正确评估自己的能力。变故出在了 T1 上——完全没想到一个普通的广搜写了两个半小时,然后只写了 30pts 的数据点,难受。最后无奈选择打掉 T2、T3、T4 的暴力。回过头来写 T1 的 n=3 的数据,未果,最后获得 75/400 的优秀成绩,接近北中倒数第一咯!也没有什么可以总结的,T2 想的 CDQ 复杂度赛后说会被卡,T3 想到正解但也没有时间了,T4 赛后还是不会。但 T3 的分数是应该拿到的,所以只能加训一下广搜,决定择日重写高效进阶的广搜部分

题解链接

(已破防,勿扰)

T1

人口普查,然而自己菜到了不是人

观测到一些密码锁具体数值并不重要,重要的只有差值,所以可以简单做

隔壁的 dalao 说可以先爆搜是 \(+/- 1\) 情况,然后控制一下上下界 \(+ / -\) 的操作次数上界,最后转化为 这个题目

讲题时还有人提出,可以区间的 \(+/- 1\) 可以转化为差分数组上的操作,然后最小化操作次数直接就上点什么最优化问题的经典策略即可

(感觉自己的广搜还是实现的太劣了,除了单次查询 \(O(1)\) 没有任何优势)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5,M=5e5+5;
int n,Q,head[N],tot,a[5],b[5],c[5];
string x,y;
struct Edge{int to,nxt;}e[M];
int dis[N],ans[N][N];bool vis[N];
inline void add(int u,int v){
    e[++tot]={v,head[u]};head[u]=tot;
    return;
}
inline void make2(){
    for(int i=0;i<=99;i++){
        int a=i%10,b=i/10%10;
        int v1=(a+1)%10,v2=(b+1)%10;
        add(i,b*10+v1),add(i,v2*10+a),add(i,v2*10+v1);
        v1=(a+9)%10,v2=(b+9)%10;
        add(i,b*10+v1),add(i,v2*10+a),add(i,v2*10+v1);
    }
    return;
}
inline void bfs2(int S){
    for(int i=0;i<=99;i++)dis[i]=0,vis[i]=false;
    queue<int> q;q.push(S);dis[S]=0;vis[S]=true;
    while(!q.empty()){
        int u=q.front();q.pop();        
        // cerr<<"Zyx "<<u<<' '<<head[u]<<endl;
        for(int i=head[u];~i;i=e[i].nxt){
            int v=e[i].to;
            if(vis[v])continue;
            // cerr<<u<<' '<<v<<endl;
            // cerr<<"-----------"<<endl;
            dis[v]=dis[u]+1;vis[v]=true;q.push(v);
        }
    }
    return;
}
inline void make3(){
    for(int i=0;i<=999;i++){
        int a=i%10,b=i/10%10,c=i/100%10;
        int v1=(a+1)%10,v2=(b+1)%10,v3=(c+1)%10;
        add(i,c*100+b*10+v1),add(i,c*100+v2*10+a),add(i,v3*100+b*10+a);
		add(i,c*100+v2*10+v1),add(i,v3*100+v2*10+a),add(i,v3*100+v2*10+v1);
        v1=(a+9)%10,v2=(b+9)%10,v3=(c+9)%10;
		add(i,c*100+b*10+v1),add(i,c*100+v2*10+a),add(i,v3*100+b*10+a);
		add(i,c*100+v2*10+v1),add(i,v3*100+v2*10+a),add(i,v3*100+v2*10+v1);
    }
    return;
}
inline void bfs3(int S){
    for(int i=0;i<=999;i++)dis[i]=0,vis[i]=false;
    queue<int> q;q.push(S);dis[S]=0;vis[S]=true;
    while(!q.empty()){
        int u=q.front();q.pop();        
        // cerr<<"Zyx "<<u<<' '<<head[u]<<endl;
        for(int i=head[u];~i;i=e[i].nxt){
            int v=e[i].to;
            if(vis[v])continue;
            // cerr<<u<<' '<<v<<endl;
            // cerr<<"-----------"<<endl;
            dis[v]=dis[u]+1;vis[v]=true;q.push(v);
        }
    }
    return;
}
inline void make4(){
    for(int i=0;i<=9999;i++){
        int a=i%10,b=i/10%10,c=i/100%10,d=i/1000%10;
        int v1=(a+1)%10,v2=(b+1)%10,v3=(c+1)%10,v4=(d+1)%10;
        add(i,d*1000+c*100+b*10+v1),add(i,d*1000+c*100+v2*10+a);
		add(i,d*1000+v3*100+b*10+a),add(i,v4*1000+c*100+b*10+a);
		add(i,d*1000+c*100+v2*10+v1),add(i,d*1000+v3*100+v2*10+a),add(i,v4*1000+v3*100+b*10+a);
		add(i,d*1000+v3*100+v2*10+v1),add(i,v4*1000+v3*100+v2*10+a);
		add(i,v4*1000+v3*100+v2*10+v1);
        v1=(a+9)%10,v2=(b+9)%10,v3=(c+9)%10,v4=(d+9)%10;
		add(i,d*1000+c*100+b*10+v1),add(i,d*1000+c*100+v2*10+a);
		add(i,d*1000+v3*100+b*10+a),add(i,v4*1000+c*100+b*10+a);
		add(i,d*1000+c*100+v2*10+v1),add(i,d*1000+v3*100+v2*10+a),add(i,v4*1000+v3*100+b*10+a);
		add(i,d*1000+v3*100+v2*10+v1),add(i,v4*1000+v3*100+v2*10+a);
		add(i,v4*1000+v3*100+v2*10+v1);
    }
    return;
}
inline void bfs4(int S){
    for(int i=0;i<=9999;i++)dis[i]=0,vis[i]=false;
    queue<int> q;q.push(S);dis[S]=0;vis[S]=true;
    while(!q.empty()){
        int u=q.front();q.pop();        
        // cerr<<"Zyx "<<u<<' '<<head[u]<<endl;
        for(int i=head[u];~i;i=e[i].nxt){
            int v=e[i].to;
            if(vis[v])continue;
            // cerr<<u<<' '<<v<<endl;
            // cerr<<"-----------"<<endl;
            dis[v]=dis[u]+1;vis[v]=true;q.push(v);
        }
    }
    return;
}
int main(){
    freopen("lock.in","r",stdin);
    freopen("lock.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>Q;
    memset(head,-1,sizeof(head));
    if(n==1){
        while(Q--){
            int x,y;cin>>x>>y;
            int ans=abs(x-y);
            cout<<min(ans,10-ans)<<'\n';
        }
        return 0;
    }
    if(n==2){
        make2();
		bfs2(0);
    }
    else if(n==3){
        make3();
		bfs3(0);
    }
	else if(n==4){
        make4();
		bfs4(0);
    }
    while(Q--){
		string x,y;cin>>x>>y;x=' '+x,y=' '+y;
		for(int i=1;i<=n;i++)a[n-i+1]=x[i]-'0';
		for(int i=1;i<=n;i++)b[n-i+1]=y[i]-'0';
		for(int i=1;i<=n;i++)c[i]=(b[i]+10-a[i])%10;
		// for(int i=1;i<=n;i++)cerr<<a[i]<<' '<<b[i]<<' '<<c[i]<<endl;
		// cerr<<endl<<"-------------------"<<endl;
		int t=0;
		for(int i=1;i<=n;i++){
			if(i==1)t+=c[i];
			else if(i==2)t+=(c[i]*10);
			else if(i==3)t+=(c[i]*100);
			else if(i==4)t+=(c[i]*1000);
		}
		// cerr<<t<<endl;
		// cerr<<"The end!"<<endl;
		cout<<dis[t]<<'\n';
    }
    return 0;
}

T2

用老师的话说,就是做的题太少

注意到两个求和号内的式子形如最值套绝对值,容易联想到切比雪夫距离,所以先将 \(\min\) 改成 \(\max\)

然后切比雪夫距离转曼哈顿距离,直接做做完了

(听上去有点蒙,所以需要推式子)

具体地,有:

\[\begin{equation} \begin{aligned} &\ \sum_{i=1}^{n} \sum_{j=1}^{n} \min (|A_i - A_j|,|B_i - B_j|) \newline &= \sum_{i=1}^{n} \sum_{j=1}^{n} (|A_i - A_j|+|B_i - B_j|) - \max (|A_i - A_j|,|B_i - B_j|) \newline &= \sum_{i=1}^{n} \sum_{j=1}^{n} |A_i - A_j| + \sum_{i=1}^{n} \sum_{j=1}^{n} |B_i - B_j| - \sum_{i=1}^{n} \sum_{j=1}^{n} \max (|A_i - A_j|,|B_i - B_j|) \newline &= \sum_{i=1}^{n} \sum_{j=1}^{n} |A_i - A_j| + \sum_{i=1}^{n} \sum_{j=1}^{n} |B_i - B_j| - \frac{1}{2} \sum_{i=1}^{n} \sum_{j=1}^{n} ((A_i + B_i) - (A_j + B_j)) - \frac{1}{2} \sum_{i=1}^{n} \sum_{j=1}^{n} ((A_i - B_i) - (A_j - B_j)) \newline &= 2 \sum_{i=1}^{n} \sum_{j=i+1}^{n} |A_i - A_j| + 2 \sum_{i=1}^{n} \sum_{j=i+1}^{n} |B_i - B_j| - \sum_{i=1}^{n} \sum_{j=i+1}^{n} ((A_i + B_i) - (A_j + B_j)) - \sum_{i=1}^{n} \sum_{j=i+1}^{n} ((A_i - B_i) - (A_j - B_j)) \end{aligned} \nonumber \end{equation} \]

然后就转化为做四遍

\[\sum_{i=1}^{n} \sum_{j=i+1}^{n} |x_i - x_j| \]

排序,扫描右端点,每次加前缀和即可!

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+5;
int n,a[N],b[N],c[N],d[N],sum[N];
inline int cal(int x[]){
    sort(x+1,x+n+1);
    for(int i=1;i<=n;i++)sum[i]=0;
    for(int i=1;i<=n;i++)sum[i]=sum[i-1]+x[i];
    int ans=0;
    for(int i=1;i<=n;i++)ans+=(x[i]*i-sum[i]);
    return ans;
}
signed main(){
    freopen("dist.in","r",stdin);
    freopen("dist.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)cin>>b[i];
    for(int i=1;i<=n;i++)c[i]=a[i]+b[i];
    for(int i=1;i<=n;i++)d[i]=a[i]-b[i];
    cerr<<cal(a)<<' '<<cal(b)<<' '<<cal(c)<<' '<<cal(d)<<endl;
    cout<<cal(a)*2+cal(b)*2-cal(c)-cal(d)<<'\n';
    return 0;
}

T3

静态问题好做吗?非常好做,观测到这种恰好 \(x\) 个不相等的东西,直接考虑大力容斥

\(F(k)\) 表示钦定至少 \(k\) 个位置满足 \(P_i = a_i\) 的方案数,有:

\[ans = \sum_{k=0}^{n} F(k) \times A_{n-k}^{n-k} \times {(-1)}^{k} \]

然后求 \(F(k)\),组合数学、大力容斥计算不了的计数题就交给 DP

\(f_{i,j}\) 表示只考虑值域 \([1,i]\) 且恰好存在 \(j\) 个位置满足 \(P_x = a_x\) 的方案数,转移方程形如:

\[f_{i,j} = f_{i-1,j} + f_{i-1,j-1} \times cnt_i \]

其中 \(cnt_i\) 表示查询区间中值为 \(i\) 的元素个数

容易发现单次查询 \(O(n^2)\)

然后这个东西咋优化呢,一个很经典的观测即不带修且数据范围灵性,可以根号,具体地,可以莫队!

复杂度上没啥问题,大概是一个 \(O(q \times n \sqrt{n})\) 的东西

问题在于如何插入贡献以及删除贡献

回到那个转移方程上来,显然可以把第一维度丢掉,即

\[{f'}_j = f_j + f_{j-1} \times cnt \]

我们发现,插入一个元素相当于给 \(cnt\) 自增一,删除一个元素相当于给 \(cnt\) 自减一

那么,就有了一个比较 naive 的做法

对于 add 操作来说,先把 \(cnt\) 的部分取出来,然后自增一,最后再丢进去

对于 del 操作来说,先把 \(cnt\) 的部分取出来,然后自减一,最后再丢进去

需要注意的是,如果出现了新的 cnt 或者消失了一个 cnt,要对 \(f\) 数组更新

点击查看代码
// 背包撤销那一套正序枚举或者倒序枚举不会啊!!!
// 菜完了菜完了菜完了菜完了啊!!!
// 谁懂老师代码出来的那一瞬间的救赎感!!!
// 初极狭,才通人。复行数十步,__________
// 原来可以直接把原有的 DP 数组给拷贝下来,更新 cnt 后重新算!!!
// 真是菜完了,这么经典的操作居然不会!!!
// 有的人活着,他已经死了,有的人死了,他的确死了
// 有的人活着,他已经不想活了,有的人死了,他死得透透的了
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e3+5,MOD=1e9+7;
int n,Q,a[N],siz,fac[N];
int len,cnt[N],f[N],tmp[N],ans[N];
struct que{
    int l,r,blk,id;
    friend bool operator < (que s,que t){
        if(s.blk!=t.blk)return s.blk<t.blk;
        return ((s.blk&1)?(s.r<t.r):(s.r>t.r));
    }
}q[N];
inline void ADD(int &x,int y){x=(x+y)%MOD;return;}
inline void init(){
    fac[0]=1;
    for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%MOD;
    return;
}
inline void add(int k){
    int x=cnt[a[k]];cnt[a[k]]++;
    for(int i=1;i<=len;i++)tmp[i]=(f[i]+x*tmp[i-1]%MOD)%MOD;
    x++;
    for(int i=1;i<=len;i++)f[i]=(tmp[i]-x*tmp[i-1]%MOD+MOD)%MOD;
    if(x==1){len++;f[len]=(MOD-x*tmp[len-1]%MOD)%MOD;}
    return;
}
inline void del(int k){
    int x=cnt[a[k]];cnt[a[k]]--;
    for(int i=1;i<=len;i++)tmp[i]=(f[i]+x*tmp[i-1]%MOD)%MOD;
    x--;
    for(int i=1;i<=len;i++)f[i]=(tmp[i]-x*tmp[i-1]%MOD+MOD)%MOD;
    if(x==0)len--;
    return;
}
inline int cal(){
    // cerr<<len<<endl;
    // for(int i=0;i<=len;i++)cerr<<f[i]<<' ';
    // cerr<<endl;
    // for(int i=0;i<=len;i++)cerr<<fac[i]<<' ';
    // cerr<<endl;
    // cerr<<"--------------------------"<<endl;
    int res=0;
    for(int i=0;i<=len;i++)ADD(res,f[i]*fac[n-i]%MOD);
    return res;
}
signed main(){
    freopen("neq.in","r",stdin);
    freopen("neq.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>Q;siz=sqrt(n);
    init();
    for(int i=1;i<=n;i++)cin>>a[i],a[i]++;
    for(int i=1;i<=Q;i++){
        int l,r;cin>>l>>r;l++;
        q[i]={l,r,l/siz,i};
    }
    sort(q+1,q+Q+1);
    f[0]=tmp[0]=1;int l=1,r=0;
    for(int i=1;i<=Q;i++){
        int ql=q[i].l,qr=q[i].r,idx=q[i].id;
        while(l>ql)add(--l);
        while(r<qr)add(++r);
        while(l<ql)del(l++);
        while(r>qr)del(r--);
        ans[idx]=cal();
    }
    for(int i=1;i<=Q;i++)cout<<ans[i]<<'\n';
    return 0;
}

每日犯唐:重计算不等于多递推一轮

(代码中 len 表示 \(f_{i,j}\) 第二维度上限)

T4

不会不会怎么办?只能明天研读一下 FSZ 奆的代码了……

Upd. 晚上开窍了!会 T4 了!

观测部分分,可以发现能够搞一个 DP,状态设计大概是 \(f_{i,c,len}\) 表示枚举前缀 \([1,i]\),代价为 \(c\),包含下标 \(i\) 的最后一段连续段为 \(len\)

首先先处理一个问题,即 \(c\) 的上限是 \(T\),所以可能会炸。然而我们发现 x 数组大小不大,且 \(n\) 也不大,暴力枚举一圈后发现有效的状态数不多。所以可以先把所有可能的代价记录下来离散化,这样状态设计就没有问题了

然后扫一遍 DP,刷表转移更容易理解,比如——

\[f_{i-1,c,0/1/2/3} \to f_{i,c,1/2/3/4} \]

\[f_{i-1,c,len} + \sum_{k=i-len}^{i-1} a_k \to f_{i,p,0} \]

其中 \(\to\) 符号是广义的,表示动词“贡献”

然后捏,这一套东西可能只能通过部分测试点。究其原因,还是 \(c\) 太大了!

所以,考虑折半

容易发现,这个 DP 正着做和倒着做是没有差异的,所以我们在序列上取一个 \(mid\),然后先正着扫一遍 \(1 \to mid\) 的 DP,再倒着扫一遍 \(r \to (mid+1)\) 的 DP,最后考虑将两个 DP 数组进行合并

此时我们大概能想到的是什么

  • 所需要的 DP 的答案应该是 \(f_{mid,???,0/1/2/3/4}\)

  • 首先枚举左边的 \(len\),并枚举 \(???\)

  • 需要从右边找到一个最优解

  • 同理,枚举右边的 \(len\),需要保证中间拼合的段长度合法

  • 找一个最大的且保证代价不超过 \(T\) 的右边的 DP 值

  • 最大的是好弄的,可以维护一个 \(c\) 上的后缀最大值

  • 那么右边的 DP 值就是最大的合法的 \(c\),对应的后缀最大 DP 值

随便维护即可,时间复杂度 \(O(\text{能过})\)

(代码咕咕咕)

小结

破防,加训走起!

后记

世界孤立我任它奚落

完结撒花!

posted @ 2025-07-12 21:00  sunxuhetai  阅读(8)  评论(0)    收藏  举报