Loading web-font TeX/Size1/Regular

模拟赛 (11~20)

一、高一高二联合NOIP模拟赛11

T1:运输 (transport)

方差最小代表什么?设 ni=1ai=sum. 最理想的情况是所有结点最终的 ai 都变成 s=sumn.但是有可能 n 不能整除 sum。设 las=summodn。那么问题转化成有 lasai 变为 s, nlas个 变成 s+1.

树上背包问题。

dpi,j 表示,以 i 为根节点的子树中,有 j 个结点为 s+1,其它结点都为 s 的代价。(如果子树内初始的松果的总数量恰好等于最终状态的松果的总数量,也就是自己能内部调整,肯定不会找外面借,或者送到外面。不能自己调整,那么就只能从外面把松果运输进来,或者运输出去,都会经过根节点 i。所以 dp 表示把多的(差的)松果放到该子树的根节点 i 时的代价)

转移方程就很好写了。具体看代码。

这里的时间复杂度似乎是 O(n3) 但是可以A。貌似能证明时间复杂度为 O(n2)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pii pair<int,int>
const int MAXN=5005;
vector<pii> G[MAXN],lxx;
int n;
ll a[MAXN];
int read(){
    int x=0;char c=getchar();bool f=0;
    while(c>'9'||c<'0'){f|=(c=='-');c=getchar();}
    while(c<='9'&&c>='0'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
    return f?-x:x;
}
ll siz[MAXN],sum[MAXN];
ll s,las;//las有多少个s+1
//如果一棵子树内少了或者多了,一定会经过根节点进行传输
ll dp[MAXN][MAXN];//以u为根节点的子树中,有x个s+1,若有多余,都移动到u结点上的最小代价
ll f[MAXN];
const ll MAX=1e18;
void dfs(int u,int fa){
    siz[u]=1,sum[u]=a[u];
    dp[u][0]=dp[u][1]=0;//多的都在u上,不用再移动
    for(int i=2;i<=las;i++) dp[u][i]=MAX;
    for(pii t:G[u]){
        int v=t.first;
        if(v==fa)   continue;
        dfs(v,u);
        siz[u]+=siz[v];
        sum[u]+=sum[v];
        for(int i=0;i<=las;i++) f[i]=MAX;
        for(int i=0;i<=min(las,siz[v]);i++){
			ll c=llabs(sum[v]-siz[v]*s-i)*t.second;//有多少个点会经过t这条边
		    //如果有多的,都移动到根节点u,只有这样才能送出去
		    //v子树内多的都在v上,所以只会移动一次
            for(int j=i;j<=min(las,siz[u]);j++){
                f[j]=min(f[j],dp[u][j-i]+dp[v][i]+c);
            }
        }
        for(int i=0;i<=las;i++) dp[u][i]=f[i];
    }
}
void Init(){
    for(int i=1;i<=n;i++)   G[i]=lxx;
    s=0;
}
int main(){
	freopen("transport.in","r",stdin);
	freopen("transport.out","w",stdout);
    int T=read();
    while(T--){
        n=read();
        Init();
        for(int i=1;i<=n;i++){
            a[i]=read();
            s+=a[i];
        }
        las=s%n;
        s/=n;
        for(int i=1;i<n;i++){
            int u=read(),v=read(),w=read();
            G[u].push_back({v,w});
            G[v].push_back({u,w});
        }
        dfs(1,0);
        printf("%lld\n",dp[1][las]);
    }
    return 0;
}

T2:或 (or)

先考虑两种特殊情况:

  1. R=2k1 显然能得到的答案区间为 [L,R]
  2. L=0.设 R 的最高的位为第 k 位。能得到的答案区间为 [0,2k+11]

上面两种情况很好证明,不再赘述。

发现两种情况都与 二的整数次幂有关。那么找到最大的,在[l,r] 中的 2k

分成两个区间来看。[l,2k1][2k,r].

第一个区间能得到答案为 [l,2k1],原因显然。第二个区间能得到的答案为 [2k,2k+2p+11]pk 之后,R 中的第一个 1)因为 k 位置上的 1 固定不变了,可以忽略,那么就和特例2是一样的。这样两个答案合并起来就是[l,2k+2p+11]

但是对于[l,2k1][2k,r],还两边都取来进行 or操作。在第一个区间的答案 [l,2k1] 中取一个数 x,另一个区间的答案 [2k,2k+2p+11] 中取一个数 y. xy 的所有不同的解就是新产生的答案。计算一下可以得到这个区间为 [L+2k,2k+11]。简单解释一下 L+2k=L|2k2k+11=(2k1)|2k。中间的那些数显然也可以取到,不再赘述。

所以最终的答案就是 [L+2k,2k+11][l,2k+2p+11] 的长度。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
	freopen("or.in","r",stdin); 
	freopen("or.out","w",stdout); 
    ll l,r;
    scanf("%lld %lld",&l,&r);
    if(l==r){
        printf("1");
        return 0;
    }
    int k=62,p=62;
    for(k=62;k>=0;k--){
        if(l&(1ll<<k))    continue;
        if(r&(1ll<<k))    break;
    }
    for(p=k-1;p>=0;p--){
        if(r&(1ll<<p))    break;
    }
    ll t=(1ll<<k)+(1ll<<(p+1))-1ll;
    ll ans=t-l+1;
    ll tot=(1ll<<(k+1))-1ll;
    ll L=(l+(1ll<<k));
    if(L<=t)	ans+=tot-t;
    else{
		ans+=tot-L+1ll;
	}
    printf("%lld",ans);
    return 0;
}

二、高一高二联合NOIP模拟赛12

T1:返乡(home)\

一组数,表示同一个人的三个成绩。

首先有一个结论:无论是不是最优解,在一种解下,每组数的和是相等的。设和为 x. 和为 x 的每一组数都是合法的。自行证明。

所以只用找到这个 能使答案最大的 x.用 dp 什么的可以搞出来。然后输出和为 x 的每一组就可以了。

简单的写法

在考场上思路:

已知若两维的数从 [l,r],那么没有二维偏序的组数为 rl+1.不再赘述。对于三维的第一维,确定之后,构造一个合法的 [l,r]就可以。设 mid=n2.第一维为 1 时,取的区间为 [mid,n],第一维为 2 时,取的区间为 [mid1,n],.....,第一维为 mid 时,取的区间为 [1,n]。第一维为 mid+1 时,取的区间为 [1,n1],第一维为 mid+2 时,取的区间为 [1,n2]...第一维为 n 时,取的区间为 [1,nmid+1]

上面是 nmod2=1 的情况,nmod2=0 的情况略有不同。赛后发现其实就是每一组数的和都为 32n.不会证。

Code:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n;
void write(int x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
int a[605];
int main(){
   freopen("home.in","r",stdin);
   freopen("home.out","w",stdout);
    scanf("%d",&n);
    n++;
    int x=(n+1)/2;
    int sum=0;
    int t=x;
    for(int i=1;i<=x;i++){
        t--;
        a[i]=n-t;
    }
    t=n-1;
    for(int i=x+1;i<=n;i++){
        a[i]=t;
        t--;
    }
    for(int i=1;i<=n;i++){
    	sum+=a[i];
//    	printf("%d\n",a[i]);
	}
	// printf("%d\n",n-1);
	printf("%d\n",sum);
	t=x;
	for(int i=1;i<=x;i++){
		int s=n-a[i]+1;t=n;
		while(s<=n&&t>=1){
			write(i-1),putchar(' '),write(s-1),putchar(' '),write(t-1),putchar('\n');
//			printf("%d %d %d\n",i-1,s-1,t-1);
			s++,t--;
		}
	}
	for(int i=1;i<=n-x;i++){
		int s=1;t=n-i;
		while(s<=n&&t>=1){
			write(x+i-1),putchar(' '),write(s-1),putchar(' '),write(t-1),putchar('\n');
//			printf("%d %d %d\n",x+i-1,s-1,t-1);
			s++,t--;
		}
	}
    return 0;
}

T2:连接(connect)

开口的:指截断了某一段钢材的选法

封闭的:没有截断任意一段钢材的选法

我们首先考虑,最优的区间,一定不是两侧都可以随便选得。(否则我可以看左侧密度大还是右侧密度大来调整)。同时如果我们开始选某一段,但是没选完,当且仅当我们是在凑质量 L 或者 R,不然我为啥不选完.

这样就可以说明只有 O(n) 段一端开口的。这个枚举左端点/右端点直接做。

剩下的都是两端封闭的情况。设左端点为 l,右端点为 r。选取的钢材总密度就是 sumrsuml1urul1. sum 是前缀重量,u 是前缀长度。之前求这一坨的最大值不好求,考虑分数规划。(srds我也不知道是什么大概就是二分答案再移项)设二分值为 mid,要找到一个 sumrsuml1urul1>=mid。所以 mid×ulsuml>=mid×ursumr.枚举每一个 l 可以求出这个 l 对应的右端点合法的区间 [a,b] ( 根据 L,R 的限制 )。现在要找到 [a,b] 中最小的mid×ursumr. 可以用 ST 表等等,但是要多一个 log。另一种方法:当 l 增加时,a,b 都是单调递增的,所以相当于滑动窗口问题,用一个单调队列来维护即可。

三、高一高二联合NOIP模拟赛13

T1.特种训练\

整个序列只由两种字符构成,考虑把序列分为若干段 “L”,和若干段“R”。

分析可得:

a[i]R,他就会一直往右跳,直到遇到一个L,就往回走,走到开始的位置。并且,原序列中,右边的第一个L会被改为 R,这个L 左边的所有 R 不变。

左边也差不多,向左遇到的第一个 R 会被改为 L,这个 R 右边的 L 不变。

当左边没有 R 或者右边没有 L 时,就会结束锻炼。

直到了怎么跳,答案就很好求了,答案跟 min(左边的 R 数量,右边的 L 数量) 有关。

但是细节有点多,要去判断一下先往哪边跳之类的。

T2.

T3.

T4.车站爆破(bomb)

考虑分块。对于每一个块,需要维护的是:1.这个块会删除之前的块中的多少个元素 cnt 2.这个块如果不考虑右面的块对它的影响(可能会删除最后的一些元素),的前缀和。

每一次修改操作,只会更改一个块。

每一次询问,只需要遍历每一个块。对于一个块,设它 不考虑右面的块对它的影响 时,会有 s 个元素保留到下一个块。那么一个块相当于:先删除栈中的 cnt 个元素,然后加入 s 个元素。维护每一个块保留在栈中的元素个数(显然保留下来的只会是sum内靠前的元素),遍历完之后利用 sum 计算。

四、高一联合NOIP模拟赛14

T1.集合\

对子集进行分析:发现子集的个数很多 ( 2n1 ),但是子集的范围很小 ( [0,n×(n+1)2] )。

所以以每一个 子集 为 单位,统计 子集和 为 s 的子集个数 cnt[s]。那么最后答案就是 n×(n+1)2s=1scnt[s]

T2.出租

引用原题解:

考虑什么时候是无解的。当出现任意一段区间 [l,r] 的租户满足它们人数的和比 k(rl+1+d) 还多的时候,说明无论如何也无法给 [l,r] 中的所有人都安排房间。我们对这个式子进行作差,得到 rl(valk)>k×d, val 就是当前位置的人数。可以这样理解: [l,r] 这些人可以被分配到 [l,r+d] 这些位置,每个位置 k 个人,那么总共就能够装下 k×(rl+1+d) 个人。将这个式子拆分成 k×(rl+1)+k×d,其中左边 k×(rl+1) 是变量(因为我们不确定 l,r 的值,对本题来说,每一个 l,r 都需要满足要求),左边的值和 [l,r] 的已有租户人数作差,看看差值是否超过 k×d,如果超过,则说明无法满足。综上:用线段树维护最大子段和,然后和 k×d 比大小即可。

T3.

T4.跳棋

分成两个子问题:

  1. 怎么填 ?
  2. 对一个确定的序列,怎么算答案。

下文的状态即指填好 ? 后的序列。

因为 n 的范围显然不支持我们把每一种 状态 枚举出来然后算答案。所以,应该每一种状态之间有关联,或者说,一些状态的答案是相同的。

先分析性质:

到底是怎么跳的?怎么转化?011100 -> 010110,或者011100 -> 110100,发现相邻的两个 1,其中一个跳 的过程,可以视为两个 1 一起平移。任意两个 0 之间, 1 的个数的奇偶性不变

把两个相邻的 1 叫为 “双1”,那么,如果一种状态确定了,“双1” 和 0 可以随意交换位置,但是单个的 1 不变。设 “双1” 个数为 a, 0 个数为 b。这个状态会产生的答案为 Caa+b

所以说,ab 都相同的状态,它们的答案是一样的,所以我们需要求出,对于每一对不同的 a,b,“双1” 个数为 a,0 个数为 b 的状态数。

用dp实现。设 dp[i][j][k][0/1] 为 前 i 个数,有 j 个“双1”,k 个 0 ,第 i 个数是否能与 i+1 一起成为 “双1” 的状态数。

转移见代码。

五、高一高二联合NOIP模拟赛15

T1.多线并行

蚌埠了

双指针

六、高一联合NOIP模拟赛16

T1.六出祁山

dp[i][j] 表示前 i 座山,最后一个高度为 j 的答案

优化:j 很大,所以考虑丢弃对最终状态没有影响的那些dp状态。打表可以找到,或者贪心地分析一下,可以发现dp[i][j] 有价值 仅当 j=a[i]+k*d (1<=i<=n, -d<=k<=d) .

七、高一高二联合NOIP模拟赛17

T1.朝汐(tide)

维护菱形。

菱形和菱形的交还是菱形

曼哈顿距离转切比雪夫距离;

或者可以理解为 维护 菱形的四条斜边信息(斜率为-1或1)

T2.泸沽寻梦(dream)

O(nu=1depu) 可以拿很多分,很好打。被卡的点是链,特判就过之。

正解:线段树合并

八、高一高二联合NOIP模拟赛18

T1.征服\

水之

T2.source

先搞清楚 最优策略,然后从这个策略里面找性质.

九、高一高二联合NOIP模拟赛19

T1.路径\

DAG上的DP

T2.异或

  1. 把区间整体异或 改为 在差分数组上每次修改。

  2. 找最优策略:分析策略的上下界,这道题,如果要连续删除 x 个数,最大操作数为 x,最小操作数为 x1 (当这 x 个数的异或和为 0 的时候)

十、高一高二联合NOIP模拟赛20

T1.表达式\

与或非性质

T2.扩张

当关于集合的DP转移方程与集合内单个元素无关

若要计算 dpT

朴素的转移是:枚举每一个 T 的子集 S

优化:计算延后

定义 dpT,i 为当集合 T 中有 i 个数已经被计算贡献的答案

dpS,i=min(dpS,i,dpT,i)

dpS,siz(S)=min(dpS,siz(S),dpT,i+getw((T,i),(S,siz(S)))

T3.搞搞新意思

  • 要分析树上的直径:可以把直径拉直,原来的树就变成了这样:

然后再这个图上分析,也许会清楚一些

  • 多次询问数组(经过某些操作)与一个变量的 max,可以把数组排序。
posted @   bwartist  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示