线段树
https://www.luogu.com.cn/problem/SP1043
题目描述
给 个数字, 个询问,每次询问给定区间的最大字段和,
笔记
一上来口胡了个分治做法,用主定理分析了一下发现时间复杂度好像是 的()
实际上维护区间前缀和最大值、后缀和的最大值、区间和、区间内最大字段和就好了
cpp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657#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;
}
数学、枚举
https://atcoder.jp/contests/abc296/tasks/abc296_d
题目描述
找到两个正整数 和 满足 且
笔记
上来先对 开根,然后以为 和 必然全都大于等于 ,wa了两发,发现忘记了可能一个数很小但另一个数很大
然后发现这是经典讨论题,往往需要枚举一个,且枚举花费的时间复杂度是可以接受的。于是这题只需要枚举 ,然后求出最小的 ,再看看满不满足条件即可
cpp1234567891011121314151617181920212223242526272829303132333435#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;
}
https://codeforces.com/problemset/problem/598/C
题目描述
给你 个点,然后让你求出所有原点到这个点的向量中夹角最小的那个
笔记
先进行极角排序,然后逐个求夹角就可以了
但关键是怎么搞出最小夹角出来
朴素使用点乘求夹角,然后再比较,会有精度损失,只能过 32 个点
开 long double,精度好一点,但只能过 103 个点
我们再卡一卡,把所有坐标乘 1000,有效果,但也只能过 104 个点
搜题解,得知了一种无精度损失的比较夹角的方法:
对于一个角,我们将其旋转,使一条边与 X 轴平行。假设角度为 ,另一条边向量模长为 ,则旋转后的点为
我们发现,这两个坐标再同时乘上已经与 X 轴平行的边的模长后,横坐标就变成了点乘,纵坐标就变成了叉乘。而同时乘上一个数是不会改变其与 X ,即原来的边的夹角
对于另外一个角也进行上述操作,于是就转变为判断两个向量,哪个向量与 X 轴夹角更小一点,可以使用叉乘判断
cpp12345//判断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;
}
https://www.luogu.com.cn/problem/P3349
题目描述
给出一个包含 个点的树,以及 个点 条边的图。现在你需要给这个树每个点分配一个序号,分配的应该为 的排列。分配后需要满足如果两个点在树上有连边,那么也应该在图上有连边。求分配方案数
数据范围
笔记
考虑 dp,dp 的时候,为了保证分配序号时不会分配重复,我们就遇到了后效性的问题。解决后效性可以把有后效性的因素放到状态里,于是我们可以设 表示给 点分配序号 ,其子节点分配序号集合为 时的方案数。但这样大力 dp 的时间复杂度太高
考虑放松限制。如果我们不必让分配的序号满足是一个排列,那么 dp 就可以变为 。但这样算出来的方案中,可能存在两点分配的序号是相同的。此时就可以考虑容斥了,我们枚举 的子集 。令 表示配分在集合 中的序号时的方案数。我们计算出 时方案数,减去 时方案数,加上 时方案数,即可得到答案
cpp123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869#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;
}
https://www.luogu.com.cn/problem/P6628
贪心+欧拉路+最小生成树
题目描述
求 到 的最短路,要求必须经过指定的 条边,至少经过一次。点 到点 的距离为
数据范围
保证 ;保证
笔记
如果若有的边都恰好走一次,那么就很像欧拉路问题了。现在所有的边可以不止经过一次,那么我们可以考虑加入一些重复边,转化为欧拉路问题。
我们知道,只要度数均为偶数,那么一定存在欧拉路,于是我们现在只需要想办法加入一些边,让所有的点的度数变成偶数
由于欧拉路还需要两个度数是奇数的点,不太好处理。我们考虑给起点和终点连接一条边权,此时就转为了欧拉回路了
因为题目还要求是最短路,因此我们加入的边权之和应最小
首先考虑将边的度数变成偶数,我们把点的编号从小到大排序,然后直接给相邻的两个度数为奇数的点连边就可以了。仔细思考原图的性质,能够发现度数为奇数的点一定有偶数个,可以两两配对
然后现在我们还面临着连通性的问题。在上面将边的度数变成偶数的连边过程中,对于点 和 ,,我们不去直接将他俩连边,而是选择连接 、、......、。这样的话,产生的边权还是一样的,中间的点因为多连了两条边,度数的奇偶性没有发生变化,但是我们多把一些点连接了起来,感性上可以发现能够让连通性变得更强一点。
但还是可能有连通块之间没有连通。我们考虑把涉及到的这些点编号从小到大排序,然后两两连边,跑最小生成树,以最小的代价将整个图连通。值得注意的是跑最小生成树时建边时应该建两个重复的边,来保持度数是偶数。
cpp12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273#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;
}