zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    DOM(九)使用DOM设置文本框
    DOM(八)使用DOM控制表单
    DOM(七)使用DOM控制表格
    Javascript制作伸缩的二级菜单
    Javascript屏蔽鼠标的右键的两种方法。
    DOM(六)事件类型
    DOM(五)事件对象
    DOM(四)事件流
    DOM(三)使用DOM + Css
    Javascript一个在页面内追加元素的小例子
  • 原文地址:https://www.cnblogs.com/BakaCirno/p/12520256.html
Copyright © 2011-2022 走看看