Caiwen的博客

2025寒假复健

2025-02-07 07:06:59

GSS1 - Can you answer these queries I

线段树

https://www.luogu.com.cn/problem/SP1043

题目描述

nn 个数字,mm 个询问,每次询问给定区间的最大字段和,1n,m5×1041\le n,m\le 5\times 10^4

笔记

一上来口胡了个分治做法,用主定理分析了一下发现时间复杂度好像是 O(mnlogn)O(mn\log n) 的()

实际上维护区间前缀和最大值、后缀和的最大值、区间和、区间内最大字段和就好了

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include<bits/stdc++.h> #define int long long #define ull unsigned long long #define ls(k) (k)<<1 #define rs(k) (k)<<1|1 #define nt tree[k] #define lt tree[ls(k)] #define rt tree[rs(k)] #define debug(x) cout<<#x<<"="<<x<<endl using namespace std; const int inf=0x3f3f3f3f3f3f3f3f; const int mod=0; typedef pair<int,int> pii; #define _ 50004 int in[_],n,m; struct Node{int lmax,rmax,sum,seg;} tree[_<<2]; inline Node merge(Node l,Node r){ Node res; res.sum=l.sum+r.sum; res.lmax=max(l.lmax,l.sum+r.lmax); res.rmax=max(r.rmax,r.sum+l.rmax); res.seg=max(max(l.seg,r.seg),l.rmax+r.lmax); return res; } void build(int k,int l,int r){ if(l==r) return nt.lmax=nt.rmax=nt.sum=nt.seg=in[l],void(); int mid=(l+r)>>1; build(ls(k),l,mid); build(rs(k),mid+1,r); nt=merge(lt,rt); } Node query(int k,int l,int r,int x,int y){ if(l>=x&&r<=y) return nt; int mid=(l+r)>>1,flag=false; Node res; if(x<=mid) res=query(ls(k),l,mid,x,y),flag=true; if(y>mid){ Node rson=query(rs(k),mid+1,r,x,y); if(!flag) res=rson; else res=merge(res,rson); } return res; } inline void subtask(){ cin>>n;for(int i=1;i<=n;i++) cin>>in[i];cin>>m; build(1,1,n); while(m--){ int l,r;cin>>l>>r; cout<<query(1,1,n,l,r).seg<<endl; } } signed main(){ ios::sync_with_stdio(false); int t=1;//cin>>t; while(t--) subtask(); return 0; }

D - M<=ab

数学、枚举

https://atcoder.jp/contests/abc296/tasks/abc296_d

题目描述

找到两个正整数 aabb 满足 a,bna,b\le na×bma\times b\ge m

笔记

上来先对 mm 开根,然后以为 aabb 必然全都大于等于 m\sqrt{m},wa了两发,发现忘记了可能一个数很小但另一个数很大

然后发现这是经典讨论题,往往需要枚举一个,且枚举花费的时间复杂度是可以接受的。于是这题只需要枚举 aa,然后求出最小的 bb,再看看满不满足条件即可

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include<bits/stdc++.h> #define int long long #define ull unsigned long long #define ls(k) (k)<<1 #define rs(k) (k)<<1|1 #define debug(x) cout<<#x<<"="<<x<<endl using namespace std; const int inf=0x3f3f3f3f3f3f3f3f; const int mod=0; typedef pair<int,int> pii; inline int exsqrt(int x){ int r=sqrt(x); while((r+1)*(r+1)<=x) r++; while((r-1)*(r-1)>=x) r--; return r; } inline void subtask(){ int n,m;cin>>n>>m; if(n<=1000000&&n*n<m) return cout<<-1,void(); int r=exsqrt(m); if(r*r==m) return cout<<m,void(); int ans=inf; for(int i=1;i<=r+1;i++){ if(m%i==0){ if(m/i<=n) return cout<<m,void(); }else if((m/i+1)<=n) ans=min(ans,i*(m/i+1)); } cout<<ans; } signed main(){ ios::sync_with_stdio(false); int t=1;//cin>>t; while(t--) subtask(); return 0; }

Nearest vectors

https://codeforces.com/problemset/problem/598/C

题目描述

给你 n(1n105)n(1\le n \le 10^5) 个点,然后让你求出所有原点到这个点的向量中夹角最小的那个

笔记

先进行极角排序,然后逐个求夹角就可以了

但关键是怎么搞出最小夹角出来

朴素使用点乘求夹角,然后再比较,会有精度损失,只能过 32 个点

开 long double,精度好一点,但只能过 103 个点

我们再卡一卡,把所有坐标乘 1000,有效果,但也只能过 104 个点

搜题解,得知了一种无精度损失的比较夹角的方法:

对于一个角,我们将其旋转,使一条边与 X 轴平行。假设角度为 θ\theta,另一条边向量模长为 vv,则旋转后的点为 (vcosθ,vsinθ)(v\cos \theta, v \sin \theta)

我们发现,这两个坐标再同时乘上已经与 X 轴平行的边的模长后,横坐标就变成了点乘,纵坐标就变成了叉乘。而同时乘上一个数是不会改变其与 X ,即原来的边的夹角

对于另外一个角也进行上述操作,于是就转变为判断两个向量,哪个向量与 X 轴夹角更小一点,可以使用叉乘判断

cpp
1
2
3
4
5
//判断OA1和OB1之间的夹角是不是比OA2和OB2之间的夹角更小 inline bool angle_less(Point a1,Point b1,Point a2,Point b2){ Point t1(a1*b1,abs(a1^b1)),t2(a2*b2,abs(a2^b2)); return (t1^t2)>0; }

P3349 [ZJOI2016] 小星星

https://www.luogu.com.cn/problem/P3349

题目描述

给出一个包含 nn 个点的树,以及 nn 个点 mm 条边的图。现在你需要给这个树每个点分配一个序号,分配的应该为 nn 的排列。分配后需要满足如果两个点在树上有连边,那么也应该在图上有连边。求分配方案数

数据范围

1m171\le m\le 17

笔记

考虑 dp,dp 的时候,为了保证分配序号时不会分配重复,我们就遇到了后效性的问题。解决后效性可以把有后效性的因素放到状态里,于是我们可以设 dp[x][i][S]dp[x][i][S] 表示给 xx 点分配序号 ii ,其子节点分配序号集合为 SS 时的方案数。但这样大力 dp 的时间复杂度太高

考虑放松限制。如果我们不必让分配的序号满足是一个排列,那么 dp 就可以变为 dp[x][i]dp[x][i]。但这样算出来的方案中,可能存在两点分配的序号是相同的。此时就可以考虑容斥了,我们枚举 {1,...,n}\{1,...,n\} 的子集 SS。令 dp[x][i]dp[x][i] 表示配分在集合 SS 中的序号时的方案数。我们计算出 S=n|S|=n 时方案数,减去 S=n1|S|=n-1 时方案数,加上 S=n2|S|=n-2 时方案数,即可得到答案

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include<bits/stdc++.h> #define int long long #define ull unsigned long long #define ls(k) (k)<<1 #define rs(k) (k)<<1|1 #define debug(x) cout<<#x<<"="<<x<<endl using namespace std; const int inf=0x3f3f3f3f3f3f3f3f; const int mod=0; typedef pair<int,int> pii; int now,dp[18][18],n,m; vector<int> ve[18],ma[18]; void dfs(int x,int fa){ for(int i=1;i<=n;i++) if((now&(1<<(i-1)))) dp[x][i]=1; for(auto to:ve[x]){ if(to==fa) continue; dfs(to,x); for(int i=1;i<=n;i++){ if(!(now&(1<<(i-1)))) continue; int s=0; for(auto j:ma[i]){ if(!(now&(1<<(j-1)))) continue; s+=dp[to][j]; } dp[x][i]*=s; } } } inline int popcnt(int x){ int res=0; while(x){ if(x&1) res++; x>>=1; } return res; } inline void subtask(){ cin>>n>>m; for(int i=1;i<=m;i++){ int u,v;cin>>u>>v; ma[u].push_back(v); ma[v].push_back(u); } for(int i=1;i<n;i++){ int u,v;cin>>u>>v; ve[u].push_back(v); ve[v].push_back(u); } int ans=0; for(now=1;now<(1<<n);now++){ memset(dp,0,sizeof(dp)); dfs(1,1); int tmp=0; for(int i=1;i<=n;i++){ if(!(now&(1<<(i-1)))) continue; tmp+=dp[1][i]; } //debug(now);debug(tmp); if((n-popcnt(now))%2) ans-=tmp; else ans+=tmp; } cout<<ans; } signed main(){ ios::sync_with_stdio(false); int t=1;//cin>>t; while(t--) subtask(); return 0; }

P6628 [省选联考 2020 B 卷] 丁香之路

https://www.luogu.com.cn/problem/P6628

贪心+欧拉路+最小生成树

题目描述

ssi(1in)i(1\le i \le n) 的最短路,要求必须经过指定的 mm 条边,至少经过一次。点 xx 到点 yy 的距离为 xy|x-y|

数据范围

1n25001\le n \le 2500

保证 1sn1\le s\le n;保证 mn(n1)2m\le \frac {n(n-1)}2

笔记

如果若有的边都恰好走一次,那么就很像欧拉路问题了。现在所有的边可以不止经过一次,那么我们可以考虑加入一些重复边,转化为欧拉路问题。

我们知道,只要度数均为偶数,那么一定存在欧拉路,于是我们现在只需要想办法加入一些边,让所有的点的度数变成偶数

由于欧拉路还需要两个度数是奇数的点,不太好处理。我们考虑给起点和终点连接一条边权,此时就转为了欧拉回路了

因为题目还要求是最短路,因此我们加入的边权之和应最小

首先考虑将边的度数变成偶数,我们把点的编号从小到大排序,然后直接给相邻的两个度数为奇数的点连边就可以了。仔细思考原图的性质,能够发现度数为奇数的点一定有偶数个,可以两两配对

然后现在我们还面临着连通性的问题。在上面将边的度数变成偶数的连边过程中,对于点 uuvvu<vu<v,我们不去直接将他俩连边,而是选择连接 (u,u+1)(u,u+1)(u+1,u+2)(u+1,u+2)、......、(v1,v)(v-1,v)。这样的话,产生的边权还是一样的,中间的点因为多连了两条边,度数的奇偶性没有发生变化,但是我们多把一些点连接了起来,感性上可以发现能够让连通性变得更强一点。

但还是可能有连通块之间没有连通。我们考虑把涉及到的这些点编号从小到大排序,然后两两连边,跑最小生成树,以最小的代价将整个图连通。值得注意的是跑最小生成树时建边时应该建两个重复的边,来保持度数是偶数。

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include<bits/stdc++.h> #define int long long #define ull unsigned long long #define ls(k) (k)<<1 #define rs(k) (k)<<1|1 #define debug(x) cout<<#x<<"="<<x<<endl using namespace std; const int inf=0x3f3f3f3f3f3f3f3f; const int mod=0; typedef pair<int,int> pii; #define _ 2502 inline int dis(int u,int v){return abs(u-v);} struct UFS{ int fa[_],n; UFS(int n){for(int i=1;i<=n;i++) fa[i]=i;this->n=n;} UFS(const UFS &old){ n=old.n; for(int i=1;i<=old.n;i++) fa[i]=old.fa[i]; } int find(int x){while(x!=fa[x]) x=fa[x]=fa[fa[x]];return x;} void merge(int x,int y){fa[find(x)]=find(y);} }; set<int> se; int de1[_],de2[_],n,m,s; struct Edge{int u,v,w;} edge[_]; int solve(UFS u2,int x){ for(int i=1;i<=n;i++) de2[i]=de1[i]; de2[s]++,de2[x]++; int res=0,las=0,pre=0,cnt=0; for(auto it=se.begin();it!=se.end();it++){ int now=*it; if(it!=se.begin()) edge[++cnt]={las,now,dis(las,now)}; las=now; if(de2[now]%2==0) continue; else if(!pre) pre=now; else{ res+=dis(pre,now); for(int i=pre+1;i<=now;i++) u2.merge(i-1,i); pre=0; } } sort(edge+1,edge+cnt+1,[](Edge x,Edge y){return x.w<y.w;}); for(int i=1;i<=cnt;i++){ int u=edge[i].u,v=edge[i].v; if(u2.find(u)==u2.find(v)) continue; res+=2*edge[i].w; u2.merge(u,v); } return res; } inline void subtask(){ cin>>n>>m>>s;se.insert(s); UFS u1(n); int base=0; for(int i=1;i<=m;i++){ int u,v;cin>>u>>v; se.insert(u),se.insert(v); de1[u]++,de1[v]++; base+=dis(u,v); u1.merge(u,v); } for(int i=1;i<=n;i++){ se.insert(i); cout<<base+solve(u1,i)<<" "; if(!de1[i]&&i!=s) se.erase(i); } } signed main(){ ios::sync_with_stdio(false); int t=1;//cin>>t; while(t--) subtask(); return 0; }
最后更新于:2025-04-03 14:33:06

Caiwen
本文作者
一只蒟蒻,爱好编程和算法