模拟赛2026

模拟赛 2026

新一年新气象,戒 P 从我做起!

1.2 NOIP 模拟赛 二五

97+100+0+0=197 pts

HHHOJ 爆炸硬控我 3h,导致 T3 无法上交。最后让 zzx 帮我交了一发。

T1

正解 cout<<0; 是个啥?

T2

lg-P4551

经典题,没什么好讲的。

T3

初始有一个大小为 \(n\) 的整数集合 \(\{a_1,a_2,\dots,a_n\}\)

规定在集合中的元素 \(x,y\) 之间建立起一个双向联系的花费为 \(|x−y|\),并定义一个集合 \(S\) 的代价为\(S\) 中每个元素都与至少一个其他元素建立联系的最小花费总和。(特殊地,对于大小小于 \(2\) 的集合,代价为 \(0\)

现在有 \(q\) 次操作,每次向集合内添加一个元素或从集合中删去一个元素,要求在每次操作后求出当前集合的代价。

对于 \(100\%\) 的数据,\(1\le n,q\le 2\times 10^5,0\le a_i,x\le 10^9\)

先考虑不修改怎么做。

可以先将集合 \(a\) 从小到大排序,会发现对于每一个元素都是将其与相邻的两个元素其中 \(1\) 个或 \(2\) 个建立联系时最优。

考虑 dp,设 \(f_{i,0/1}\) 表示前 \(i\) 个数,除了 \(i\) 之外已经建立联系,\(i\) 是否与前一个数建立联系。有 \(f_{i,0}=f_{i-1,1},f_{i,1}=\min(f_{i-1,0},f_{i-1,1})+|a_i-a_{i-1}|\)

若加上修改,考虑用权值线段树维护。

先离散化,需要考虑如何合并两个区间。

由于中间的数肯定是已经建立联系的,只需要考虑一个区间 \(P\) 在集合中的数的左右的值,为 \(ls_p,rs_p\)。设为 \(f_{p,0/1,0/1}\)\(0/1\) 的定义同上。

pre(p,i,j)=min({
	pre(p<<1,i,0)+pre(p<<1|1,0,j)+ct(rs(p<<1),ls(p<<1|1)),
	pre(p<<1,i,0)+pre(p<<1|1,1,j)+ct(rs(p<<1),ls(p<<1|1)),
	pre(p<<1,i,1)+pre(p<<1|1,0,j)+ct(rs(p<<1),ls(p<<1|1)),
	pre(p<<1,i,1)+pre(p<<1|1,1,j)
});

\(pre\) 表示 \(f\)\(p<<1,p<<1|1\) 分别代表左右节点,\(ct(x,y)\) 表示离散化后为 \(x,y\) 的值建立联系的代价。

在注意分讨一下特殊情况就行了。

Code

点击查看代码
#define ll long long
#define MC(x,y) memcpy(x,y,sizeof(x))
const int N=4e5+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{
    int n,m,tot,a[N],v[N];
    struct qry{
        int op,x;
    }q[N];
    bool vis[N];
    struct Tree{
        int l,r,ls,rs;
        ll pre[2][2];
        #define l(p) t[p].l
        #define r(p) t[p].r
        #define ls(p) t[p].ls
        #define rs(p) t[p].rs
        #define pre(p,x,y) t[p].pre[x][y]
    }t[N<<2];
    inline ll ct(int x,int y){
        return abs(v[x]-v[y]);
    }
    inline void up(int p){
		//无左右节点 
        while(!ls(p<<1) && !ls(p<<1|1)) return ls(p)=rs(p)=0,pre(p,0,0)=0,pre(p,0,1)=pre(p,1,0)=pre(p,1,1)=INF,void();
        //无左节点
		while(!ls(p<<1)) return ls(p)=ls(p<<1|1),rs(p)=rs(p<<1|1),MC(t[p].pre,t[p<<1|1].pre),void();
        //无右节点
		while(!ls(p<<1|1)) return ls(p)=ls(p<<1),rs(p)=rs(p<<1),MC(t[p].pre,t[p<<1].pre),void();
        //左右节点都只有 1 个节点
		while(ls(p<<1)==rs(p<<1) && ls(p<<1|1)==rs(p<<1|1)){
            ls(p)=ls(p<<1);rs(p)=rs(p<<1|1);
            pre(p,0,0)=0,pre(p,0,1)=pre(p,1,0)=INF,pre(p,1,1)=ct(ls(p),rs(p));
            return ;
        }
		//左只有一个
        while(ls(p<<1)==rs(p<<1)){
            ls(p)=ls(p<<1),rs(p)=rs(p<<1|1);
            pre(p,0,0)=pre(p<<1|1,1,0);
            pre(p,1,0)=min({pre(p<<1|1,1,0),pre(p<<1|1,0,0)})+ct(rs(p<<1),ls(p<<1|1));
            pre(p,0,1)=pre(p<<1|1,1,1);
            pre(p,1,1)=min({pre(p<<1|1,1,1),pre(p<<1|1,0,1)})+ct(rs(p<<1),ls(p<<1|1));
            return ;
        }
		//右只有一个
        while(ls(p<<1|1)==rs(p<<1|1)){
            ls(p)=ls(p<<1),rs(p)=rs(p<<1|1);
            pre(p,0,0)=pre(p<<1,0,1);
            pre(p,0,1)=min({pre(p<<1,0,1),pre(p<<1,0,0)})+ct(rs(p<<1),ls(p<<1|1));
            pre(p,1,0)=pre(p<<1,1,1);
            pre(p,1,1)=min({pre(p<<1,1,1),pre(p<<1,1,0)})+ct(rs(p<<1),ls(p<<1|1));
            return ;
        }
		//一般
        for(int i=0;i<=1;i++){
            for(int j=0;j<=1;j++){
                pre(p,i,j)=min({pre(p<<1,i,0)+pre(p<<1|1,0,j)+ct(rs(p<<1),ls(p<<1|1)),
                                pre(p<<1,i,0)+pre(p<<1|1,1,j)+ct(rs(p<<1),ls(p<<1|1)),
                                pre(p<<1,i,1)+pre(p<<1|1,0,j)+ct(rs(p<<1),ls(p<<1|1)),
                                pre(p<<1,i,1)+pre(p<<1|1,1,j)});
            }
        }
        ls(p)=ls(p<<1),rs(p)=rs(p<<1|1);
    }
    void Build(int p=1,int l=1,int r=tot){
        l(p)=l,r(p)=r;
        if(l==r){
            pre(p,0,0)=0;
            pre(p,0,1)=pre(p,1,0)=pre(p,1,1)=INF;
            return ;
        }
        int mid=(l+r)>>1;
        Build(p<<1,l,mid);Build(p<<1|1,mid+1,r);
        up(p);
    }
    void update(int p,int op,int d){
        if(l(p)==r(p)){
            if(op==1) ls(p)=rs(p)=d;
            if(op==2) ls(p)=rs(p)=0;
            return ;
        }
        int mid=(l(p)+r(p))>>1;
        if(d<=mid) update(p<<1,op,d);
        else update(p<<1|1,op,d);
        up(p);
    }
    int main(){
        cin>>n>>m;
        for(int i=1;i<=n;i++){
            cin>>a[i];
            v[++tot]=a[i];
        }
        sort(a+1,a+1+n);
        for(int i=1;i<=m;i++){
            cin>>q[i].op>>q[i].x;
            v[++tot]=q[i].x;
        }
        sort(v+1,v+1+tot);
        tot=unique(v+1,v+1+tot)-v-1;
        for(int i=1;i<=n;i++){
            a[i]=lower_bound(v+1,v+1+tot,a[i])-v;
        }
        for(int i=1;i<=m;i++){
            q[i].x=lower_bound(v+1,v+1+tot,q[i].x)-v;
        }
        Build();
        for(int i=1;i<=n;i++) update(1,1,a[i]);
        int tt=n;
        for(int i=1;i<=m;i++){
            update(1,q[i].op,q[i].x);
            if(q[i].op==2) tt--;
            else tt++;
            if(tt<2) cout<<0<<"\n";
            else cout<<t[1].pre[1][1]<<"\n";
        }
        return 0;
    }
}

T4

AT_agc004_f [AGC004F] Namori

神仙题。

首先 \(n\) 为奇数肯定无解。

先考虑 \(m=n-1\) 的情况。

可以先将其黑白染色,将其转化为每次交换黑白点,使每个节点的黑白点互换。

对于以 \(i\) 为跟的子树,设其子树内(包括它自己)的黑白点个数分别是 \(cw_i,cb_i\)。则对于 \(i\)\(fa_i\) 的边,至少要交换 \(|cw_i-cb_i|=c_i\) 次,将内部所有多余的子交换出去才行。可以证明存在这种方案满足条件。

此时答案为 \(\sum\limits_{i=1}^n |c_i|\)

\(m=n\) 时,图是一个基环树,将其按奇偶环分类讨论。

  1. 偶环:此时任然可以奇偶染色,此时考虑多出来的边 \(A,B\) 对答案的贡献。
    对于这条边,设其交换了 \(x\) 次,设为 \(A\rightarrow B\)(反过来则 \(x<0\)),对于 \(B\) 不经过 \(A\) 到环顶端的路径,贡献为 \(|c_i+x|=|-c_i-x|\),对于 \(A\) 不经过 \(B\) 到环顶端的边,贡献为 \(|c_i-x|\)
    可以将这些 \(c_i\)\(-c_i\) 看作数轴上的点,要求一个 \(x\) 使其到这些点的距离之和最小,当 \(x\)\(c\) 的中位数时最小。

  2. 奇环:此时会存在一条环上的边的两点颜色相同,需要考虑交换这两个点的贡献。
    当交换这两个点时,若这两个点颜色相同,会使这两个点颜色同时取反,当两种颜色数量不同且为偶数时可以将这两个点交换来改变数量,操作数为 \(\frac{|\sum cw-\sum cb|}{2}\),之后就同偶环一样处理。

Code

点击查看代码
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define PLL pair<ll,ll>
#define PII pair<int,int>
#define lb(x) ((x)&(-x))
using namespace std;
const int N=1e5+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{
	int n,m,col[N],w[N],k[N],num[N],tot,s,A,B;
	vector<int> to[N];
	bool odd;
	void dfs0(int u,int fa){
		s+=col[u];
		for(int v:to[u]){
			if(v==fa) continue;
			if(col[v]){
				odd=col[v]==col[u];
				A=u,B=v;
			}
			else col[v]=-col[u],dfs0(v,u);
		}
	}
	void dfs1(int u,int fa){
		w[u]+=col[u];
		for(int v:to[u]){
			if(v==fa || (A==u && B==v) || (A==v && B==u)) continue;
			dfs1(v,u);
			k[u]+=k[v];w[u]+=w[v];
		}
	}
	int main(){
		cin>>n>>m;
		for(int i=1,u,v;i<=m;i++){
			cin>>u>>v;
			to[u].pb(v);
			to[v].pb(u);
		}
		if(n&1) cout<<-1<<"\n",exit(0);
		ll ans=0;
		col[1]=1;dfs0(1,0);
		if(m==n-1){
			if(s) cout<<-1<<"\n",exit(0);
		}
		else{
			if(odd){
				// if(s&1) cout<<-1<<"\n",exit(0);
				ans+=abs(s>>1);
				w[A]-=s>>1,w[B]-=s>>1;
			}
			else{
				if(s) cout<<-1<<"\n",exit(0);
				k[A]=1,k[B]=-1;
			}
		}
		dfs1(1,0);
		for(int i=1;i<=n;i++){
			if(k[i]) num[++tot]=k[i]*w[i];
			else ans+=abs(w[i]);
		}
		num[++tot]=0;
		sort(num+1,num+1+tot);
		int tp=num[(tot+1)>>1];
		for(int i=1;i<=tot;i++) ans+=abs(num[i]-tp);
		cout<<ans<<"\n";
		return 0;
	}
}
int main(){
	IOS;H_H::main();
	return 0;
}
posted @ 2026-01-03 19:10  tyh_27  阅读(2)  评论(0)    收藏  举报