各种奇奇怪怪的题目
对于所有的问题,记
1、
区间
法一:暴力
时间复杂度
法二:线段树
我们发现
利用这个性质,每次pushup改为gcd即可
时间复杂度
法三: 人类智慧
根据数学直觉,如果数据不经过精心构造,十几个数gcd起来答案就很可能是
所以我们以
这里还有一个优化:如果你已经算过这个区间了,那就不用再算了,扔一个
(小声BB: 这些不都是我经常干的事吗)
法四: ST表
我们又发现这道题他没有修改,因为他有可重复贡献性
时间复杂度
贴代码
#include<bits/stdc++.h>
using namespace std;
const int N=3000005;
inline char gc () {
static char buf[4000000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 4000000, stdin), p1 == p2) ? EOF : *p1 ++ ;
}
int read () {
int ans = 0; char ch = gc();
while(!isdigit(ch)) ch = gc();
while(isdigit(ch)) ans = ans * 10 + ch - '0', ch = gc();
return ans;
}//题目给的快读板子,码风不同很正常
int log_2[N],n,m,a[N],x,y,ans[N],f[N][25];
long long pow2[55];
int gcd(int a,int b){return b?gcd(b,a%b):a}
int main(){
n=read();m=read();
pow2[0]=1;log_2[0]=-1;
for(int i=1;i<=21;++i)pow2[i]=pow2[i-1]*2;
for(int i=1;i<=n;++i)log_2[i]=log_2[(i>>1)]+1;
for(int i=1;i<=n;++i)f[i][0]=read();
for(int i=1;i<=21;++i){
for(int j=1;j+pow2[i]-1<=n;++j)
f[j][i]=gcd(f[j][i-1],f[j+pow2[i-1]][i-1]);
}
for(int i=1;i<=m;++i){
x=read();y=read();
if(x==y){ans[i]=f[x][0];continue;}
int logl=log_2[y-x+1];
ans[i]=gcd(f[x][logl],f[y-pow2[logl]+1][logl]);
printf("%d\n",ans[i]);
}
return 0;
}
2、
对
法一:暴力
不用多说,时间复杂度
法二:优化暴力
对法一用ST表优化,使得区间查询复杂度变为常数,复杂度
法三:优化暴力2
因为区间每次扩展1,我们发现可以在计算的时候记录这个区间的值,可以在O(n)的时间里求出O(n)个区间的值,复杂度
分析到了这步,我们发现这种区间是平方级别的。那真的没有更快的做法了吗?
法四:转化问题
前三个方法都是面向区间单独计算,这里我们转化一下问题
考虑计算每一个数的贡献。
对于一个序列中的数
所以,对于一个数
如果左边没有大于等于他的数,那么
那么他的贡献就是
可以
对于
法五:正解
对于法四的
上代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int mod=998244353;
inline void IO(){
ios::sync_with_stdio(0);
}
long long n,a[2000005],s1[2000005],s2[2000005];
vector<int> v;
int main(){
IO();cin>>n;
for(int i=1;i<=n;++i)cin>>a[i],s1[i]=n+1,s2[i]=0;
v.push_back(1);
for(int i=2;i<=n;i++){
while(!v.empty()&&a[i]>a[(v.back())]){
s1[(v.back())]=i;v.pop_back();
}
v.push_back(i);
}
v.clear();v.push_back(n);
for(int i=n-1;i>=1;i--){
while(!v.empty()&&a[i]>=a[(v.back())]){
s2[(v.back())]=i;v.pop_back();
}
v.push_back(i);
}
long long ans=0;
for(int i=1;i<=n;i++){
long long l=s2[i]+1,r=s1[i]-1;
ans=(ans+1ll*a[i]%mod*(i-l+1)%mod*1ll*(r-i+1)%mod)%mod;
}
cout<<(ans+mod)%mod<<endl;
return 0;
}
3、逆序对距离之和
定义逆序对
法一:暴力
如果使用冒泡排序求解,复杂度为
法二:正解
考虑传统归并排序做法,在归并的过程中求解问题。
从
把右边一大坨简化一下,得出:
在归并刚开始对着left做前缀和即可,之后每次发现逆序对,只需要O(1)的时间即可计算出所有逆序对间的距离的和。
贴代码:
#include<bits/stdc++.h>
using namespace std;
int n,a[100005],b[100005],pos[100005];
long long cnt=0,s[100005];
void merge(int l,int r) {
if(l>=r)return;
int mid=(l+r)>>1,i=l,j=mid+1,k=l;
merge(l,mid);merge(mid+1,r);
s[l-1]=0;
for(int i=l;i<=mid;i++)s[i]=s[i-1]+pos[a[i]];
while(i<=mid&&j<=r) {
if(a[i]<=a[j])b[k++]=a[i++];
else cnt+=(long long)(mid-i+1)*pos[a[j]]-(s[mid]-s[i-1]),b[k++]=a[j++];
}
while(i<=mid)b[k++]=a[i++];
while(j<=r)b[k++]=a[j++];
for(int i=l;i<=r;i++)a[i]=b[i];
}
int main() {
ios::sync_with_stdio(0);
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i],pos[a[i]]=i;
merge(1,n);
cout<<cnt<<endl;
return 0;
}
4、
给定两个数组
有
之后
法一:暴力
按照题意模拟即可,时间复杂度
法二:优化暴力
ST表的成名之战
我们发现,如果在暴力执行的过程中,
时间复杂度仍为
贴个代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll n,q,tp,last,a[300005],b[300005],xuanxue,f[300005][20];
ll lg2[300005],pow2[300005];
int main() {
cin>>n>>q>>tp;
for(int i(1);i<=n;++i)cin>>a[i];
for(int i(1);i<=n;++i)cin>>b[i],b[i]+=b[i-1];
pow2[0]=1;lg2[0]=-1;
for(int i(1);i<=19;++i)pow2[i]=pow2[i-1]<<1;
for(int i(1);i<=n;++i)lg2[i]=lg2[(i>>1)]+1;
for(int i(1);i<=n;++i)f[i][0]=a[i];
for(int i(1);i<=19;++i){
for(int j=1;j+pow2[i]-1<=n;++j)
f[j][i]=max(f[j][i-1],f[j+pow2[i-1]][i-1]);
}
for(register int i(1);i<=q;++i){
int l,r;ll k;cin>>l>>r>>k;
ll logl=lg2[r-l+1],xuanxue=max(f[l][logl],f[r-pow2[logl]+1][logl]);
for(int j=l;j<=r;++j){
if(k>=xuanxue){k+=b[r]-b[j-1];break;}
else if(k>=a[j])k+=b[j]-b[j-1];
}
cout<<k<<'\n';
}
return 0;
}
法三:正解
令
。
考虑两个函数复合,也即把两个"连续段"复合在一起,经过计算我们发现若初始连续段数是
所以可以在线段树的每个节点上维护"连续段",在询问时拆到
时间复杂度
上代码(呼呼呼呼呼太难写了):
#include<bits/stdc++.h>
using namespace std;
const int MAXN=3e5+5,SZ=MAXN*4;
#define ull unsigned long long
const ull INF=1e18;
#define mid ((l+r)>>1)
int n,q,tp,a[MAXN],b[MAXN],ans;
vector<int> v1[SZ];
vector<ull>v2[SZ];
inline ull calc(int p,int i){return i==v1[p].size()-1?INF:v1[p][i+1]-1;}
void build(int p,int l,int r){
if(l==r){
if(a[l])v1[p].push_back(0),v2[p].push_back(0);
v1[p].push_back(a[l]),v2[p].push_back(b[l]);
return;
}
build(p*2,l,mid),build(p*2+1,mid+1,r);
for(int i=0,j=0;i<v1[p*2].size();++i){
while(calc(p*2+1,j)<v1[p*2][i]+v2[p*2][i])++j;
int las=v1[p*2][i];
while(j<v1[p*2+1].size()){
v1[p].push_back(las),v2[p].push_back(v2[p*2][i]+v2[p*2+1][j]);
if(calc(p*2+1,j)>=calc(p*2,i)+v2[p*2][i])break;
las=calc(p*2+1,j)+1-v2[p*2][i],++j;
}
}
}
ull K;
void ask(int p,int l,int r,int L,int R){
if(r<L||l>R)return;
if(L<=l&&r<=R){
int lb=0,rb=v1[p].size()-1,mid2,pos=0;
while(lb<=rb){
mid2=(lb+rb)>>1;
if(v1[p][mid2]<=K)lb=mid2+1,pos=mid2;
else rb=mid2-1;
}
K+=v2[p][pos];return;
}
ask(p*2,l,mid,L,R);
ask(p*2+1,mid+1,r,L,R);
}
int main(){
scanf("%d%d",&n,&q);
for(int i=1;i<=n;++i)scanf("%d",&a[i]);
for(int i=1;i<=n;++i)scanf("%d",&b[i]);
build(1,1,n);
while(q--){
int l,r;scanf("%d%d%llu",&l,&r,&K);
ask(1,1,n,l,r);
printf("%llu\n",K);
}
return 0;
}
5、
的
法一:暴力
暴力枚举
法二:打表
如果你不着急,也可以考虑打表。
法三:正解
不妨设
引理 1:
证明显然......
引理 2:
证明:
由
证毕。
回到原题,设
由引理1,
而
所以我们枚举
时间复杂度
#include<bits/stdc++.h>
using namespace std;
const int N=30000005;
inline void open(const char *s){
string str=s;
freopen((str+".in").c_str(),"r",stdin);
freopen((str+".out").c_str(),"w",stdout);
}
inline void close(){
fclose(stdin);fclose(stdout);
}
inline void IO(){
ios::sync_with_stdio(0);
}
int n,a[N];
int main(){
IO();
int k=N/2,T,id=0;
for(register int i=1;i<=k;++i){
for(int j=i*2;j<=N;j+=i){
if((i^j)==j-i)++a[j];
}
}
for(int i=1;i<=N;++i)a[i]+=a[i-1];
cin>>T;
while(T--){
cin>>n;
cout<<"Case "<<++id<<": "<<a[n]<<endl;
}
return 0;
}
6、
定义
法一:暴力
暴力枚举
法二:正解
我们考虑一个质数
这个贡献就是欧拉函数......
对于这道题,可以预处理欧拉函数,将得到的答案*2即可。
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=10000000;
int phi[N+75],s[N+75],p[N+75],m,n,v[N+75];
void primes(int n){
for(int i=2;i<=n;i++) {
if(!v[i]){v[i]=i,p[++m]=i,phi[i]=i-1;}
for(int j=1;j<=m;j++){
if(p[j]>v[i]||p[j]>n/i)break;
v[i*p[j]]=p[j];
phi[i*p[j]]=phi[i]*(i%p[j]?p[j]-1:p[j]);
}
}
s[1]=0;
for(int i=2;i<=n;++i)s[i]=s[i-1]+phi[i]*2;
}
inline void IO(){
ios::sync_with_stdio(0);
}
int main(){
cin>>n;primes(N);
ll ans=0;
for(int i=1;i<=m&&p[i]<=n;++i)ans+=s[n/p[i]]+1;
cout<<ans<<endl;
return 0;
}
7、
Treeland国有N个城市,从0至N-1标号。城市之间有N-1条无向的路,每条路连接一对城市。这些路构成了一棵树。(这意味着,每对城市之间可以通过一系列道路相互到达。)
现在,我们用一个字符串数组
Treeland国的居民希望制造一个Treeland定位系统(TPS)。TPS将帮助人们判断自己在哪个城市。这个系统将包含K个标记信标。每个信标将安装在一个城市中。当一个人打开自己的TPS接收器时,它将会计算当前位置到各个信标的距离。(距离为最短路的长度。)
显然,只有当每个城市对应着不同的信标读数时,TPS才可以使用。换言之,K和信标放置的位置必须使得任意两个城市收到的数字序列不同。(注意,不同的信标可以辨别出来。)
请你计算最小的K的大小。
写在最前面
信标显然只会放在叶子节点上,因为如果放在非叶子节点上,显然会造成重复,不如放在叶子节点上方便。
法一:暴力
利用这个性质,暴力枚举所有的放置情况,一一判断即可,时间复杂度
法二:暴力优化
本题答案显然具有单调性。考虑二分答案,每次
可以使用
法三:卡时
考虑对法一加上卡时优化
法四:人类智慧!
根据数学直觉!如果我们在法二二分的过程中找了许许多多组解还是没找到,那就很有可能!是无解的。
利用这个直觉,我们引入更多随机因素。
考虑用随机选取信标安放处,我的方法是随机抽取,也可以
因为出题人过于毒瘤,可能会卡我们的随机,我们认为枚举10000次是一个很安全的范围。
然后我们就在几乎不烧脑的情况下切掉了这道题。
其实这个方法比暴力还好写
代码如下
#include<bits/stdc++.h>
using namespace std;
vector<int> v[65];
int n,vis[55],one[105],tot,ch[51],g[51][51];
vector<int> d[65];
bool check2(int mid) {
for(int i=1;i<=n;++i)d[i].clear();
for(int i=1;i<=n;++i){
for(int j=1;j<=mid;++j){
d[i].push_back(g[i][ch[j]]);
}
}
for(int i=1;i<=n;++i){
for(int j=i+1;j<=n;++j){
int s=0;
for(int k=1;k<=mid;++k)
s+=d[i][k-1]==d[j][k-1];
if(s==mid)return 0;
}
}
return 1;
}
bool check(int mid) {
for(int i=1;i<=10000;++i) {
memset(vis,0,sizeof vis);
for(int j=1;j<=mid;++j) {
int t=rand()%tot+1;
while(vis[t])t=rand()%tot+1;
ch[j]=one[t];vis[t]=1;
}
if(check2(mid))return 1;
}
return 0;
}
int main() {
freopen("TPS.in","r",stdin);
freopen("TPS.out","w",stdout);
srand(10007);
cin>>n;
if(n==1){
cout<<"0\n";return 0;
}
memset(g,0x3f,sizeof g);
for(int i=1;i<=n;++i) {
g[i][i]=0;
for(int j=1;j<=n;++j) {
char c;cin>>c;
if(c=='Y'){
v[i].push_back(j);
g[i][j]=1;
}
}
}
for(int k=1;k<=n;++k)
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
for(int i=1;i<=n;++i)
if(v[i].size()==1)one[++tot]=i;
int l=1,r=tot;
while(l<r) {
int mid=(l+r)>>1;
if(check(mid))r=mid;
else l=mid+1;
}
cout<<l<<'\n';
return 0;
}
法五:正解
其实不想贴的,但是毕竟不是所有人都喜欢乱搞,我还是贴上吧。
我们先假定选择了一个点为根,并且在根节点放置了信标。
设一个节点有
如果不这样放,考虑对于两棵都没有放的子树,他们汇集到
由于在根节点放置了信标,可以只考虑深度相同的点。因为他们的
所以,对于一个确定的根,我们可以贪心地放置信标,得到当前的答案。
我们可以找到任意一个度数
时间复杂度
(借鉴了https://zepto.page/topcoder-srm598-tps/)
8、
定义
再定义
求
法一:暴力
枚举每一个区间,暴力计算区间众数。时间复杂度
法二:优化暴力
考虑把序列分成
对于每个询问
·开头不足一整段的。
·中间的整段。
·结尾不足一整段的。
显然序列在
本文来自博客园,作者:JZX102624,转载请注明原文链接:https://www.cnblogs.com/JZX102624/p/16935155.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现