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\n",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\n",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\n",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\n",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\n",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:\n",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:\n",Case);
for(int i=1;i<=m;i++) printf("%d\n",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\times(k-1)}{2}\) 条路径中,边 \(v-fa_v\) 被经过了 \(i\times (k-i)\) 次,所以可以推出方程:
当然要注意,要用额外的数组来存 \(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\n",w);
else printf("0\n");
return;
}
int root=1;
while(d[root]<2) root++;
dfs(root,0);
printf("%lld\n",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\),那么假如我按顺序杀怪兽损失血量最少,那么我损失血量为:
假如我先杀第 \(2\) 只,再杀第 \(1\) 只怪兽,损失血量为:
此时我受到的伤害肯定不会减少,那么:
那么只需要按 \(a_2\times k_1\le a_1\times 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\n",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\n",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<i\le pos1)\),因为加入的 \(a_{r+1}\) 可以和这些区间中的某个数贴在一起,当然结果就不变了。而 \(f[i,r+1]=f[i,r]+1,(pos1<i\le r+1)\),在这些区间中,\(a_{r+1}\) 很孤独,结果+1。
如果 \(a_{r+1}-1\) 和 \(a_{r+1}+1\) 这两个数分别出现在 \(pos1,pos2(l<pos1<pos2<r+1)\) 位置,那么显然如果 \(l<i\le 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\n",Case,ans);
}
int main(){
int T;scanf("%d",&T);
for(int i=1;i<=T;i++) solve(i);
return 0;
}