2018年宁夏邀请赛/2019年银川网络赛题解

这套题真的有点传奇了,考了两次。虽然银川赛区名声不行,但这套题质量还是过硬的,所以来补一下,还是收获颇丰。

A. Maximum Element In A Stack

题意

实现一个支持查询当前栈中元素的最大值的栈。

题解

最大栈的模板题,相当于在原始栈的基础上再开一个栈存前缀最大值,pop操作与原栈同步就好。

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stack>
using namespace std;
typedef long long LL;
int n,p,q,m;
unsigned int SA,SB,SC;
unsigned int rng61(){
	SA^=SA<<16;
	SA^=SA>>5;
	SA^=SA<<1;
	unsigned int t=SA;SA=SB;
	SB=SC;
	SC^=t^SA;
	return SC;
}

stack<int> sta,ms;
void push(int x){
	if(!ms.empty()) ms.push(max(x,ms.top()));
	else ms.push(x);
	sta.push(x);
}
void pop(){ 
	if(!ms.empty()) ms.pop(),sta.pop();
}

void solve(int Case){
	while(!ms.empty()) sta.pop(),ms.pop();
	scanf("%d%d%d%d%u%u%u",&n,&p,&q,&m,&SA,&SB,&SC);
	LL ans=0;
	for(int i=1;i<=n;i++){
		if(rng61()%(p+q)<p) push(rng61()%m+1);
		else pop();
		if(!ms.empty()) ans^=1ll*ms.top()*i;
	}
	printf("Case #%d: %lld
",Case,ans);
}

int main(){
	int T;scanf("%d",&T);
	for(int i=1;i<=T;i++) solve(i);
	return 0;
}

B. Rolling The Polygon

题意

给你一个多边形,沿逆时针滚一圈,要求多边形中某一点的运动路程。

题解

其实可以发现多边形沿一个角滚一下,这个点的轨迹是一个以该点到滚动角的距离为半径,滚动角的补角为角度的扇形,可以直接把这个距离算出来。

代码

#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
using namespace std;
int n;
double x[55],y[55],ag[55],dis[55];

double calc(int i,int j){
	double res=(x[i]-x[j])*(x[i]-x[j]);
	res+=(y[i]-y[j])*(y[i]-y[j]);
	return sqrt(res);
}

double calcangle(int i,int j,int k){
	double res=x[i]*y[j]+x[j]*y[k]+x[k]*y[i]-y[j]*x[k]-y[i]*x[j]-y[k]*x[i];
	double a=calc(i,j),b=calc(k,j),c=calc(i,k);
	res/=a*b;
	res=asin(res);
	if(c*c>b*b+a*a) return res;
	else return 3.1415926525-res;
}

void solve(int Case){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%lf%lf",&x[i],&y[i]);
	scanf("%lf%lf",&x[0],&y[0]);
	double ans=0;
	for(int i=1,a=i-1,b=i,c=i+1;i<=n;i++,a--,b--,c--){
		if(a<1) a+=n;if(b<1) b+=n;if(c<1) c+=n;
		ag[i]=calcangle(a,b,c);
		dis[i]=calc(0,b);
		//printf("(%.0lf,%.0lf),(%.0lf,%.0lf),(%.0lf,%.0lf) angle=%lf dis=%lf
",x[a],y[a],x[b],y[b],x[c],y[c],ag[i],dis[i]);
		ans+=ag[i]*dis[i];
	}
	printf("Case #%d: %.3lf
",Case,ans);
}

int main(){
	int T;scanf("%d",&T);
	for(int i=1;i<=T;i++) solve(i);
	return 0;
}

C. Caesar Cipher

本场白给题

#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
using namespace std;
int n,m;
char s1[55],s2[55],t[55];

void solve(int Case){
	scanf("%d%d",&n,&m);
	scanf("%s%s%s",s1+1,s2+1,t+1);
	int sub=s1[1]-s2[1];
	for(int i=1;i<=m;i++) t[i]=(t[i]-'A'+sub+26)%26+'A';
	printf("Case #%d: %s
",Case,t+1);
}

int main(){
	int T;scanf("%d",&T);
	for(int i=1;i<=T;i++) solve(i);
	return 0;
}

D. Take Your Seat

题意

杜华是个疯子,有一天他去坐飞机,轮到他上机时,他会从没有人的位置中随机选择一个位置坐下。如果其他乘客发现自己位置被人占之后,他也会疯掉,从没有人的位置中随机选择一个坐下。那么问题来了:

1.共有 (n) 个乘客,编号 (1,2,...,n),对应座位号 (1,2,...,n),依次登机,杜华是 (1) 号乘客,问第 (n) 位乘客能做 (n) 号位置的概率。

2.共有 (m) 个乘客,编号 (1,2,...,m),对应座位号 (1,2,...,m),随机顺序登机,问最后一个登机的乘客能坐到自己对应位置上的概率。

题解

概率杀我。

先看第一个问题:

  • 如果杜华坐了 (1) 号,那么此时概率 (=1)
  • 如果杜华坐了 (i(1<i<n)) 号,那么 (2,3,...,i-1) 这些乘客可以顺利坐下,然后第 (i) 位乘客会像杜华一样随机挑选,可以发现现在问题还是同样的问题,只是 (n) 变成了 (n-i+1)
  • 如果杜华坐了 (n) 号,当然概率直接为 (0)

那么如果设该问题的概率为 (f[n]),可知 (f[n]=frac{1+f[n-1]+f[n-2]+...+f[2]}{n}) ,那这时就可以计算了,其实手算一下,可以发现 (f[n]=frac{1}{2})

再来看第二个问题,可以发现:

  • 如果杜华是第 (i) 个登机的人,那么这个问题就相当于规模为 (m-i+1) 的一问题。

那么设二问题概率为 (g[m]),可得 (g[m]=frac{f[m]+f[m-1]+...+f[1]}{m})

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
using namespace std;
double f[55],g[55];

void solve(int Case){
	int n,m;
	scanf("%d%d",&n,&m);
	
	printf("Case #%d: %.6lf %.6lf
",Case,f[n],g[m]);
}

int main(){
	f[1]=g[1]=1;
	double sum=0;
	for(int i=2;i<=50;i++){
		sum+=f[i-1];
		f[i]=sum/i;
		g[i]=(sum+f[i])/i;
	}
	int T;scanf("%d",&T);
	for(int i=1;i<=T;i++) solve(i);
	return 0;
}

E. 2-3-4 Tree

题意

给出234树的形态和插入方法,然后依次插入一个 (n) 的全排列,要求输出最后234树的前序遍历。

如果总共加入234树的值不超过 (3) 个,那么这棵树显然只有根节点一个点。否则从根节点开始按一下规律插入值:

1.如果当前节点是一个 '4节点':

  • 将这个节点中间的值拿出来,左右两边的值和四个儿子构成两个 '2节点'。
  • 如果原 '4节点' 是根节点,那么把中间值重新构成一个根节点,让新生成的两个 '2节点' 成为它的儿子。
  • 否则把中间值插入原 '4节点' 的父节点,新生成的两个 '2节点' 成为原 '4节点' 父节点的儿子。

2.如果当前节点是叶节点了,那么直接将值加入当前节点中,插入完成。

3.否则:

  • 寻找值属于当前节点的哪个子节点。
  • 进入子节点继续从第 (1) 步开始判断。

题解

有点变态的数据结构模拟题啊……读懂题都要下一番功夫。

其实构造这个234树的难点就在于对 '4节点' 的调整。我们来考虑一下:

1.如果当前节点为 '4节点' 并且当前节点是根节点:

  • 新建一个节点,将右值存入,它的1儿子为原4节点的3儿子,2儿子为原4节点的4儿子。
  • 再新建一个节点,将中值存入,它的1儿子为原4节点,2儿子为刚刚新建的节点。指定它为新的根节点。
  • 设定这 (3) 个点的大小信息,分配儿子时也不要忘记重新定向儿子的父亲。

2.如果当前节点为 '4节点' 并且当前节点不是根节点:

  • 新建一个节点,将右值存入,它的1儿子为原4节点的3儿子,2儿子为原4节点的4儿子。
  • 将中值加入原4节点的父节点。让新建的节点成为原4节点的父节点的又一个儿子。
  • 调整节点信息,并对父节点中的值排序,对父节点的所有儿子进行排序。

3.经过调整之后,从父节点开始,寻找应该插入哪颗子树。注意这时如果父节点是 '4节点',也不调整了。

如果是不需要调整的节点,那就很好找了。

  • 要注意的点:往一个节点中插入值之后,一定要排序。向一个节点加入了儿子,也要对它所有儿子排序;加入儿子同时也要将儿子的父亲指针指向父节点。

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
using namespace std;
const int N=5010;
int n;
bool cmp(int x,int y);


struct _234Tree{
	int data[N][3],siz[N],tr[N][4],fa[N],rt,tot;
	void init(){
		memset(data,0,sizeof(data));
		memset(tr,0,sizeof(tr));
		memset(fa,0,sizeof(fa));
		memset(siz,0,sizeof(siz));
		rt=tot=0;
	}
	void insert(int id,int val){
		if(siz[id]==3){
			if(id==rt){
				rt=++tot;int rs=++tot;
				data[rt][0]=data[id][1],siz[rt]=1,tr[rt][0]=id,tr[rt][1]=rs;
				data[rs][0]=data[id][2],siz[rs]=1,tr[rs][0]=tr[id][2],tr[rs][1]=tr[id][3];fa[rs]=rt;
				fa[tr[id][2]]=rs;fa[tr[id][3]]=rs;
				siz[id]=1;fa[id]=rt;
			}
			else{
				int par=fa[id],rs=++tot;
				data[par][siz[par]++]=data[id][1],tr[par][siz[par]]=rs;
				data[rs][0]=data[id][2],siz[rs]=1,tr[rs][0]=tr[id][2],tr[rs][1]=tr[id][3];fa[rs]=par;
				fa[tr[id][2]]=rs;fa[tr[id][3]]=rs;
				siz[id]=1;
				sort(data[par],data[par]+siz[par]);
				sort(tr[par],tr[par]+siz[par]+1,cmp);
			}
			int par=fa[id];
			if(val<data[par][0]) insert(tr[par][0],val);
			else if(siz[par]==1||val<data[par][1]) insert(tr[par][1],val);
			else if(siz[par]==2||val<data[par][2]) insert(tr[par][2],val);
			else insert(tr[par][3],val);
			return;
		}
		if(!tr[id][0]){
			data[id][siz[id]++]=val;
			sort(data[id],data[id]+siz[id]);
			return;
		}
		if(val<data[id][0]) insert(tr[id][0],val);
		else if(siz[id]==1||val<data[id][1]) insert(tr[id][1],val);
		else if(siz[id]==2||val<data[id][2]) insert(tr[id][2],val);
	}
	void dfs(int id){
		if(!id) return;
		for(int i=0;i<siz[id];i++) cout<<data[id][i]<<" ";cout<<endl;
		for(int i=0;i<=siz[id];i++) dfs(tr[id][i]);
	}
}_234;
bool cmp(int x,int y){
	return _234.data[x][0]<_234.data[y][0];
}

void solve(int Case){
	printf("Case #%d:
",Case);
	_234.init();
	scanf("%d",&n);
	int x;
	scanf("%d",&x);
	_234.rt=_234.tot=1;
	_234.data[1][0]=x;
	_234.siz[1]=1;
	for(int i=2;i<=n;i++){
		scanf("%d",&x);
		_234.insert(_234.rt,x);
	}
	_234.dfs(_234.rt);
}

int main(){
	int T;scanf("%d",&T);
	for(int i=1;i<=T;i++) solve(i);
	return 0;
}

F. Moving On

题意

有一个点权边权图,多组询问,问从 (u)(v) 经过的点点权不超过 (w) 的路径的最小值。

题解

如果熟悉floyd最短路的话,这道题还是很简单的。

  • 首先从 (n) 的范来说,可以肯定用floyd。
  • 然后可以将询问离线,按 (w) 排序,把所有点也按点权排序。
  • 双指针,如果当前询问的 (w) 大于已经处理的点的点权,尝试继续处理之后的点。

代码

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#define ff first
#define ss second
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
const int N=2e5+10;
int n,m,ans[N];
int g[210][210];
PII p[210];
struct Qure{
	int u,v,w,id;
}q[N];
bool cmp(Qure a,Qure b){return a.w<b.w;}

void solve(int Case){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&p[i].ff),p[i].ss=i;
	for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) scanf("%d",&g[i][j]);
	for(int i=1;i<=m;i++) scanf("%d%d%d",&q[i].u,&q[i].v,&q[i].w),q[i].id=i;
	sort(p+1,p+n+1);
	sort(q+1,q+m+1,cmp);
	for(int i=1,j=0;i<=m;i++){
		while(j<n&&p[j+1].ff<=q[i].w){
			int k=p[++j].ss;
			for(int u=1;u<=n;u++)
				for(int v=1;v<=n;v++)
					g[u][v]=min(g[u][v],g[u][k]+g[k][v]);
		}
		ans[q[i].id]=g[q[i].u][q[i].v];
	}
	printf("Case #%d:
",Case);
	for(int i=1;i<=m;i++) printf("%d
",ans[i]);
}

int main(){
	int T;scanf("%d",&T);
	for(int i=1;i<=T;i++) solve(i);
	return 0;
}

G. Factories

题意

给一颗树,要求你从选 (k) 个叶节点,使得这 (k) 个叶节点各自之间的距离之和最短。

题解

这是个树形背包的板子题。

如果从以 (v) 为根节点的子树中选定了 (i) 个节点,那么在所有 (frac{k imes(k-1)}{2}) 条路径中,边 (v-fa_v) 被经过了 (i imes (k-i)) 次,所以可以推出方程:

[f[u][i]=minig(f[u][i],f[u][i-j]+f[v][j]+w_{uv} imes j imes (k-j)ig) ]

当然要注意,要用额外的数组来存 (f[u][i]),统计玩一颗子树之后再付给 (f[u][i]),这不是为了加快速度,而是为了避免重复统计同一颗子树中的信息。

这里我想起了 CCF 某次的第 5 题,我好像就是没用开另外的数组,导致只有 70 分。

代码

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <queue>
#define ff first
#define ss second
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
const int N=1e5+10;
const int inf=0x3f3f3f3f;
const LL INF=0x3f3f3f3f3f3f3f3f;
int n,k,cnt[N],d[N];
LL f[N][110],tmp[110];
int head[N],to[N*2],val[N*2],nxt[N*2],tot;

int read(){
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9') {x=x*10+c-'0';c=getchar();}
	return x*f;
}

void add(int u,int v,int w){
	to[++tot]=v;val[tot]=w;nxt[tot]=head[u];head[u]=tot;
}

void dfs(int u,int fa){
	cnt[u]=0;f[u][0]=0;
	if(d[u]==1) f[u][1]=0,cnt[u]=1;	
	if(d[u]==1) return;
	for(int i=1;i<=k;i++) f[u][i]=INF;
	for(int i=head[u];i;i=nxt[i]){
		if(to[i]==fa) continue;
		dfs(to[i],u);
		cnt[u]+=cnt[to[i]];
		if(cnt[u]>k) cnt[u]=k;
		int v=to[i];LL w=val[i];
		for(int j=0;j<=cnt[u];j++) tmp[j]=INF;
		for(int j=0;j<=cnt[u];j++) 
			for(int p=0;p<=cnt[v]&&p<=j;p++)
				tmp[j]=min(tmp[j],f[u][j-p]+f[v][p]+w*p*(k-p));
		for(int j=0;j<=cnt[u];j++) f[u][j]=tmp[j];
	}
}

void solve(int Case){
	printf("Case #%d: ",Case);
	n=read(),k=read();
	for(int i=1;i<=n;i++) head[i]=0,d[i]=0;
	tot=0;
	int u,v,w;
	for(int i=1;i<n;i++){
		u=read(),v=read(),w=read();
		add(u,v,w);add(v,u,w);
		d[u]++;d[v]++;
	}
	if(n==2){
		if(k==2) printf("%d
",w);
		else printf("0
");
		return;
	}
	int root=1;
	while(d[root]<2) root++;
	dfs(root,0);
	printf("%lld
",f[root][k]);
}

int main(){
	int T;scanf("%d",&T);
	for(int i=1;i<=T;i++) solve(i);
	return 0;
} 

H. Fight Against Monsters

题意

(n) 个怪兽,每个怪兽有血量和攻击力,你每回合会收到所有没有死掉的怪兽的依次攻击,问你杀死所有怪兽最少损失多少血量。

题解

微扰法贪心。

首先可以算出每只怪兽的血量可以顶我几回合的攻击,记为 (k_i),每只怪兽的攻击力记为 (a_i),那么假如我按顺序杀怪兽损失血量最少,那么我损失血量为:

[a_1 imes k_1+a_2 imes (k_1+k_2)+...+a_n imes sum_{i=1}^{n}k_i ]

假如我先杀第 (2) 只,再杀第 (1) 只怪兽,损失血量为:

[a_2 imes k_2+a_1 imes (k_1+k_2)+...+a_n imes sum_{i=1}^{n}k_i ]

此时我受到的伤害肯定不会减少,那么:

[egin{aligned}a_1 imes k_1+a_2 imes (k_1+k_2)+...+a_n imes sum_{i=1}^{n}k_i&le a_2 imes k_2+a_1 imes (k_1+k_2)+...+a_n imes sum_{i=1}^{n}k_i\a_1 imes k_1+a_2 imes k_1+a_2 imes k2&le a_2 imes k_2+a_1 imes k_1+a_1 imes k_2\a_2 imes k_1&le a_1 imes k_2end{aligned} ]

那么只需要按 (a_2 imes k_1le a_1 imes k_2) 这个顺序对怪兽排序,依次杀掉就可以了。

代码

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <algorithm>
#define ff first
#define ss second
using namespace std;
typedef long long LL;
typedef pair<LL,LL> PII;
const int N=1e5+10;
int n;
PII p[N];

bool cmp(PII a,PII b){return a.ss*b.ff<b.ss*a.ff;}

void solve(int Case){
	printf("Case #%d: ",Case);
	scanf("%d",&n);
	for(int i=1;i<=n;i++) {
		scanf("%d%d",&p[i].ss,&p[i].ff);
		LL num=0,sum=0;
		while(sum<p[i].ss) num++,sum+=num;
		p[i].ss=num;
	}
	sort(p+1,p+n+1,cmp);
	LL ans=0,sum=0;
	for(int i=1;i<=n;i++){
		sum+=p[i].ss;
		ans+=sum*p[i].ff;
	}
	printf("%lld
",ans);
}

int main(){
	int T;scanf("%d",&T);
	for(int i=1;i<=T;i++) solve(i);
	return 0;
}

K. Vertex Covers

题意

定义完全覆盖点集 (S)(G) 中任意一条边总有至少 (1) 个顶点在 (S) 中。

(S) 的积:(S) 中所有点的点权之积;空集的积为 (1)

(G) 的所有 (S) 的积之和。

题解

(n) 的范围来说,肯定是双向搜索。可以将前 (frac{n}{2}) 个点和其余 (n-frac{n}{2}) 个点分为两堆,分别枚举每堆的状态,算出每种状态的积。

这两堆种,如果有边在一堆之内,那么这个边的两端至少有一个点要被选。如果是有边在两堆之间,如果一堆的状态确定,可以发现另一堆最差的状态也确定了,然后另一堆中没被选的点其实是可选可不选,这就是这个集合的超集,这个超集之和就是这一堆所有方案之和。

计算超集之和就是高维前缀和:

	for(int u=0;u<nl;u++)
		for(int sta=0;sta<1<<nl;sta++)
			if(!(sta&1<<u)) fl[sta]=(fl[sta]+fl[sta|1<<u])%mod;

当然高维前缀和也可以计算子集之和:

	for(int u=0;u<nl;u++)
		for(int sta=0;sta<1<<nl;sta++)
			if(sta&1<<u) fl[sta]=(fl[sta]+fl[sta^1<<u])%mod;

代码

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <queue>
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
const int M=1e6+10;
LL a[40],ans,fl[M],fr[M];
int n,m,mod;
vector<int> g[40];

void solve(int Case){
	ans=0;
	scanf("%d%d%d",&n,&m,&mod);
	for(int i=0;i<n;i++) g[i].clear();
	for(int i=0;i<n;i++) scanf("%d",&a[i]);
	for(int i=0,u,v;i<m;i++){
		scanf("%d%d",&u,&v);
		u--;v--;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	int nl=n/2,nr=n-nl;
	for(int sta=0;sta<(1<<nl);sta++){
		bool flag=true;fl[sta]=1;
		for(int u=0;u<nl;u++){
			for(int v:g[u]){
				if(v>=nl) continue;
				if(!(sta&1ll<<u)&&!(sta&1ll<<v)) {flag=false;break;}
			}
			if(!flag) {fl[sta]=0;break;}
			if(sta&1ll<<u) fl[sta]=fl[sta]*a[u]%mod;
		}	
	}
	//高维前缀和 
	for(int u=0;u<nl;u++)
		for(int sta=0;sta<1<<nl;sta++)
			if(!(sta&1<<u)) fl[sta]=(fl[sta]+fl[sta|1<<u])%mod;
	
	for(int sta=0;sta<(1<<nr);sta++){
		bool flag=true;fr[sta]=1;
		int link=0;
		for(int i=0;i<nr;i++){
			int u=nl+i;
			for(int v:g[u])
				if(v<nl){
					if(!(sta&1<<i)) link|=1<<v;
				}
				else{
					int j=v-nl;
					if(!(sta&1<<i)&&!(sta&1<<j)) {flag=false;break;}
				}
			if(!flag) {fr[sta]=0;break;}
			if(sta&1<<i) fr[sta]=fr[sta]*a[u]%mod;
		}
		ans=(ans+fr[sta]*fl[link])%mod;
	}
	printf("Case #%d: %lld
",Case,ans);
}

int main(){
	int T;scanf("%d",&T);
	for(int i=1;i<=T;i++) solve(i);
	return 0;
}

L. Continuous Intervals

题意

给数列 (a_1,a_2,...,a_n),问有多少个连续子序列 (a_1,a_2,...,a_m) 满足排序后 (a_i-a_{i-1}le 1)

题解

其实这个题和蓝桥杯2013的最后一题基本一致,只是蓝桥杯是全排列,而这个是任意数。相似问题:

这类问题当然是用线段树解决的,但是用法有两种,我这里选择在原数列上来,这样形象一点,还有一种是在自然数列上进行,在这道题上就没那么好想、好写了。
设 f[i,r] 为 (a_i,a_{i+1},...,a_r) 排序后是几段,那么线段树可以维护 (f[1,r],f[2,r],...,f[r,r])
此时我加入 (a_{r+1}),观察 (f[1,r+1],f[2,r+1],...,f[r+1,r+1]) 相对 (f[1,r],f[2,r],...,f[r,r]) 会发生什么变化。
可以发现,如果数 (a_{r+1}) 上一次出现的位置在 (pos) 的话,(f[1,r+1],f[2,r+1],...,f[pos,r+1]) 相对之前是不会有变化的,因为本来这些区间里就有 (a_{r+1}),你再加一个 (a_{r+1}) 当然不会有任何影响。
如果 (a_{r+1}-1)(a_{r+1}+1) 中有一个数出现在 (pos1(pos1>l)) 位置,那么 (f[i,r+1]=f[i,r](l<ile pos1)),因为加入的 (a_{r+1}) 可以和这些区间中的某个数贴在一起,当然结果就不变了。而 (f[i,r+1]=f[i,r]+1,(pos1<ile r+1)),在这些区间中,(a_{r+1}) 很孤独,结果+1。
如果 (a_{r+1}-1)(a_{r+1}+1) 这两个数分别出现在 (pos1,pos2(l<pos1<pos2<r+1)) 位置,那么显然如果 (l<ile pos1)(f[i,r+1]=f[i,r]-1),因为加入了 (a_{r+1}) 后这些区间中会有两个分开的被连接到了一起。至于+1的情况,就和上面差不多了。
当然还有这两个数都没有出现的情况,直接所有区间都+1就行了,因为 (a_{r+1}) 在这些区间里很孤单

代码

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <map>
using namespace std;
typedef long long LL;
const int N=1e5+10;
int n,a[N];
map<int,int> lastpos;
struct SegTree{
	#define mid (l+r>>1)
	int minv[N*4],tag[N*4],num[N*4];
	void build(int id,int l,int r){
		minv[id]=tag[id]=0,num[id]=r-l+1;
		if(l==r) return;
		build(id<<1,l,mid);
		build(id<<1|1,mid+1,r);
	}
	void pushdown(int id){
		minv[id<<1]+=tag[id];tag[id<<1]+=tag[id];
		minv[id<<1|1]+=tag[id];tag[id<<1|1]+=tag[id];
		tag[id]=0;
	}
	void pushup(int id){
		minv[id]=min(minv[id<<1],minv[id<<1|1]);
		num[id]=0;
		if(minv[id]==minv[id<<1]) num[id]+=num[id<<1];
		if(minv[id]==minv[id<<1|1]) num[id]+=num[id<<1|1];
	}
	void upd(int id,int l,int r,int L,int R,int x){
		if(L<=l&&r<=R) {minv[id]+=x;tag[id]+=x;return;}
		if(tag[id]) pushdown(id);
		if(L<=mid) upd(id<<1,l,mid,L,R,x);
		if(R>mid) upd(id<<1|1,mid+1,r,L,R,x);
		pushup(id);
	}
	int ask(int id,int l,int r,int L,int R){
		if(L<=l&&r<=R) return minv[id]==1?num[id]:0;
		if(tag[id]) pushdown(id);
		int res=0;
		if(L<=mid) res+=ask(id<<1,l,mid,L,R);
		if(R>mid) res+=ask(id<<1|1,mid+1,r,L,R);
		return res;
	}
	#undef mid
}tr;

void solve(int Case){
	lastpos.clear();
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	tr.build(1,1,n);
	LL ans=0;
	for(int i=1;i<=n;i++){
		int l=lastpos[a[i]]+1,pos1=lastpos[a[i]-1],pos2=lastpos[a[i]+1];
		if(pos1>pos2) swap(pos1,pos2);
		if(pos1>=l) tr.upd(1,1,n,l,pos1,-1),tr.upd(1,1,n,pos2+1,i,1);
		else if(pos2>=l) tr.upd(1,1,n,pos2+1,i,1);
		else tr.upd(1,1,n,l,i,1);
		ans+=tr.ask(1,1,n,1,i);
		lastpos[a[i]]=i;
	}
	printf("Case #%d: %lld
",Case,ans);
}

int main(){
	int T;scanf("%d",&T);
	for(int i=1;i<=T;i++) solve(i);
	return 0;
}
原文地址:https://www.cnblogs.com/BakaCirno/p/12520256.html