2.26字串的最大差
如题,n<=5e5
求和号可以拆分,只要单独对最大值和最小值求和再相减就可以了。
方法一
最大值和最小值是好处理的,且从数列中删去最大值等价于对最值左右两段的子区间分别求和。
分到区间长度为零时,就结束了。
这里区间最大值用st表维护,代码源ac,但cf会mle。改成线段树cf应该就可以了。
随机数据下,朴素方法的时间复杂度近似于快排
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e6+1000;
int p[maxn][22];
int lg[maxn],a[maxn];
int n;
int mn=0,mx=0;
int query1(int l,int r){
int len=r-l+1;
if(a[p[l][lg[len]]]<=a[p[r-(1<<lg[len])+1][lg[len]]])
return p[l][lg[len]];
else return p[r-(1<<lg[len])+1][lg[len]];
}
void ef1(int l,int r){
if(l>r)return ;
int key=query1(l,r);
mn+=a[key]*(key-l+1)*(r-key+1);
ef1(l,key-1);
ef1(key+1,r);
}
int query2(int l,int r){
int len=r-l+1;
if(a[p[l][lg[len]]]>=a[p[r-(1<<lg[len])+1][lg[len]]])
return p[l][lg[len]];
else return p[r-(1<<lg[len])+1][lg[len]];
}
void ef2(int l,int r){
if(l>r)return ;
int key=query2(l,r);
mx+=a[key]*(key-l+1)*(r-key+1);
ef2(l,key-1);
ef2(key+1,r);
}
signed main(){
cin>>n;
lg[1]=0;
for(int i=2;i<=n;i++)
if(i>>lg[i-1]+1)lg[i]=lg[i-1]+1;
else lg[i]=lg[i-1];
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
p[i][0]=i;
for(int j=1;(1<<j)<=n;j++){
for(int i=1;i<=n;i++){
if(i+(1<<j)-1>n)break;
if(a[p[i][j-1]]<=a[p[i+(1<<j-1)][j-1]])
p[i][j]=p[i][j-1];
else p[i][j]=p[i+(1<<j-1)][j-1];
}
}
ef1(1,n);
for(int i=1;i<=n;i++)
p[i][0]=i;
for(int j=1;(1<<j)<=n;j++){
for(int i=1;i<=n;i++){
if(i+(1<<j)-1>n)break;
if(a[p[i][j-1]]>=a[p[i+(1<<j-1)][j-1]])
p[i][j]=p[i][j-1];
else p[i][j]=p[i+(1<<j-1)][j-1];
}
}
ef2(1,n);
cout<<mx-mn<<endl;
}
方法二
cf题解,对于每一个元素,用l和r数组记录ai左(右)侧第一个比它大(小)的元素的位置。
l和r即是ai的影响范围。显然l和r可以通过单调队列(栈)来实现。
注意ai和aj相同的情况
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e6+1000;
int a[maxn];
int l[maxn],r[maxn];
int n,mn=0,mx=0;
struct node{
int val,id;
};
stack<node>s;
signed main(){
cin>>n;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++){
while(!s.empty()&&s.top().val<=a[i])s.pop();
if(s.empty())l[i]=i;
else l[i]=i-s.top().id;
s.push((node){a[i],i});
}
while(!s.empty())s.pop();
for(int i=n;i>=1;i--){
while(!s.empty()&&s.top().val<a[i])s.pop();//当ai和aj(i<j)相同时,注意不要让区间计算两次
if(s.empty())r[i]=n+1-i;
else r[i]=s.top().id-i;
s.push((node){a[i],i});
}
for(int i=1;i<=n;i++)
mx+=l[i]*r[i]*a[i];
while(!s.empty())s.pop();
for(int i=1;i<=n;i++){
while(!s.empty()&&s.top().val>=a[i])s.pop();
if(s.empty())l[i]=i;
else l[i]=i-s.top().id;
s.push((node){a[i],i});
}
while(!s.empty())s.pop();
for(int i=n;i>=1;i--){
while(!s.empty()&&s.top().val>a[i])s.pop();
if(s.empty())r[i]=n+1-i;
else r[i]=s.top().id-i;
s.push((node){a[i],i});
}
for(int i=1;i<=n;i++)
mn+=l[i]*r[i]*a[i];
cout<<mx-mn<<endl;
}
2.27no crossing
给出一个有向图,找一条恰好经过 k 个点的最短路径,要求每次选的边不能跃过之前已经经过的节点。即对于路径中的边 x→y ,不存在以前经过的点 t 使得三者的编号满足 min(x,y)≤t≤max(x,y) 。
n,k<=100
【cf793D】
思路
转化一下表述,即是要求对于一个数轴,从一点出发进行k-1次跳跃,每次跳跃的区间必须与先前的所有区间为子区间或者不相交,求k-1一次跳跃后的最小花费。
明显的区间dp,处理时反向建边可以颠倒先后区间的包含关系。(正向建边也行)
#include<bits/stdc++.h>
using namespace std;
const int maxn=110;
int f[maxn][maxn][maxn][2];
struct node{int v,c;};
int g[maxn][maxn];
int n,m,K;
signed main(){
cin>>n>>K>>m;K--;
memset(g,0x3f,sizeof g);
for(int i=1,u,v,c;i<=m;i++){
scanf("%d%d%d",&u,&v,&c);
g[v][u]=min(g[v][u],c);
}
memset(f,0x3f,sizeof f);
memset(f[0],0,sizeof f[0]);
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
f[1][i][j][0]=g[j][i],f[1][i][j][1]=g[i][j];
for(int k=2;k<=K;k++){
for(int i=1;i<=n;i++)
for(int j=i+k;j<=n;j++)
for(int l=i+1;l<j;l++){
f[k][i][j][0]=min(f[k][i][j][0],min(f[k-1][l][j][0]+g[l][i],f[k-1][l][j][1]+g[j][i]));
f[k][i][j][1]=min(f[k][i][j][1],min(f[k-1][i][l][0]+g[i][j],f[k-1][i][l][1]+g[l][j]));
}
}
int ans;
ans=g[0][0];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
ans=min(ans,min(f[K][i][j][0],f[K][i][j][1]));
if(ans==g[0][0])ans=-1;
cout<<ans<<endl;
return 0;
}
2.28dis
若知道异或的可逆性,就是一个裸的lca。
如不知道,则还需用倍增数组记入一下区间的异或和。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+1000;
int a[maxn],dep[maxn],s[maxn];
int p[maxn][20];
int n,m;
vector<int>g[maxn];
void dfs(int u,int dp){
dep[u]=dp;
for(int i=0;i<g[u].size();i++){
int v=g[u][i];
if(!dep[v]){
s[v]=s[u]^a[v];
p[v][0]=u;
dfs(v,dp+1);
}
}
}
inline int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
for(int i=18;i>=0;i--)
if(dep[x]-(1<<i)>=dep[y])x=p[x][i];
if(x==y)return x;
for(int i=18;i>=0;i--)
if(p[x][i]!=p[y][i])
x=p[x][i],y=p[y][i];
return p[x][0];
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<n;i++){
int x,y;scanf("%d%d",&x,&y);
g[x].push_back(y),g[y].push_back(x);
}
memset(dep,0,sizeof dep);
s[1]=a[1],p[1][0]=1;
dfs(1,1);
for(int i=1;(1<<i)<=n;i++)
for(int j=1;j<=n;j++)
p[j][i]=p[p[j][i-1]][i-1];
while(m--){
int x,y;
scanf("%d%d",&x,&y);
printf("%d\n",s[x]^s[y]^s[lca(x,y)]^s[lca(x,y)]^a[lca(x,y)]);
}
return 0;
}
3.1选数
题面
给n个正整数,问是否能从其中选出若干数,使得\(sum({a_i1,a_i2,...a_ik})\;mod\;n=\;0\)
输出任何一种组合即可,不存在输出-1
n<=1e5,a_i<=1e9
solution
仅当n<=1e4时,可以直接dp。
考虑mod n=0的特殊性,不难发现余数大概率会出现重复。
再考虑ai的前缀和,如果余数为0,则答案已出。
若余数不为零,则必会有\(i≠j,且s_i\;mod\;n=s_j\;mod\;n\),此时\(sum({a_i+1,a_i+2,...a_j})\;mod\;n=\;0\)
所以一定有解。。。(多少有点脑筋急转弯)
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+1000;
int s[maxn]={0};
int a[maxn];
int c[maxn];
int n;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
int x;scanf("%d",&a[i]);
s[i]=(s[i-1]+a[i])%n;
}
memset(c,0,sizeof c);
for(int i=1;i<=n;i++){
if(s[i]==0){
printf("%d\n",i);
for(int j=1;j<i;j++)
printf("%d ",j);
printf("%d\n",i);
break;
}
else if(c[s[i]]){
printf("%d\n",i-c[s[i]]);
for(int j=c[s[i]]+1;j<i;j++)
printf("%d ",j);
printf("%d\n",i);
break;
}
else c[s[i]]=i;
}
}
3.2序列操作
题面
对于一个长度1e6的自然数序列进行至多1e6次操作,分两种:改变一个点,或使得序列中所有小于y的数都变为y
输出全部操作完成后的序列。
solution
结果不符合区间可加性,但操作是区间可分,且tag上可叠加的。
故是一道只修改无询问的线段树。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+1000;
int a[maxn],n,q;
int t[maxn<<2];
inline void read(int &x){
char tmp=getchar();x=0;
while(tmp<'0'||tmp>'9')tmp=getchar();
while(tmp>='0'&&tmp<='9')x=(x<<1)+(x<<3)+tmp-'0',tmp=getchar();
}
void build(int x,int l,int r){
if(l==r){
t[x]=a[l];
return ;
}
int mid=l+r>>1;
build(x<<1,l,mid);
build(x<<1|1,mid+1,r);
t[x]=0;
}
void modify(int x,int l,int r,int p,int val){
if(l==r){
t[x]=val;
return ;
}
int mid=l+r>>1;
t[x<<1]=max(t[x<<1],t[x]),t[x<<1|1]=max(t[x<<1|1],t[x]),t[x]=0;
if(p<=mid)modify(x<<1,l,mid,p,val);
else modify(x<<1|1,mid+1,r,p,val);
}
void push_tag(int x,int l,int r){
if(l==r){
if(l!=n)printf("%d ",t[x]);
else printf("%d\n",t[x]);
return ;
}
int mid=l+r>>1;
t[x<<1]=max(t[x<<1],t[x]),t[x<<1|1]=max(t[x<<1|1],t[x]);
push_tag(x<<1,l,mid);
push_tag(x<<1|1,mid+1,r);
}
int main(){
cin>>n>>q;
for(int i=1;i<=n;i++)
read(a[i]);
build(1,1,n);
while(q--){
int op,x,y;
read(op);
if(op==1){
read(x),read(y);
modify(1,1,n,x,y);
}
else{
read(y);
t[1]=max(t[1],y);
}
}
push_tag(1,1,n);
}
solution2
每个点的值等于max{最后一次1操作的值,1操作之后最大的y}
预先处理出y的后缀并计入每个点最后一次1操作的值和时间,就可以简化到O(n)
3.3数数
题面
在给定 N 长的数组 {A} 中进行 Q 次询问 [Li,Ri] 区间中不大于 Hi 的元素个数。
N,Q<=1e5, 1<=Li,Ri<=N, Hi<=1e9
solution
无修改,考虑离线算法。
将数组{A}和询问都按从小到大排序。Ai<=Hi时,Ai对应的所在点+1,否则利用前缀和输出元素个数。过程用树状数组维护。
【key】排序后每次询问的Hi递增,使得区间元素个数前缀和是单调递增的。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+1000;
struct node{
int val,id;
}a[maxn];
struct qt{
int l,r,h,id;
}b[maxn];
int t[maxn];
int res[maxn];
int n,m;
inline void read(int &x){
char tmp=getchar();x=0;
while(tmp<'0'||tmp>'9')tmp=getchar();
while(tmp>='0'&&tmp<='9')x=(x<<1)+(x<<3)+tmp-'0',tmp=getchar();
}
bool cmp1(node x,node y){
return x.val<y.val;
}
bool cmp2(qt x,qt y){
return x.h<y.h;
}
inline int lowbit(int x){return x&-x;}
inline void add(int x){
while(x<=n){
t[x]++;
x+=lowbit(x);
}
}
inline int query(int x){
int ans=0;
while(x>0){
ans+=t[x];
x-=lowbit(x);
}
return ans;
}
int main(){
int T;cin>>T;
while(T--){
cin>>n>>m;
for(int i=1;i<=n;i++)
read(a[i].val),a[i].id=i;
sort(a+1,a+n+1,cmp1);
for(int i=1;i<=m;i++)
read(b[i].l),read(b[i].r),read(b[i].h),b[i].id=i;
sort(b+1,b+m+1,cmp2);
memset(t,0,sizeof t);
int i=1,j=1;
while(i<=n&&j<=m){
if(a[i].val<=b[j].h){
add(a[i].id);
i++;
}
else{
res[b[j].id]=query(b[j].r)-query(b[j].l-1);
j++;
}
}
while(j<=m){
res[b[j].id]=query(b[j].r)-query(b[j].l-1);
j++;
}
for(int i=1;i<m;i++)
printf("%d ",res[i]);
printf("%d\n",res[m]);
}
}
solution2
假设每次询问强制在线,则我们可以用函数式(可持久化)线段树维护。(没有验证过可行性)
3.4Minimum Or Spanning Tree
题面
求最小异或生成树,权值在边。
solution