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