好题——思维题
前言
本文章将会持续更新,主要是一些个人觉得比较妙的题,主观性比较强(给自己记录用的),有讲错请补充。
带 !号的题是基础例题,带 * 号的是推荐首先完成的题(有一定启发性的)。
算法思维题单
下面的题目都是码量较小,不是很好想的题。
P2804 神秘数字
先看数据范围:
我们继续来分析题目,我们可以把每个数剪掉平均数
又看一看也可以发现只要后面的数大于前面的数就满足条件,也就是正序对。
我们只要在一以前加上一个零,又用树状数组求正序对就可以了(仿照求逆序对)。
Orac and Medians
找性质题。
做题思路:
当序列中没有
当
当
我们结合第二第三个性质时,就发现只要有两个相邻的数都大于等于
这时候还差了一个判断条件,性质三中平均数等于排名第二的,只要这个排名第二的数大于等于
启示:拆分问题,先探寻小的特殊情况,如,
有一个相似的题:一个长度为
其实就是找连续的三个数中有出现两次的数的种类数。
XOR-gun
题目描述
给定一个长为
我们知道两个数异或起来一定不大与左移两个数位数最大值再减一,如
所以按照位数分块,这时我们又发现,当一个块中的数大于等于
启示:异或的题可能与二进制位数有关。
P8775 [蓝桥杯 2022 省 A] 青蛙过河
此题看着无法下手的是
可以把一个青蛙跳
直接求不好求,想到用二分把求值变成判断。
当判断的距离为
相当于在
P6608 [Code+#7] 神秘序列
开始可以跟着样例手算一下,发现从前往后扫一遍只要有
正着算比较好算,只要扫到一个
但难点是构造出这个序列,可以想到倒着推回去,从前往后扫,只要找到一个零就把
可以试着打表看一下:
1:1
2:0 2
3:1 2
4:0 1 3
5:1 1 3
6:0 0 2 4
7:1 0 2 4
8:0 2 2 4
9:1 2 2 4
10:0 1 1 3 5
11:1 1 1 3 5
12:0 0 0 2 4 6
13:1 0 0 2 4 6
14:0 2 0 2 4 6
15:1 2 0 2 4 6
16:0 1 3 2 4 6
17:1 1 3 2 4 6
18:0 0 2 1 3 5 7
19:1 0 2 1 3 5 7
20:0 2 2 1 3 5 7
发现并没有什么规律,之前想过用一些数据结构使这个暴力方法优化到
又根据上表把每次改变的数的下标写下来。
1 2 1 3 1 4 1 2 1 5 1 6 1 2 1 3 1 7 1 2
发现
可以得出以下式子:
简化一下式子就是:
然后用每个数的循环次数来推整个序列。
设原始的序列为
这样就可以求出序列了。
但操作的次数是
这道题二分的范围不好求,参考这篇,
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e7+10;
int n;
int a[N],b[N];
bool check(int mid)
{
int k=mid+n,i=1;
while(k)
{
k=k*i/(i+1);
i++;
}
i--;
if(i<=mid) return 1;
return 0;
}
signed main()
{
scanf("%lld",&n);
int x=sqrt(n)*1.772456;
int r=x+100;
int l=max(x-100,2ll);
while(l<r)
{
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
// if(l==x+100) 之前想错了,这里应该没有无解情况
// {
// puts("Daydream!");
// return 0;
// }
n+=l;
int len=1;
while(n)
{
int now=n;
n=n*len/(len+1);
a[len]=now-n;
len++;
}
len--;
int sum=0;
for(int i=len;i>=1;i--)
{
b[i]=a[i]*i-sum;
sum+=a[i];
}
printf("%lld\n",len);
for(int i=1;i<=len;i++) printf("%lld ",b[i]);
return 0;
}
P1758 [NOI2009] 管道取珠
此题只要想到求
后面一个简单的 DP 。注意边界问题。
P8955 「VUSC」Card Tricks
此题做法比较多,个人觉得最简洁的是线段数加扫描线的算法。
如果单纯按照题目给的时间顺序维护序列的异或值是行不通的,无法判断是否有数超过给的
但我们利用或运算的单调递增性,可以试着用线段树维护时间轴。线段树第
Double Happiness
考的就是一个叫费马平方和定理
模(除以)4余1的素数可表示为两自然数的平方和
证明参考:费马平方和定理的天书证明
注意特判
染色问题
注:这些题目是一类问题,所以放在一起讲了,难度从低到高。
问题1:
一个
- 把一行涂成一种颜色
。 - 把一列涂成一种颜色
。
该行(或该列)格子原有的颜色都会被覆盖成新涂上的颜色。
求操作后每种颜色的数量。
如果暴力做
因为该行(或该列)格子原有的颜色都会被覆盖成新涂上的颜色。只要倒序操作,涂过的行列就删除。
问题2:
有一个
线段树维护肯定是不行的,有两种方式:并查集维护,链表维护。
大概方法:维护一个单向链表,倒序枚举操作次数,每次操作时
void change(int l,int r,int col)
{
int now=0;
for(int i=l;i<=r;i=nxt[i])
{
if(!vis[i]) vis[i]=col;
nxt[now]=nxt[r];
now=i;
}
}
例题:P2391 白雪皑皑
问题3:
直接上题目链接:P9715 「QFOI R1」头
其实就是上面的结合。
但此题中有两种涂色方式,有一种是:如果涂色时遇到已经被染色的格子,就不再进行染色,我们正序删就可以了。
所以把两种方式分开,先倒序处理
code
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+10;
int n,m,k,q;
struct node
{
int op,l,r,c,t;
}a[N];
long long ans[N];
struct lin
{
int sum,vis[N],nxt[N];
void change(int l,int r)
{
int now=0;
for(int i=l;i<=r;i=nxt[i])
{
if(!vis[i]) sum++;
vis[i]=1;
nxt[now]=nxt[r];
now=i;
}
}
int ask(int l,int r)
{
int res=0;
for(int i=l;i<=r;i=nxt[i])
{
if(!vis[i]) res++;
}
return r-l+1-res;
}
}tx,ty;
void change(int id)
{
if(a[id].op==1)
{
ans[a[id].c]+=1ll*(a[id].r-a[id].l+1-tx.ask(a[id].l,a[id].r))*(m-ty.sum);
tx.change(a[id].l,a[id].r);
}
else
{
ans[a[id].c]+=1ll*(a[id].r-a[id].l+1-ty.ask(a[id].l,a[id].r))*(n-tx.sum);
ty.change(a[id].l,a[id].r);
}
}
int main()
{
scanf("%d%d%d%d",&n,&m,&k,&q);
for(int i=1;i<=q;i++) scanf("%d%d%d%d%d",&a[i].op,&a[i].l,&a[i].r,&a[i].c,&a[i].t);
for(int i=1;i<=n;i++) tx.nxt[i]=i+1;
for(int i=1;i<=m;i++) ty.nxt[i]=i+1;
for(int i=q;i>=1;i--)
if(a[i].t) change(i);
for(int i=1;i<=q;i++)
if(!a[i].t) change(i);
for(int i=1;i<=k;i++)
printf("%lld ",ans[i]);
return 0;
}
P1315 [NOIP2011 提高组] 观光公交
贪心思维题(也有费用流大佬)
先考虑
先预处理出每个站点最晚来的乘客。然后对于每一个点判断是车等乘客还是乘客等车,来更新从每一个站出发的时间。
然后考虑
我们知道,在一次加速只会对后面的一段有影响。
1.到下一个点还人需要等待车:以后的时间就不再影响了
2.到下一个点不需要人等待车:对以后的时间还会加速直到……出现情况1,或者到最后一个点。
对于
时间复杂度:
P4375 [USACO18OPEN] Out of Sorts G
先来看一道简单一点的题:P4378 [USACO18OPEN] Out of Sorts S
冒泡排序就是每次把一个大数移到后面去。
如:
1 5 3 8 2
如果排好序就是:1 2 3 5 8
先归位
对于
对于每个数都是把一个比它大的数移到它的后面。
所以答案就是:所有数大于它的个数中的最大值。
可以用树状数组求解。
然后我们来看这道题。
题意就是正向一次反向一次冒泡排序。
可以先像上面的做法考虑:
对于每一个数,每一次正反两次可能出现:一个比它大的数移到后面,然后几个比它小的数移到前面。这样是无法计数的。
那我们考虑离散化,把
考虑一个位置
最后就是要求:每个位置大于它的个数中的最大值。
离散化加树状数组可解决此题。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战