Logo lxn 的博客

博客

标签
暂无

2023公益班题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-07-06 17:17:15

2023编程公益班期末考试题解

T1:

加法,求和,不解释

T2:

注意i<=j<=k

方法1 :三重循环枚举,超时。

方法2:减少枚举量

选择枚举中间,可以并列枚举左侧赵最大xai,右侧找最大zak;时间复杂度o(n*n)

long long x,y,z ,n,a[200009],ans;

int main()
{
	cin>>n>>x>>y>>z;
	
	ans=-3*1e18;
	for(int i=1;i<=n;i++){
		cin>>a[i];
			
	}
	for(int j=1;j<=n;j++){
		long long l=-2e18,r=-2e18;
		for(int i=1;i<=j;i++)l=max(l,x*a[i]);
		for(int k=j;k<=n;k++)r=max(r,z*a[k]);
		ans=max(ans,l+a[j]*y+r);
	} 
	
	cout<<ans<<endl;
}

方法三:

方法二内的枚举有大量重复计算。

for(int j=1;j<=n;j++){
		long long l=-2e18,r=-2e18;
		for(int i=1;i<=j;i++)l=max(l,x*a[i]);\/\/改成提前枚举前缀最大值。
		for(int k=j;k<=n;k++)r=max(r,z*a[k]);\/\/改成提前处理后缀最大值。
		ans=max(ans,l+a[j]*y+r);
	} 

参考代码:

long long x,y,z ,n,a[200009],f[200009],g[200009],ans;
int main(){
	cin>>n>>x>>y>>z;
	memset(f,0x80,sizeof(f));
	memset(g,0x80,sizeof(g));
	ans=-3*1e18;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		f[i]=max(f[i-1],a[i]*x);	
	}
	for(int i=n;i>=1;i--)
		g[i]=max(g[i+1],a[i]*z);
	for(int i=1;i<=n;i++){
		ans=max(ans,f[i]+a[i]*y+g[i]);
	}	
	cout<<ans<<endl;
}

方法四:

可以进行3次前缀最值。

#include<bits\/stdc++.h>
using namespace std;
long long x,y,z,a,n,p,q,r;
int main() {
	cin>>n>>p>>q>>r;
	x=y=z=-3*1e18;
	for(int i=1; i<=n; ++i) {
		cin>>a;
		x=max(x,a*p);
		y=max(y,x+a*q);
		z=max(z,y+a*r);
	}
	cout<<z;
}

T3:

我们可以把题目看成一个栈,而题目的要求就是在这个栈里面进行入栈、出栈和查询的工作。

  • 解题思路: 我们设f[i]为栈中从下到上的i个元素中的最大值,当我们加入一个新元素x时,t++,由于多了一个元素,所以f[t]=max(f[t-1],x)。那么在出栈时只要输出f[t-1],在查找时只要输出f[t]。

T4:

本题修改自:一平学长讲课练习题ABC289C

深度优先搜索,子集枚举,广度优先搜索等做法都可以。

T5:马走日

本题的一次行走过程是宽度优先搜索BFS。每次行走都BFS的化,时间复杂度O(LLN)只能解决一般的数据,另外一半超时。

实际这个题目我们只需要计算马的相对位置即可。运用一次BFS,后面直接对马的两对坐标求差值,o(1)查询。时间复杂度O(L*L+N);

参考代码;

#include <bits\/stdc++.h>
using namespace std;
#define maxn 1010
\/\/ a:从(1,1)到目标坐标需要的最少步数,vis:访问标志
int a[maxn][maxn], vis[maxn][maxn],l;
int dx[] = {1, 2, 2, 1, -1, -2, -2, -1}, dy[] = {2, 1, -1, -2, -2, -1, 1, 2};
queue<pair<int,int> > q;  \/\/ q:坐标队列
void bfs()
{
    while (!q.empty())
    {
        int x = q.front().first, y = q.front().second;
        q.pop();
        for (int i = 0; i < 8; ++i)
        {
            int nx = x + dx[i], ny = y + dy[i];
            if ( nx >0 && nx <=l && ny >0 && ny <=l && vis[nx][ny] == 0)
            {
                a[nx][ny] = a[x][y] + 1;
                q.push(make_pair(nx,ny));

                vis[nx][ny] = 1;
            }
        }
    }
}
int main()
{
   
    memset(a, 0, sizeof a);
    \/\/ (1,1)进入队列,并初始化
    q.push(make_pair(1,1));
    vis[1][1] = 1;
    a[1][1] = 0;
    \/\/ 搜索(1,1)到所有坐标的最少步数
   

    int T;
    scanf("%d%d",&l, &T);
     bfs();
    while (T--)
    {
        
        int ax, ay, bx, by;
        scanf("%d%d%d%d", &ax, &ay, &bx, &by);
        \/\/ 获取坐标距离
        int x = abs(ax - bx) + 1;
        int y = abs(ay - by) + 1;
        if(vis[x][y])printf("%d\n", a[x][y]);
        else printf("-1\n");
    }
    return 0;
}

T6:返程之旅

学过图论的同学,可以发现这是最短路的变形题目。

注意到是单源最短路,且没有负边权,选择 Dijkstra。在城市的停留的时间可以丢进路线的时间里,具体操作如下:每条路的边权=原来的边权+目的地的点权。这样就转化成了一个 Dijkstra 板子题。

T7:瘦身迷宫

类似于求最短路径的问题,可以使用 bfs 讨论。

定义队列 q,含有四个元素:x 和 y:当前小容坐标、Time:当前时刻、size:小容身材

每次取出队首并向四个方向以及不动拓展,并判断当前位置是否走过以及这个位置小容能否站的下(小容的占地区域内有没有障碍物),如果站的下就入队,入队时判断一下小容身材的情况。

  • 优化1.及当小容占地是 1×1的时候可以不用判断小容原地不动的情况,此时站着不动是无意义的举措,因为无论怎样走都不会有障碍物遮挡他。
  • 优化2.只有当身体缩小才能继续移动的时候等待,其余的点不用等待。

T8:隔离点

方法一:

直接考虑原问题比较困难,我们可以这么想:删去的最少=留下来的最多。

那么我们考虑用类似于最小生成树的思想。在使用Kruskal算法时,并查集还要保存一个是否连接感染病毒的城市。然后合并的时候必须要两个集合不是都有病毒的城市(最多只有一个集合有病毒的城市)才可以合并。

方法二:

树形dp

20230818比赛题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-08-26 17:25:24

T4

100pts:

按二进制位枚举,对于第j位:

设$\sum _{l_1 \le r_1 \le i} XOR(l_1,r_1)$的值为$f1[i][j]$ 。如果第j位为0,$f1[i][j] = f1[i-1][j] + sum1[i][j] \times 2^j$,如果第j位为1,$f1[i][j] = f1[i-1][j] + sum0[i][j] \times 2^j$ ,其中$sum0/1[i][j]= \sum _{l \le i} XOR(l,i)$,只考虑第j位。

然后把$f1$的每一位综合起来,得到$f1[i]= \sum f1[i][j]$

设$\sum _{l_1 \le r_1 < l_2 \le r_2 \le i} XOR(l_1,r_1) \times XOR(l_2,r_2)$的值为$f2[i][j]$ 。如果第j位为0,$f2[i][j] = f2[i-1][j] + sum1[i][j] \times 2^j$,如果第j位为1,$f2[i][j] = f2[i-1][j] + sum0[i][j] \times 2^j$ ,其中$sum0/1[i][j]= \sum _{l \le i} f1[l-1] \times XOR(l,i)$,只考虑第j位。

然后把$f2$的每一位综合起来,得到$f2[i]= \sum f2[i][j]$

设$\sum _{l_1 \le r_1 < l_2 \le r_2 < l_3 \le r_3 \le i} XOR(l_1,r_1) \times XOR(l_2,r_2) \times XOR(l_2,r_2)$的值为$f3[i][j]$ 。如果第j位为0,$f3[i][j] = f3[i-1][j] + sum1[i][j] \times 2^j$,如果第j位为1,$f3[i][j] = f3[i-1][j] + sum0[i][j] \times 2^j$ ,其中$sum0/1[i][j]= \sum _{l \le i} f2[l-1] \times XOR(l,i)$,只考虑第j位。

然后把$f3$的每一位综合起来,得到$f3[i]= \sum f3[i][j]$,即为答案

另一种统计答案的方式是正着求$f2$,倒着求$f1$,然后枚举$r_2$的位置。

70pts:

设$\sum _{l_1 \le r_1 \le i} XOR(l_1,r_1)$的值为$f1[i]$ 。

$n^2$正着倒着分别求$f1$,然后$n^2$枚举$l_2$,$r_2$即可。

t5

  • 25pts:N^3 枚举 (以哪个为根,dfs,枚举颜色)
  • 50pts:N^2 枚举 (优化颜色枚举)
  • 菊花:记录所有颜色的数量,对每个颜色计数
  • 链:对每个颜色维护一个序列,复杂度O(n),分别计数
  • 100pts:对每个颜色维护一个虚树,动态规划
    • 设 $dp[u][0/1/2]$ 表示u到u的子树中路径包含0~2条关键边的点数
    • 记录答案

20230826考试题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-08-26 17:26:56

原题链接

第一题:原创签到

第二题:https://ac.nowcoder.com/acm/contest/57360/C

第三题:https://ac.nowcoder.com/acm/contest/57360/B

第四题:https://acm.hdu.edu.cn/showproblem.php?pid=7308

第五题:https://acm.hdu.edu.cn/showproblem.php?pid=7322

第一题 T310720 宝石分配

给出 $N,M$,求最大的 $x$ 满足 $x|N$ 且 $x \le M$。

$O(\sqrt n)$ 枚举 $N$ 的因数即可。

第二题 T372575 数学高手也会爱上毒瘤数学题2

求 $1!! \times 2!! \times 3!! \times \cdots \times n!!$ 的后缀 $0$ 的个数。

因为质因数分解后 $2$ 的次数一定大于等于 $5$ 的次数,因此可以转化为求质因数分解后 $5$ 的次数。

分奇偶双阶乘进行讨论,对于奇数的双阶乘,可以发现 $5!!、6!!\cdots$ 都包含一个 $5$,$10!!、11!!\cdots$ 都包含一个 $10$,这样推下去,会发现$5,10,15,20,25,30 \cdots$ 出现的次数分别是(设最大的奇数为 $n$) $n-4,n-9,n-14,n-19 \cdots$,等差数列求和即可。

做到这里会发现一个问题,就是像 $25$ 这种对质因数$5$的次数的贡献为 $2$,因此,我们可以递归地计数。设上面的等差数列求和过程为函数 $calc(n,5)$,则将$calc(n/5,25),calc(n/5/5,125) \cdots$ 也统计进答案即可。(这样可以保证因数 $5^k$ 被统计了 $k$ 次)

最后用 $\_\_int128$ 统计答案,时间复杂度 $O(logn)$。

第三题 T372576 占卜

首先可以发现性质,对于两个大小相等的可重集合,想要计算可信度,可以将两个集合分别排序,然后将对应位置的差的绝对值加起来就是这两个集合的可信度。

排序之后,有两种方法统计答案:

方法一:

$ans = \sum_{1 \le i \le n} \sum_{1 \le j \le n} [\sum_{0 \le k < min(i,j)} (^{i-1}k) (^{j-1}_k)][\sum{0 \le k \le min(n-i,n-j)} (^{n-i}_k) (^{n-j}_k)] |a_i-b_j|$

利用组合恒等式 $\sum_{0 \le k \le min(n,m)} (^{n}k) (^{m}_k) = (^{n+m}{n})$ 化简,得

$ans = \sum_{1 \le i \le n} \sum_{1 \le j \le n} (^{i+j-2}{i-1})(^{2n-i-j}{n-i}) |a_i-b_j|$

预处理组合数统计答案即可。

如果不会组合恒等式也可以用 $cnt[i][j]$ 表示从第一个集合的前$i$个元素和第二个集合的前$j$个元素中选取的方案总数,然后DP转移即可。

方法二:动态规划

设$f[i][j]$表示考虑第一套卡牌的前 $i$ 张和第二套卡牌的前 $j$ 张,所产生的所有答案的和。

转移 $f[i][j]=f[i-1][j]+f[i][j-1]+cnt[i-1][j-1] \times |a_i-b_j|$

其中 $cnt[i][j]=cnt[i-1][j]+cnt[i][j-1]$

两个方法的时间复杂度均为 $O(n^2)$

第四题 T372577 什么是羁绊

每张卡牌有三个指标 $a_i,b_i,c_i$,每张卡牌可以进行一次升级或不升级,升级之后变成$a_i',b_i',c_i'$

计算 $max(\max_{1\le i\le n}a_i-\min_{1\le i\le n}a_i,\max_{1\le i\le n}b_i-\min_{1\le i\le n}b_i,\max_{1\le i\le n}c_i-\min_{1\le i\le n}c_i)$ 的最小值

做法:首先令所有卡牌都不升级,然后统计一次答案。在统计答案的时候,找到产生当前答案的瓶颈所在的卡牌。如果该卡牌没有被升级过,可以确定的是,如果接下来不升级这张卡牌,答案一定不会更优,因为瓶颈没有消除。

所以可以贪心地每次找到当前处于瓶颈的卡牌(如果不能升级就直接退出,因为之后的答案不会更优了),然后进行升级,用当前的答案尝试更新最终的答案。

这样最多升级 $n$ 张卡牌之后,就获得了本题的答案。

在升级过程中需要使用多个 $set$ 对升级过和没升级过的卡牌进行维护和取最大最小值等操作,复杂度 $O(nlogn)$

第五题 T372578 逛校园

给出 $n$ 个点的有向带权图,统计最小环的个数和长度。

$floyd$ 跑最短路+最短路计数,用 $dis[i][j]$ 表示从 $i$ 到 $j$ 的最短路,用 $cnt[i][j]$ 表示从 $i$ 到 $j$ 的最短路条数。

考虑 $floyd$ 的三重循环的意义,最外层枚举到 $k$ 的时候,当前的 $dis$ 和 $cnt$ 都表示除了端点只经过编号小于等于 $k$ 的点的情况下的最短路和最短路条数。为了防止重复找环,$floyd$ 最外层枚举到 $k$ 的时候,只将经过 $k$ 的环统计进答案,即枚举编号小于 $k$ 的所有点 $i$,尝试用 $dis[i][k]+w[k][i]$更新答案。

更新答案的意思是:如果当前记录的最小环的大小大于 $dis[i][k]+w[k][i]$,就直接更新最小环的大小,并令最小环的个数等于 $cnt[i][k]$;如果当前记录的最小环的大小等于 $dis[i][k]+w[k][i]$,就只令当前记录的最小环的个数加上 $cnt[i][k]$;其它情况不更新。

本题为 $floyd$ 的深度理解与应用,时间复杂度 $O(n^3)$

20230917普及组模拟赛题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-09-17 19:28:54

T1:

最大公约数与最小公倍数。注意数据范围,要用$long long$ .

#include <bits\/stdc++.h>
using namespace std;
long long gcd(long long a,long long b)
{
    if(b==0) return a;
    else return gcd(b,a%b);
}
long long lcm(long long a,long long b)
{
 return a*b\/gcd(a,b);
}
int main()
{
	long long a,b,c,i;
	cin>>a>>b>>c;
	i=lcm(a,b);
	cout<<lcm(i,c);
	return 0;
 } 

T2:

\/*细节
判定无解
本题中答案为 
0
0 不代表无解,如

1 0
0 114514
有解且答案为 0.
因此可以将 
ans 预设为 −1,程序结束时ans 若仍为 −1 则无解。*\/
#include<bits\/stdc++.h>
#define N 1000001
using namespace std;
typedef long long ll;
ll n,k,x[N],ans=-1;
bool flag;
int main(){
    cin>>n>>k;
    for(ll i=1,a,b;i<=n;i++){
        cin>>a>>b;
        x[a]+=b;
    }
    for(ll i=0;i<N-k;i++){
        if(!(x[i]&&x[i+k])||(k==0&&x[i]<2))continue;
        if(k==0)ans=max(ans,x[i]*i);
        else ans=max(ans,min(x[i+k],x[i])*(2*i+k));
    }
    if(ans==-1)cout<<"NO"<<endl;
    else cout<<ans<<endl;
    return 0;
}

T3

#include<bits\/stdc++.h>
#define ll long long
#define endl '\n'
using namespace std;
const int N=2e5+10;
struct node{
	int k,id;
}a[N];
int n,b[N],c[N],ft[N],bk[N];
bool cmp(node x,node y){
	return x.k<y.k;
}
int main(){
	
	cin>>n;
	for(int i=1;i<=n+1;i++) cin>>a[i].k,a[i].id=i;
	for(int i=1;i<=n;i++) cin>>b[i];
	sort(a+1,a+n+2,cmp);
	sort(b+1,b+n+1);
	for(int i=1;i<=n;i++) ft[i]=max(ft[i-1],max(0,a[i].k-b[i]));
	for(int i=n;i>=1;i--) bk[i]=max(bk[i+1],max(0,a[i+1].k-b[i]));
	for(int i=1;i<=n+1;i++) c[a[i].id]=i;
	for(int i=1;i<=n+1;i++) cout<<max(ft[c[i]-1],bk[c[i]])<<' ';
	return 0;
}

T4:P6394 樱花,还有你

题目来源:P6394 樱花,还有你

924普及组——提高-模拟赛题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-09-24 10:10:49

T1幸运字符:

考察点:字符串。入门题目。

方法: 两种情况: 找出连续的"vv",把第二个v替换为k即可。 找出连续的"KK",把第一个k替换为v即可。

有同学用了$o(n^2)$的算法。 参考代码:

#include<bits\/stdc++.h>

using namespace std;

#define int long long
#define endl '\n'

int n;
string s; 

int ans = -1;

char change(char ch){
	if(ch == 'V') return 'K';
	if(ch == 'K') return 'V';
}
int sum(){
	int res = 0;
	for(int i = 0; i < n-1; i ++){
		if(s[i] == 'V' && s[i+1] == 'K'){
			res++;
		}
	}
	return res;
}

signed main(void){
	cin >> n;
	cin >> s;
	int cnt = sum();
	ans = max(ans, cnt);
	for(int i = 0; i < n; i ++){
		s[i] = change(s[i]);
		int cnt = sum();
		ans = max(ans, cnt);
		s[i] = change(s[i]);
	}
	cout << ans << endl;
	return 0;
}

实际这个题目$o(n)$的算法即可以解决。 第一遍先找VK,找到的打标记。,第二遍找没打标记的连续“VV”或“KK”。

参考代码:

#include<bits\/stdc++.h>

using namespace std;

#define int long long
#define endl '\n'

int n,ans;
string a;


signed main(void){
	cin >> n;
	cin >> a;
	for(int i=0;i<n-1;i++)
		if(a[i]=='V'&&a[i+1]=='K')ans++,a[i]=a[i+1]='.';
	for(int i=0;i<n-1;i++){
		if(a[i]!='.'&&a[i]==a[i+1]){
			ans++;break;
		}
	}
	cout<<ans<<endl;
	return 0;
}

T2 慢跑

考察点:模拟、数学。 在这个超长跑道上。起始坐标大、速度慢的会挡住起始坐标小速度快的人。

有同学贪心,先计算出T时刻后的最终速度。看坐标小的会不会被下一个坐标大的挡住,这样的方法是错的,因为下一个坐标大的速度也可能被前面的挡住,直接计算的最终坐标点并不是其真正的坐标点。例如新增加的第二组样例数据。

  • 方法一:根据坐标的大小逆序来,记录逆序的最小真正终点。 参考代码:
\/\/ liqianzuo
#include<iostream>
#include<cstring>
using namespace std;
const int N=1e6;
long long n,t;
long long lo[N+3],loc,v;
int main()
{
	scanf("%d%d",&n,&t);
	for(int i=0;i<n;i++)
	{
		scanf("%d%d",&loc,&v);
		lo[i]=loc+v*t;
	}
	long long ans=1;
	for(int i=n-1;i>0;i--)
	{
		if(lo[i-1]>=lo[i])
		{
			lo[i-1]=lo[i];
		}
		else
		{
			ans++;
		}
	}
	cout<<ans;
	return 0;
}
  • 方法二: 也可以正序做,但是要把前面挡住的作为并查集来处理 例如A-B,B-C,那么他们就组成一个集合。 参考代码:
\/\/刘chenkai
#include<bits\/stdc++.h>
#include<unistd.h>
#define ll long long
#define ull unsigned long long
#define uint unsigned int
#define pii pair<int,int>
#define mp(a,b) make_pair(a,b)
#define endl '\n'
using namespace std;
const int N=1e5+1;
const ull INF=0x7f7f7f7f7f7f7f7f;
int fa[N];
bool b[N];
void init(int n)
{
	for(int i=1;i<=n;i++) fa[i]=i;
}
int find(int i)
{
	return fa[i]==i?i:fa[i]=find(fa[i]);
}
void unionn(int i,int j)
{
	fa[find(i)]=find(j);
}
struct node
{
	ll x,v;
	bool operator < (node b)
	{
		return x<b.x;
	}
}a[N];
inline ll read()
{
	ll x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x;
}

int main()
{
	ll n=read(),T=read(),ans=0;
	ull num1=INF,num2=INF;
	init(n);
	for(int i=1;i<=n;i++)
	{
		a[i].x=read();a[i].v=read();
	}
	for(int i=n;i>=1;i--)
	{
		if(i%2==0) 
		{
			num2=a[i].x+a[i].v*T;
			num2=min(num1,num2);
			if(num2>=num1) unionn(i,i+1);
		}
		else 
		{
			num1=a[i].x+a[i].v*T;
			num1=min(num1,num2);
			if(num1>=num2) unionn(i,i+1);
		}
	}
	for(int i=1;i<=n;i++)
	{
		if(!b[find(i)])
		{
			b[find(i)]=1;
			ans++;
		}
	}
	cout<<ans<<endl;
	return 0;
}	

T3 子段和

枚举子段区间$[l,r]$,再计算修改,有$o(n^3)$暴力算法。

不带修改:枚举子段区间$[l,r]$,利用前缀和,有$o(n^2)$算法。

最大子段和: 动态规划:当前点和前面的连接更优秀吗?更优秀和前面连,否则就自己开始。

  • 设$f[i]$为以$i$为结束点的最大子段和长度。
  • 那么 $f[i]=max(f[i-1]+a[i],a[i])$
  • 最终的结果为: $ans=max(f[i]),1\leq i \leq n$;
  • 时间复杂度为$o(n)$

最大子段和再变形,只有一次修改机会,调用$n$次最大子段和算法即可。

参考代码:

\/\/zhou zu hao
#include<bits\/stdc++.h>
using namespace std;
int n,a[100005],x,dp[1006],maxx=-0x3f3f3f3f;
signed main(){
	cin>>n>>x;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int j=1;j<=n;j++){
		int kun=a[j];
		a[j]=x;
		for(int i=1;i<=n;i++){
			dp[i]=max(dp[i-1]+a[i],a[i]);
			maxx=max(maxx,dp[i]);
		} 
		a[j]=kun;
	} 
	cout<<maxx;
	return 0;
} 
  • 如果数据范围更大,比如$n=10^5$怎么解决?

还有$o(n)的算法$ 设$dp[i][0/1]$为打当前点是否用了这次修改机会的最大值。

\\\liu xi
#include <bits\/stdc++.h>
using namespace std;
#define ll long long
#define FOR(i,begin,end) for(ll i=(begin);i<=(end);++i)
const ll maxn=1e6+5;
inline ll read(){
	ll f=0, k=1; char ch=getchar();
	while(ch<'0' || ch>'9'){ if(ch=='-') k=-1; ch=getchar();}
	while(ch>='0' && ch<='9'){ f=(f<<1)+(f<<3)+(ch-'0'); ch=getchar();}
	return f*k;
}
ll n, x, a[maxn], dp[maxn][2], ans;
int main(){
	n=read();
	x=read();
	FOR(i,1,n){
		a[i]=read();
	}
	dp[0][0]=0;
	FOR(i,1,n){
		dp[i][0]=max(dp[i-1][0]+a[i],a[i]);
		dp[i][1]=max(dp[i-1][0]+x,max(dp[i-1][1]+a[i],x));
		ans=max(dp[i][1],ans);
	}
	cout << ans;
	
	return 0;
}

T4修栅栏

数学知识:

在长度确定的情况下,如何构成的长方形面积最大?

长宽越接近,面积越大?你会证明吗?

结论

按照长度排序,用长度尽可能接近的木棍来修栅栏。 要找相同数目的,数据范围$10^6$首选桶排序。

桶内数值为偶数的直接使用。 用不上的只能是奇数中的1个,就把这1个就削去1,放入下一个桶,因为木棍只可以被削1次,因此还要做一个削后木棍数量的标记,防止重复。

参考代码:

\/\/李jinhan
#include<iostream>
using namespace std;
long long a[1000050],mp1[1000050],mp2[1000050];
long long b[1000050],cnt=0;
int main()
{
	int n;
	long long maxx=0;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		mp1[a[i]]++;
		maxx=max(maxx,a[i]);
	}
	for(int i=maxx;i>=1;i--)
	{
		if((mp1[i]+mp2[i])%2==1&&mp1[i]>0) mp1[i]--,mp2[i-1]++;
		\/\/for(int j=1;j<=((mp1[i]+mp2[i])\/2);j++) b[++cnt]=i;
		while((mp1[i]+mp2[i])>=2)
		{
			if(mp1[i]) mp1[i]--;
			else mp2[i]--;
			if(mp1[i]) mp1[i]--;
			else mp2[i]--;
			b[++cnt]=i;
		}
	}
	long long ans=0;
	\/\/cout<<cnt<<'\n';
	\/\/for(int i=1;i<=cnt;i++) cout<<b[i]<<' ';
	for(int i=1;i<cnt;i+=2)
	{
		long long x=b[i];
		long long y=b[i+1];
		ans+=x*y;
		\/\/cout<<"ans+="<<x<<'*'<<y<<'\n';
	}
	cout<<ans;
	return 0;
}

T5 飞行

图论题目。最短路,而且是边权为固定值的最短路,可以用BFS。 可以跑从$n$到其他所有点的最短路。 然后枚举中转点计算。设其AB单独走到C点,再从C点到N点。

924提高组模拟赛

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-09-24 14:36:09

T2 优美区间

区间问题,区间[l,r]都是ai的倍数,就是最小公倍数是a[i]

方法1.区间最小公倍数可以进行区间合并。$st$表可以解决。 然后再通过二分答案找到最大长度。 最后枚举最大长度,输出答案。时间复杂度$o(nlog^n)$。

方法2.找到一个数$a[i]$可扩展的左侧区间和右侧区间。 怎样快速的找到?

例如:4 4 2 6 8 6 9 3 9 12 8

当$a_i$向右扩展到最后一个可扩展的数$a_j$后,$a_{i+1}..a_j$没有必要再扩展。直接跳到$j+1$即可。所以一个数被向同一个放心扩展的时候,不管是作为除数还是被除数,只需要访问1次。时间复杂度$o(n)$.

方法一参考代码:

#include <bits\/stdc++.h>
#define pb push_back
using namespace std;

const int N=3e5+50,LOG_N=20; 

int gcd(int a,int b){ return !b?a:gcd(b,a%b); }
int n,f[N][LOG_N],g[N][LOG_N],lg[N],a[N];
vector<int> ans;
int qf(int l,int r){
	int k=lg[r-l+1];
	return min(f[l][k],f[r-(1<<k)+1][k]);
}
int qg(int l,int r){
	int k=lg[r-l+1];
	return gcd(g[l][k],g[r-(1<<k)+1][k]);
}
bool check(int len){
	int i;
	for(i=1;i+len-1<=n;++i){
		if(qf(i,i+len-1)==qg(i,i+len-1))return true;
	}
	return false;
}
int main(int argc,char *argv[]){
	int i,k,mid;
	scanf("%d",&n); 
	for(i=1;i<=n;++i){
		scanf("%d",&a[i]); 
		f[i][0]=g[i][0]=a[i]; 
	}
	lg[0]=-1; 
	for(i=1;i<=n;++i)lg[i]=lg[i>>1]+1;
	for(k=1;k<20;++k){
		for(i=1;i+(1<<k)-1<=n;++i){
			f[i][k]=min(f[i][k-1],f[i+(1<<(k-1))][k-1]);
			g[i][k]=gcd(g[i][k-1],g[i+(1<<(k-1))][k-1]);
		}
	}
	int l=1,r=n+1;
	while(r-l>1){
		mid=(l+r)>>1;
		if(check(mid))l=mid;
		else r=mid;
	}
	for(i=1;i+l-1<=n;++i)
		if(qf(i,i+l-1)==qg(i,i+l-1))ans.pb(i); 
	printf("%d %d\n",(int)ans.size(),l); 
	for(auto t:ans) printf("%d ",t); puts(""); 
	return 0; 
}

方法二参考代码:

#include<iostream>
#include<cstdio>
#include<queue>

using namespace std;
const int N = 300030;

int n,c[N],l[N],r[N],cnt,maxn;
queue<int>q;

int main()
{
	scanf("%d",&n);
	for(int i=1;i <= n;i++)
		scanf("%d",&c[i]);
	int x = 1;
	for(int i=1;i <= n;i = x){
		while(x <= n && c[x]%c[i] == 0) x++;
		r[i] = x-1;
	}
	x = n;
	for(int i=n;i >= 1;i--){
		if(r[i]){
			x = i;
			while(x >= 1 && c[x]%c[i] == 0) x--;
			l[i] = x+1;
			i = x+1;
		}
	}
	for(int i=1;i <= n;i++){
		if(!l[i])
			continue;
		if(r[i]-l[i]+1 == maxn)
			q.push(l[i]) , cnt++;
		if(r[i]-l[i]+1 > maxn){
			maxn = r[i]-l[i]+1 , cnt = 1;
			while(!q.empty())
				q.pop();
			q.push(l[i]);
		}
			
	}
	printf("%d %d\n",cnt,maxn);
	while(!q.empty())
		printf("%d ",q.front()) , q.pop();
	puts("");
	return 0;
}

T3

题目分析

设lowbit(x^y)=2^t

则x的t-1位和y的t-1位相同。

不断把数分组,找到对应的位数。

30\% 的数据,$o(n^2)$暴力枚举。

10\% $a_i \leq1$, 分为两个集合,0集合和1集合,ij分别属于这两个集合才可行。

10\% $a_i \leq3$, 怎么做?

分为两种情况:

$lowbit(a_i,a_j)==1$的情况

0集合和1集合,ij分别属于这两个集合才可行。 $lowbit(a_i,a_j)==10$的情况 分别处理两个集合:{10,00},{11,01}

扩展到所有情况:

lowbit(x,y) 把x和y都分解成二进制 奇数(二进制末位是1),偶数(二进制末位是0)

lowbit(x^y)=1

把奇数放到左边,假设有x个, 把偶数放到右边,假设有y个, xy2 对 lowbit起来是等于1。

lowbit(x^y)=2 (10)

x和y全奇 或者 全偶

假如x和y全奇数

二进制倒数第二位一个是0一个是1

倒数第二位是0 倒数第二位是1

1 2 3 4 5 001 010 011 100 101

001 010 011 100 101

322 * 1 = 12

011 001         010 100 101

122 * 2 = 8      112 * 2=4

101 001 112 * 4 = 8

方法一:分治, 二元组一个在左边,另一个在右边,这样的贡献是很容易统计的 对于每一个数,它只在最多31层中被分治到,总时间复杂度是$o(n*31)$

方法二:找出后t-1位一样的分成一组,再把这组内第t位为1和0的个数都找出来。计算乘积。也可以用trie树,但注意用的时候要从低位开始建树,因为我们要从低位找相同前缀。

方法一参考代码:


#include<bits\/stdc++.h>
#define MOD 1000000007
#define MAXN 999999999
#define LL long long
using namespace std;

LL a[100005],b[100005],ans;
LL n;

void work(LL m,LL t) {
	LL l,r,p,i;
	if(m<=1||t>=30) return;
	l=0;
	for(i=1; i<=m; i++)
		if(a[i]&(1<<t)) {
			l++;
			b[l]=a[i];
		}
	r=l;
	for(i=1; i<=m; i++)
		if(!(a[i]&(1<<t))) {
			r++;
			b[r]=a[i];
		}
	for(i=1; i<=m; i++) a[i]=b[i];
	ans=(ans+(l*(m-l)%MOD)%MOD*(1<<t)%MOD)%MOD;\/\/l*r
	work(l,t+1);
	p=0;
	for(i=l+1; i<=r; i++) {
		p++;
		a[p]=a[i];
	}
	work(p,t+1);
}

int main() {

	int i;

	scanf("%lld",&n);
	for(i=1; i<=n; i++)
		scanf("%lld",&a[i]);
	work(n,0);
	ans=(ans*2)%MOD;
	printf("%lld",ans);
	return 0;
}

方法二参考代码:

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include<bitset>
using namespace std;
typedef long long ll;
const int N=100009;
const ll mod=1e9+7;
int nex[N*31][2],m,n,cnt=0,flag;
ll num[N*31][2],ans=0;
int a[N],le[N],ri[N];
inline void insert(int x) {
	int l,p,y=x;
	int now=0;
	for(int i=0; i<=30; i++) {
		p=1&(x>>i);
		if(!nex[now][p]) 
			nex[now][p]=++cnt;
		num[now][p]++; 	
		now=nex[now][p];
	} 
}
void count(int u,ll t){

	if(num[u][0]&&num[u][1])
		ans=(ans+num[u][0]%mod*num[u][1]%mod*(1<<t))%mod;
		
	if(nex[u][0]){
		count(nex[u][0],t+1);
	}
	if(nex[u][1]){
		count(nex[u][1],t+1);
	}
	
} 

int main() {
	int x=0; 
	scanf("%d",&n);
	for(int i=1;i<=n;i++) {
		scanf("%d",&a[i]);
		insert(a[i]);
	}
		
	count(0,0); 
	ans=ans*2%mod;
	printf("%d\n",ans);

	return 0;
}

T4逆序对

题目分析

对于当前枚举的L,R若满足条件,则对于当前的L,当R取R~N时都满足条件。
对于当前的枚举的L,R若不满足条件,则对于当前的L,当R取L+1~R时都不能满足条件。

于是就转变成了一个滑动窗口问题,双指针维护,用树状数组算出左右指针移动对当前总值的贡献 双指针,随着l的左移,r不断左移,单调的。

两个树状数组, 一个是y:处理 r...n后缀的情况,r不断增大。 一个是x:处理 1..l,前缀的情况,l不断增大。


#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>

using namespace std;


#define LL "%lld"


#define lb(x) ((x)&(-(x)))

const int maxn=100010;

int n,z[maxn],y[maxn],x[maxn];

long long k;

bool cmp(int a,int b) {
	return z[a]<z[b];
}

void insert(int *z,int p,int d) {
	for (; p<=n; p+=lb(p))
		z[p]+=d;
}

int query(int *z,int p) {
	int ans=0;
	for (; p; p-=lb(p))
		ans+=z[p];
	return ans;
}

int main() {

	scanf("%d" LL,&n,&k);
	for (int a=1; a<=n; a++)
		scanf("%d",&z[a]),y[a]=a;
	sort(y+1,y+n+1,cmp);
	x[y[1]]=1;
	for (int a=2; a<=n; a++)
		if (z[y[a]]==z[y[a-1]]) x[y[a]]=x[y[a-1]];
		else x[y[a]]=x[y[a-1]]+1;
	for (int a=1; a<=n; a++)\/\/完成a离散化后的数据 
		z[a]=x[a];
	memset(x,0,sizeof(x));
	memset(y,0,sizeof(y));
	long long nowans=0;
	int p=n;
	while (p>=1) {\/\/第一遍逆序对 
		nowans+=query(y,z[p]-1);\/\/逆序做,查询比他小的数。nowans是所有逆序对的个数。 
		insert(y,z[p],1);
		p--;
	}
	p++;

\/\/存储当前到l的逆序对情况 
	long long ans=0;
	int l,r=1;
	for (l=1; l<=n; l++) {
		if (r==l) {\/\/,不能有l==r,因此要删掉r,同时减去对应的逆序对数目。 
			nowans-=l-1-query(x,z[r])+query(y,z[r]-1);\/\/减去和1.r中 1..r-1和人构成的逆序对个数,减去和r点和后面数据构成的逆序对个数query(y,z[r]-1) 
			insert(y,z[r],-1);\/\/删掉r 
			r++;
		}
		nowans+=l-1-query(x,z[l])+query(y,z[l]-1);\/\/1..l,r..n构成的逆序对总数 
		insert(x,z[l],1);
		while (nowans>k && r<=n) {\/\/个数太多,r要后移。 
			nowans-=l-query(x,z[r])+query(y,z[r]-1);
			insert(y,z[r],-1);
			r++;
		}
		if (nowans<=k) ans+=n-r+1;\/\/r 到n都符合条件。 
	}
	printf(LL "\n",ans);

	return 0;
}

题目分享

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-10-09 18:39:00

题目14 题目14

20231015题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-10-15 08:36:56

T1 函数

表达式基本运算与输出考察,入门难度。

注意的点:a可能为0,变为一次方程。

a!=0 bb-4a*c<0无解

bb-4a*c==0只有一个解

bb-4ac>0 两个解。 x1=(-b-sqrt(bb-4ac)/(2a)

x2=(-b+sqrt(bb-4a*c)/(2a)

bb-4a*c==0只有一个解

坑点:考察输出,四舍五入和截尾巴的处理。

参考代码:

#include<cstring>
#include<cstdlib>
#include<cmath>
using namespace std;

int k; 
double a,b,c;
double f(double x){
	int temp=int(x*10000);
	return 1.0*temp\/10000.0;
}
int main(){
	freopen("func.in","r",stdin);
	freopen("func.out","w",stdout);
	int ans;
	double x,y,dt;
	cin>>k>>a>>b>>c;
	if(a==0){
		x=-1.0*c\/(1.0*b);
		printf("%.4lf",(k==0)?x:f(x));
		return 0;
	}
	if(fabs(b*b-4*a*c)<=0.00001){
		x=-1.0*b\/(2.0*a);
		printf("%.4lf",(k==0)?x:f(x));
		return 0; 
	}
	if((b*b-4*a*c)<-0.000001){printf("WTF");return 0;}
	else {
		dt = sqrt(1.0*(b*b-4*a*c));
		x=(-b-dt)\/(2.0*a);
		y=(-b+dt)\/(2.0*a);
		if(k==0)printf("%.4lf %.4lf",x,y);
		else  printf("%.4lf %.4lf",f(x),f(y));
	}
}

T2 打怪

读题审题。

方法1:可以理解为背包。 背包的总容量就是能量单位W. 遇到攻击力xi大于a+b的选手就避过去。 时间复杂度n*w,预计得分50.

#include <iostream>
using namespace std;
const int N = 10010;
int n, w, a, b, f[N];
int main()
{
	 freopen("guai.in","r",stdin);
	freopen("guai.out","w",stdout);
	scanf("%d %d", &n, &w);
    scanf("%d %d", &a, &b);
    for(int i = 1; i <= n; i++)
    {
        int x, y;
        scanf("%d %d", &x, &y);
        for(int j = w; j >= y && x <= a + b; j--)
        {
            f[j] = max(f[j], f[j - y] + 1);
        }
    }
    printf("%d", f[w]);
}

方法二:贪心 在可以攻击的选手里面挑选出能量消耗少的怪兽。

时间复杂度o(n*logn)

#include<bits\/stdc++.h>
using namespace std;
const int N = 10010;
int n, w, a, b, x,y,f[N];
using namespace std;
int main(){
	freopen("guai.in","r",stdin);
	freopen("guai.out","w",stdout);
    cin>>n>>w>>a>>b;
    a+=b;
    int cnt=0;
    for(int i=0;i<n;i++){
        int x;
        cin>>x;
        if(x>a)
            cin>>y;
        else
            cin>>f[cnt++];\/\/cin>>a[cnt];cnt++;
    }
    sort(f,f+cnt);  \/\/力气从小到大排序
    int sum=0;
    for(int i=0;i<cnt;i++){
        w-=f[i];
        if(w<0)break;
        sum++;
    }
    cout<<sum<<endl;
}

T3:修路

  • 子任务1:50分 可以看做求最短路问题。暴力上foyed,发现超时,时间复杂度是$O(N^3)$。
  • 子任务2

  方法1

    因为图很简单,就是一天直线上添加一条边,只有在告诉公路左右两侧的端点最短路会改变。也就是只需要用x或y重新一次,降低时间复杂度,时间复杂度是O(N*N)。

  方法2

    因为图步长值为1,还可以跑n遍bfs,时间复杂度也是O(N*N) 参考代码:

#include <bits\/stdc++.h>
using namespace std;
const int N=2e3+9;
int a[N][N], s[N];
int main()
{
	freopen("short.in","r",stdin);
	freopen("short.out","w",stdout); 
	int n, x, y;
	cin >> n >> x >> y;
	\/\/int a[n+1][n+1], s[n];
	for (int i=1; i<=n; i++)
		for (int j=1; j<=n; j++) a[i][j]=fabs(i-j);\/\/求出加边前的最短距离
	for (int i=1; i<=n; i++)
		for (int j=1; j<=n; j++)
			a[i][j]=min(a[i][j], min(a[i][x]+a[y][j]+1, a[i][y]+a[x][j]+1));\/\/开始优化~
	for (int i=1; i<n; i++) s[i]=0;
	for (int i=1; i<=n; i++)
		for (int j=1; j<=n; j++) s[a[i][j]]++;\/\/统计
	for (int i=1; i<n; i++) cout << s[i]\/2 << endl;
	return 0;\/\/愉快地结束
}

T4:笨鸟

闫总讲解 (x,a,b)可以经过的范围为(a+1,b-1)

因为在前行的过程中,不点击就要往下掉。横坐标增加时候,纵坐标最多+1 在整个过程中,不断取合法的跳跃区间。

      x+1,y+1 
(x,y)
      x+1,y-1
      
这个点只能到达只能到达奇偶性不相同的结点。因此取值的时候,不能只考虑大小,还要考虑奇偶性。
     

从(x0,y0)设最终的位置为(x1,y1) 设其水平距离差为。x=x1-x0 那么最多点击次数是x,最少点击次数是0.可以到达的区间是:(x1,y0-x) (x1,y0+x)

设点击次数为t 那么上升的高度是t,下降的高度是(x-t)

最终的值为y1=y0+t-(x-t) =y0+2t-x

 t=(y1+x)\/2
 

这就是跳跃的最小次数。

参考代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#define N 500005
using namespace std;
template <typename T> 
inline void _read(T& x){ 
  char ch=getchar();bool sign=true; 
  while(!isdigit(ch)){if(ch=='-')sign=false;ch=getchar();} 
  for(x=0;isdigit(ch);ch=getchar())x=x*10+ch-'0'; 
  if(!sign)x=-x; 
}
int T,n;
int ans[N];
int main(){
  freopen("birds.in","r",stdin);
  freopen("birds.out","w",stdout); 
  int i,j,k,temp=0,l=0,r=0,pos,x,a,b;
  cin>>n>>pos;
  for(i=1;i<=n;i++){
      _read(x);_read(a);_read(b);
      a++;b--;
      l-=x-temp;l=max(l,a);
      r+=x-temp;r=min(r,b);
      if((l&1)!=(x&1))l++;\/\/考虑不同的奇偶性 
      if((r&1)!=(x&1))r--;\/\/考虑不同的奇偶性 
      if(l>r)break;
      temp=x;
      ans[i]=(x+l)>>1;
  }
  if(i>n){\/\/注意,题目给定是到达这个点的最小点击次数,不一定是最终到达终点的次数。 
  	for(i=1;i<=n;i++){
  		printf("%d\n",ans[i]);
		}
		printf("%d\n",ans[n]);
	}
	else puts("Stupid bird!");
}

T5:楼梯

根据提议,两个楼直接越宽,那么c越小,两个楼之间越近,c越大。 我们设楼间的距离为len,脚垫垂线左侧为a,右侧为b,则a+b=len. 设交点的高度为h.x对应hx,y对应墙的高度hy

hy/len=h/a

hx/len=h/b

a+b=len

a=h*len/hy

b=h*len/hx

a+b=h*len(1/hx+1/hy)=len

h*(1/hx+1/hy)=1

1/h=1/hx+1/hy

hx=sqrt(xx-lenlen) hy=sqrt(yy-lenlen)

判断1/c与1/h的大小。

视频讲解:来自ACWING 闫总视频讲解

参考代码

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#define siz 
#define minn(a,b) (a<b?a:b)
using namespace std;
double x, y, c;
double work(double t) {
    return 1 \/ sqrt(x * x - t * t) + 1 \/ sqrt(y * y - t * t);
}
int main() {
	freopen("ladder.in","r",stdin);
	freopen("ladder.out","w",stdout);
    cin>>x>>y>>c;
    double l = 0, r = minn(x,y), mid;
    while(r - l > 1e-5) {
        mid = l + (r - l) \/ 2; 
        if( work(mid) > (1\/c) ) r=mid;
        else l=mid;
    }
    printf("%.3lf\n",mid);
}

多校联测

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-10-26 17:47:42

WFYZ:

SLYZ:https://www.luogu.com.cn/contest/140152

tj:https://www.cnblogs.com/WrongAnswer90-home/p/17771210.html

SDSY:

动态规划-基础篇

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-11-09 17:29:51

动态规划的引入

  • 0动态规划和贪心
共 90 篇博客