模拟赛 2026
模拟赛 2026
新一年新气象,戒 P 从我做起!
1.2 NOIP 模拟赛 二五
97+100+0+0=197 pts
HHHOJ 爆炸硬控我 3h,导致 T3 无法上交。最后让 zzx 帮我交了一发。
T1
正解 cout<<0; 是个啥?
T2
经典题,没什么好讲的。
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
神仙题。
首先 \(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\) 时,图是一个基环树,将其按奇偶环分类讨论。
-
偶环:此时任然可以奇偶染色,此时考虑多出来的边 \(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\) 的中位数时最小。 -
奇环:此时会存在一条环上的边的两点颜色相同,需要考虑交换这两个点的贡献。
当交换这两个点时,若这两个点颜色相同,会使这两个点颜色同时取反,当两种颜色数量不同且为偶数时可以将这两个点交换来改变数量,操作数为 \(\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;
}
1.7 NOIP 模拟赛 二六
T1
你有一个环。在环上随机挑选 \(n\) 个点,求存在一个大小为 \(\frac{2\pi}{k}\) 的弧能覆盖所有点的概率。
考虑固定一个点,要求剩下 \(n-1\) 满足条件的概率是 \(\frac{1}{k^{n-1}}\),有 \(n\) 个点,所以概率为 \(\frac{n}{k^{n-1}}\)。
T2
你有一个大小为 \(n\) 的环,节点标号为 \(1\sim n\)。
标号为 \(x\) 的点的权值为 \(f(x)=\sum_{i=1}^x\sum_{j=1}^x[(i\times j)| x]\)(\(|\) 表示整除),求环上所有点的权值之和。
\(n\le 10^{12}\)。
甚至赛时并没有做出来。
答案可以转化为 \(\sum\limits_{i=1}^n\sum\limits_{j=1}^n \lfloor \frac{n}{i\times j} \rfloor\),相当于求 \((i,j,k)\) 的个数使其满足 \(i\times j\times k\le n\),直接求就好了。复杂度 \(n^{\frac{2}{3}}\)。
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=2e5+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{
ll n;
void solve(){
cin>>n;
ll ans=0;
for(ll i=1;i*i*i<=n;i++){
for(ll j=i;j*j<=n/i;j++){
ll k=n/i/j;
if(i^j) ans+=6ll*(k-j)+3;
else ans+=3ll*(k-j)+1;
}
}
cout<<ans<<"\n";
}
int main(){
int T=1;
// cin>>T;
while(T--) solve();
return 0;
}
}
int main(){
IOS;H_H::main();
return 0;
}
T3
你有一个大小为 \(n\) 的环,环上的元素分为 \(0/1\) 两种。
现已知有 \(A\) 个元素旁边存在 \(0\)(即与它相邻的两个元素中有至少一个为 \(0\)),有 \(B\) 个元素旁边存在 \(1\)(即与它相邻的两个元素中有至少一个为 \(1\))。
要求构造一组可能的情况,或判断无解。\(n\le 10^5\)。
赛时因为细节问题并没有过这题。
思维含量不大,但细节死多,全是分讨。。。
对于其中一个点,它左右两边的值有 \(4\) 种情况,为 \(00,01,10,11\),设其个数分别为 \(x_1,x_2,x_3,x_4\),可以列出方程:
解得:
若 \(x_1<0\) 或 \(x_2+x_3<0\) 或 \(x_4<0\) 时无解。
会发现这些数相邻的数是无法造成贡献的,只会隔一位造成贡献。因此可以将能互相造成贡献的数拎出来,相当于构造一个 \(01\) 串,满足子串为 \(00,01,10,11\) 的个数分别是 \(x_1,x_2,x_3,x_4\) 个。
现在要考虑能互相造成贡献的数所处的位置。当 \(n\) 为奇数是,假设 \(n=5\),情况如下:

此时序列代表的位置是 \([1,3,5,2,4,1]\)。
若 \(n\) 为偶数,则会形成两个序列。假设 \(n=6\),同理 序列为 \([1,3,5,1]\) 和 \([2,4,6,2]\)。
会发现对于构造的序列的首尾必须一样。接下来考虑如何构造这个序列。
其实就是 这道题。
一开始的自然的想法是枚举 \(x_2,x_3\) 分别的个数。显然复杂度不对。
假设构造的序列长度为 \(len\)。
可以将构造转化成将序列末尾加数,对应个数分别是 \(x_1,x_2,x_3,x_4\)。
会发现 \(x_2,x_3\) 可以是看作是 \(0,1\) 的一个「转变」,即当加了若干个 \(0\) 之后要加 \(1\) 或是 \(1\) 后面要加 \(0\),贡献在 \(x_2,x_3\) 上。
由于序列首尾相同,所以「转变」的次数必须为偶数次,即 \(x_2+x_3\) 为偶数。
可以考虑一种构造方案,假设序列为 \(A\),令 \(A_1=0\),在后面接 \(x_1\) 个 \(0\),在「转变」一次,在接 \(x_4\) 个 \(1\),最后一直「转变」即可。
void solve(int len,int num_1,int num_3){//长度为 len+1,num_1 相当于 x_1,num_3 相当于 x_4。
for(int i=1;i<=num_1+1;i++) a[i]=0;
for(int i=1;i<=num_3+1;i++) a[i+num_1+1]=1;
for(int i=num_1+num_3+3;i<=len+1;i++) a[i]=a[i-1]^1;
if(a[1]!=a[len+1]) cout<<"NIE\n",exit(0);//特判不满足首尾相同的条件,因为可能会出现 num_1=num_3=0 的情况。
}
对与 \(n\) 为奇数时是容易构造的,但对于 \(n\) 为偶数时需要构造两个序列,因此需要将 \(x_1,x_4\) 分到两个序列中。
当 \(x_2+x_3=0\) 和 \(x_2+x_3=2\) 的情况特判掉,此时需要考虑 \(x_2+x_3>2\) 的情况。
一种方法是将 \(x_2+x_3\) 平分到两个序列中(若 \(\frac{x_2+x_3}{2}\) 为奇数则各加减 \(1\)),在第一个序列中为 \(mid\) 个。枚举 \(x_1\) 在序列一的个数为 \(tp_1\) 个,要满足 \(\frac{n}{2}-(tp1+mid)\le x_4\)。
最后将答案与序列一一对应即可。
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 a[N];
void solve(int len,int num_1,int num_3){//构造序列
for(int i=1;i<=num_1+1;i++) a[i]=0;
a[num_1+2]=1;
for(int i=1;i<=num_3;i++) a[i+num_1+2]=1;
for(int i=num_1+num_3+3;i<=len+1;i++) a[i]=a[i-1]^1;
if(a[1]!=a[len+1]) cout<<"NIE\n",exit(0);
}
int n,A,B,ans[N];
int main(){
cin>>n>>A>>B;
if(n==2){//特判 n=2
if(A==2 && B==0) cout<<"TAK\n",cout<<"00\n",exit(0);
if(A==1 && B==1) cout<<"TAK\n",cout<<"01\n",exit(0);
if(A==0 && B==2) cout<<"TAK\n",cout<<"11\n",exit(0);
cout<<"NIE"<<"\n";
return 0;
}
int num_1=n-B,num_2=A+B-n,num_3=n-A;//num_1 对应 x_1,num_2 对应 x_2+x_3,num_3 对应 x_4。
if(num_1<0 || num_2<0 || num_3<0 || num_2&1) cout<<"NIE\n",exit(0);//无解
if(n&1){//n 为奇数
if(!num_2){//特判 num_2 为 0 的情况
if(num_1 && num_3) cout<<"NIE\n",exit(0);
if(num_1) fill(ans+1,ans+1+n,0);
else fill(ans+1,ans+1+n,1);
}
else{
solve(n,num_1,num_3);
int tot=0;
for(int i=1;i<=n;i+=2) ans[i]=a[++tot];
for(int i=2;i<=n;i+=2) ans[i]=a[++tot];
}
}
else{
if(!num_2){
if(!num_3) fill(ans+1,ans+1+n,0);
else if(!num_1) fill(ans+1,ans+1+n,1);
else if(num_1!=n/2 && num_3!=n/2) cout<<"NIE\n",exit(0);
else for(int i=1;i<=n;i+=2) ans[i]=0,ans[i+1]=1;
}
else if(num_2==2){//特判
if(num_1>=n/2){
num_1-=n/2;
for(int i=1;i<=n;i+=2) ans[i]=0;
}
else if(num_3>=n/2){
num_3-=n/2;
for(int i=1;i<=n;i+=2) ans[i]=1;
}
else cout<<"NIE\n",exit(0);
solve(n/2,num_1,num_3);
for(int i=2,j=0;i<=n;i+=2) ans[i]=a[++j];
}
else{
int tp1=0,tp2=n/2,mid=max(2,n/2-(num_1+num_3));
if(mid&1) mid++;
if(mid>num_2) mid-=2;
tp2-=mid;
while(num_3<n/2-(tp1+mid)) tp1++,tp2--;
solve(n/2,tp1,tp2);
for(int i=1,j=0;i<=n;i+=2) ans[i]=a[++j];
solve(n/2,num_1-tp1,num_3-tp2);
for(int i=2,j=0;i<=n;i+=2) ans[i]=a[++j];
}
}
cout<<"TAK"<<"\n";
for(int i=1;i<=n;i++) cout<<ans[i];
cout<<"\n";
return 0;
}
}
int main(){
IOS;H_H::main();
return 0;
}
T4
赛时并不会做。令 \(n=2N\)。
可以考虑每种连通块出现的次数。
将环看作长度为 \(n\) 的序列,那么环上一段区间也对应序列的一段区间。
设 \(f_{l,r}\) 表示连通块对应在序列上后最左端为 \(l\),最右端为 \(r\) 的内部的方案数。满足 \([l,r]\) 内部的点一定不会往外连。
若不考虑 \(l,r\) 在同一个连通块的限制。则 \(f_{l,r}=g(c_{l,r})\),其中 \(c_{l,r}\) 表示 \([l,r]\) 没有被连边的点的个数,\(g(x)\) 表示 \(x\) 个点连边方案数,\(x\) 为奇数时为 \(0\),否则为 \(1\times 3 \times \dots (x-1)\)。
若要满足 \(l,r\) 在同一个连通块,可以用容斥来计算。
有 \(f_{l,r}=g(c_{l,r})-\sum\limits_{k=l}^{r-1} f_{l,k}\times g(c_{k+1,r})\)。
最后答案为 \(\sum f_{l,r}\times g(n - 2 K - c_{l,r})\)。
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=600+20,M=1e5+20;
const ll INF=1ll<<60,mod=1e9+7;
namespace H_H{
int n,k,pos[N],c[N];
ll f[N][N],g[N];
int get(int l,int r){
if(l>r) return 0;
return c[r]-c[l-1];
}
int main(){
cin>>n>>k;
n<<=1;
for(int i=1,x,y;i<=k;i++){
cin>>x>>y;
pos[x]=y,pos[y]=x;
}
g[0]=1;
for(int i=2;i<=n;i+=2) g[i]=g[i-2]*(i-1)%mod;
for(int i=1;i<=n;i++) c[i]=c[i-1]+(!pos[i]);
for(int l=1;l<=n;l++){
for(int r=l;r<=n;r++){
if((r-l+1)&1 || get(l,r)&1) continue;
bool flag=1;
for(int i=l;i<=r;i++){
if(pos[i] && (pos[i]<l || pos[i]>r)){
flag=0;
break;
}
}
if(!flag) continue;
f[l][r]=g[get(l,r)];
for(int i=l;i<r;i++){
f[l][r]=(f[l][r]-f[l][i]*g[get(i+1,r)]%mod+mod)%mod;
}
}
}
ll ans=0;
for(int l=1;l<=n;l++){
for(int r=l;r<=n;r++){
ans=(ans+f[l][r]*g[n-2*k-get(l,r)]%mod)%mod;
}
}
cout<<ans<<"\n";
return 0;
}
}
int main(){
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
IOS;H_H::main();
return 0;
}
1.8 NOIP 模拟赛 二七
这场比赛没打,让我补一下。
T1
莫队也可以过。经典题没什么好讲的。
T2
对一个排列 \(a\),给出 \(m\) 组有序数对 \((x_i,y_i)\),问是否存在一种顺序重新排列这几组数对,使得能依次交换 \(a_{x_i},a_{y_i}\) 来排序。
若 \(m\) 不是任意交换两数排序中次数的最小值,也输出
NO。\(n\le 2\times 10^5,m\le 4\times 10^5\)。
没做,让我先补一下。
T3
T4
2.7 NOIP 模拟赛 二八
T1
难度还好,但感觉有点难实现。
相当于要找存在至少两个以上是最大值的,将其删去。可以拿指针或 set 维护。
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 Tp template<typename T>
//#define Ts template<typename T,typename ...args>
#define lb(x) ((x)&(-x))
using namespace std;
const int N=2e5+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{
struct node{
int a,b,c;
inline void input(){cin>>a>>b>>c;}
}a[N];
struct node2{
PII p;int id;
node2(){p={0,0};id=0;}
node2(int _a,int _b,int _id){p={_a,_b},id=_id;}
bool operator < (const node2&xx) const {return p<xx.p; }
bool operator == (const node2&xx) const {return p==xx.p;}
bool operator > (const node2&xx) const {return p>xx.p; }
};
bool vis[N];
multiset<node2> s[4];
multiset<int> q[4];
int n;
void erase(int id){
if(vis[id]) return ;
vis[id]=1;
q[1].erase(q[1].find(a[id].a));
q[2].erase(q[2].find(a[id].b));
q[3].erase(q[3].find(a[id].c));
}
bool Del(auto &ss,int A,int B){
auto bg=ss.lower_bound(node2(A,B,0));
auto ed=ss.upper_bound(node2(A,B,0));
for(auto i=bg;i!=ed;i++){
node2 tp=*i;erase(tp.id);
}
if(bg==ed) return 0;
ss.erase(bg,ed);
return 1;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
a[i].input();
q[1].insert(a[i].a);q[2].insert(a[i].b);q[3].insert(a[i].c);
s[1].insert(node2(a[i].a,a[i].b,i));
s[2].insert(node2(a[i].a,a[i].c,i));
s[3].insert(node2(a[i].b,a[i].c,i));
}
bool flag=1;
while(flag && !q[1].empty() && !q[2].empty() && !q[3].empty()){
int A=*prev(q[1].end()),B=*prev(q[2].end()),C=*prev(q[3].end());
flag=Del(s[1],A,B)|Del(s[2],A,C)|Del(s[3],B,C);
}
if(q[1].empty() || q[2].empty() || q[3].empty()) cout<<-1<<"\n",exit(0);
int A=*prev(q[1].end()),B=*prev(q[2].end()),C=*prev(q[3].end());
cout<<(A+B+C)<<"\n";
return 0;
}
}
int main(){
IOS;H_H::main();
return 0;
}
T2
首先当每个数都是 \(k-1\) 时和最大,但是可能不符合限制。考虑如何修改使其满足限制。
对每一行考虑,当全部填 \(k-1\) 时的值为 \(s=(k-1)\times m \% k\),那么我们需要将其选择几个数减小使其与 \(s\) 相同。
若对于某一行 \(i\)。若 \(A_i\le s\),那么就将其减少 \(s-A_i\),否则减少 \(s+k-A_i\)。
每一列同理。
按这种方法可能出现行与列减小的值不同。若相差为 \(k\) 的倍数,则存在一种方法满足条件,否则无解。
点击查看代码
#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=2e5+20,M=1e5+20;
const ll INF=1ll<<60,mod=998244353;
namespace H_H{
int n,m,k;
int a[N],b[N];
int main(){
cin>>n>>m>>k;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=m;i++) cin>>b[i];
ll r=1ll*m*(k-1)%k,c=1ll*n*(k-1)%k;
ll sr=1ll*n*r,sc=1ll*m*c;
for(int i=1;i<=n;i++){
sr-=a[i];
if(r-a[i]<0) sr+=k;
}
for(int i=1;i<=m;i++){
sc-=b[i];
if(c-b[i]<0) sc+=k;
}
if((sr-sc)%k!=0) cout<<-1<<"\n",exit(0);
cout<<(1ll*(k-1)*n*m-max(sc,sr))<<"\n";
return 0;
}
}
int main(){
IOS;H_H::main();
return 0;
}
T3
为啥 NOIP 模拟赛会出网络流???
先求出图的最小生成树,那么保留的边一定在这个最小生成树上。
可以建出 Kruskal 重构树,非叶节点的点代表树上的一条边,点的权值就是边的权值,设为 \(v_i\)。对于这个点,若它的左右子树不存在骑士,那么这条边就要被保留。否则删去。
可以考虑网络流建图。源点向每个骑士连 \((1,0)\) 的边,\(1\) 代表流量,\(0\) 代表费用。每个骑士向他可以派去的地方对应的叶节点连一条边 \((1,0)\)。非叶节点向父亲连一条 \((1,-v_i)\) 的边,同时再连一条 \((1,0)\) 的边。树的根节点向汇点连 \((1,-\inf)\) 的边,表示这条边必须走。记得在答案中将这条边的值加回来。
点击查看代码
#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 Tp template<typename T>
//#define Ts template<typename T,typename ...args>
#define lb(x) ((x)&(-x))
using namespace std;
const int N=1e5+20,M=1e6+20;
const ll INF=1ll<<60,mod=998244353;
ll Mx;
namespace SSP{
struct edge{int to,nxt;ll w,c;}e[M];
int head[N],cnt=1;
void add(int u,int v,ll w,ll c){
e[++cnt]={v,head[u],w,c};
head[u]=cnt;
}
void con(int u,int v,ll w,ll c){
add(u,v,w,c);
add(v,u,0,-c);
}
int s,t,Ns[N];
ll cost,d[N];
bool vis[N];
bool spfa(){
MS(d,0x3f);MS(vis,0);MC(Ns,head);
Mx=d[1];
queue<int> q;
q.push(s);vis[s]=1;d[s]=0;
bool flag=0;
while(!q.empty()){
int u=q.front();q.pop();
vis[u]=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;ll w=e[i].w;
if(!w || d[v]<=d[u]+e[i].c) continue;
d[v]=d[u]+e[i].c;
if(!vis[v]) q.push(v),vis[v]=1;
if(v==t) flag=1;
}
}
return flag;
}
ll dfs(int u,ll Sum){
if(u==t) return Sum;
ll pus=0,k;
vis[u]=1;
for(int i=Ns[u];i;i=e[i].nxt){
Ns[u]=i;
int v=e[i].to;ll w=e[i].w;
if(vis[v] || !w || d[v]!=d[u]+e[i].c) continue;
k=dfs(v,min(w,Sum));
if(!k) d[v]=INF;
pus+=k,Sum-=k;e[i].w-=k,e[i^1].w+=k;cost+=k*e[i].c;
if(!Sum) break;
}
vis[u]=0;
return pus;
}
PLL calc(){
ll flow=0,x=0;cost=0;
while(spfa()) while((x=dfs(s,INF))) flow+=x;
return {flow,cost};
}
}
namespace H_H{
int n,m,k;
int ff[N],val[N];
bool vis[N];
struct edge{
int u,v,w;
bool operator < (const edge &xx)const {
return w<xx.w;
}
}e[M];
int find(int xx){
if(xx==ff[xx]) return xx;
return ff[xx]=find(ff[xx]);
}
int main(){
cin>>n>>m>>k;
iota(ff+1,ff+1+n,1);
for(int i=1,u,v,w;i<=m;i++){
cin>>u>>v>>w;
e[i]={u,v,w};
}
sort(e+1,e+1+m);
ll sum=0;
int cnt=n;
for(int i=1;i<=m;i++){
int fu=find(e[i].u),fv=find(e[i].v);
if(fu==fv) continue;
sum+=e[i].w;
val[++cnt]=e[i].w;
ff[cnt]=ff[fu]=ff[fv]=cnt;
SSP::con(fu,cnt,1,0);
SSP::con(fv,cnt,1,0);
}
SSP::s=cnt+k+1,SSP::t=cnt+k+2;
for(int i=n+1;i<=cnt+1;i++) SSP::con(i,SSP::t,1,-val[i]);
for(int i=1,num;i<=k;i++){
cin>>num;
SSP::con(SSP::s,cnt+i,1,0);
for(int j=1,x;j<=num;j++){
cin>>x;vis[x]=1;
SSP::con(cnt+i,x,1,0);
}
}
for(int i=1;i<=n;i++) vis[find(i)]|=vis[i];
for(int i=1;i<=cnt;i++){
if(i==find(i)) SSP::con(i,SSP::t,1,-mod),sum+=mod;
}
auto tp=SSP::calc();
if(sum+tp.second>=mod || tp.first<k) cout<<-1<<"\n",exit(0);
// cout<<sum<<"\n";
if(sum+tp.second==1) cout<<-1<<"\n",exit(0);
cout<<sum+tp.second<<"\n";
return 0;
}
}
int main(){
IOS;H_H::main();
return 0;
}

浙公网安备 33010602011771号