一轮集训DAY5测试结题报告及问题反思

一轮集训DAY5测试结题报告及问题反思

首先声明,作者菜鸡一个,如有错误欢迎指出。

Secret Message 秘密信息

读题,会发现题目就让球两个玩意:1. 给定字符串,求其是多少个字符串的前缀。2.给定字符串,求其有多少个字符串是他的前缀。
这两个玩意,第一个可以维护 \(siz\),查询后加上就是。第二个可以维护\(ed\),查询时边走边加就是。注意最后的时候siz和ed算重得减。

#define N 505005
int t[N<<2][2],ed[N<<2],siz[N<<2],cnt=1,n,m;
char c[105005];
void insert(char a[],int len){
	int p=1;
	for(int i=1;i<=len;i++){
		int x=a[i]-'0';
		if(!t[p][x])t[p][x]=++cnt;
		p=t[p][x];
		siz[p]++;
	}
	ed[p]++;
}
int find(char a[],int len){
	int p=1,ans=0;
	for(int i=1;i<=len;i++){
		int x=a[i]-'0';
		if(!t[p][x])return ans;
		p=t[p][x];
		ans+=ed[p];
	}
	ans+=siz[p]-ed[p];
	return ans;
}
int main(){
	//freopen("secret.in","r",stdin);
	//freopen("secret.out","w",stdout);
	ios::sync_with_stdio(false);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		int k;cin>>k;
		for(int j=1;j<=k;j++)cin>>c[j];
		insert(c,k);
	}
	for(int i=1;i<=m;i++){
		int k;cin>>k;
		for(int j=1;j<=k;j++)cin>>c[j];
		cout<<find(c,k)<<"\n";
	}
	return 0;
}

子序列

Magic简化版,套路地运用异或前缀和,设\(S_i=a_1\oplus a_2\oplus …\oplus a_i\)

然后问题化为:求满足\(s_i\oplus s_j\ge k\)的数对数量。

显然枚举一个\(s_i\),再设计函数solve(s,t)表示对于\(s\)计算\(s\oplus S_i\ge t\)的数对数量。

\(s,t\)的二进制表示分别为\((b_k,b_{k-1}…b_1),(c_k,c_{k-1}…c_1)\)

对于答案的计算,可以将其分为两部分:可以在当前位解决的和留在后续解决的。

则我们设指针\(p\)\(k\)遍历到\(1\),按位统计贡献,将这个过程看作我们不断填数的过程,设所填数为\(x\),其二进制表示为\((d_k,d_{k-1}…d_1)\)

分类讨论:

  1. \(b_p=1,c_p=1\)。此时,我们若想要保持\(x\oplus s>t\)的可能,就需要让\(x\oplus s\)的第\(p\)位为1,也即我们填上\(d_p=0\),此时我们无法计算出答案
  2. \(b_p=0,c_p=1\),类比情况一,填上\(d_p=1\)
  3. \(b_p=1,c_p=0\),此时我们将\(d_p\)填为\(1/0\),当填为\(0\)时,第\(p-1\sim 1\)位无论填什么都可以对答案产生贡献,所以直接累加上\(siz[t[p][0]]\)的贡献并将\(d_p\)填为\(1\)即可。
  4. \(b_p=0,c_p=0\),类比情况三,可以得到算上\(siz[t[p][1]]\)的贡献然后将\(d_p\)填为0即可。

Code:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 5050500
#define int long long
int t[N<<2][2],siz[N<<2],n,m,cnt=1,s[N<<1],ans;
void insert(int k){
    int p=1;
    for(int i=54;i>=0;--i){
        int x=(k>>i)&1;
        if(!t[p][x])t[p][x]=++cnt;
        p=t[p][x];
        siz[p]++;
    }
    return ;
} 
int find(int s,int tt){
    int ans=0,p=1;
    for(int i=54;i>=0;--i){
        if(!p)return ans;
        int x=(s>>i)&1,y=(tt>>i)&1;
        if(!y){
            ans+=siz[t[p][x^1]];
            p=t[p][x];
        }
        else {
            p=t[p][x^1];
        }
    }
    return ans;
}
void read(int &x){
    int s=0,w=1;
    char ch=getchar();
    while(ch>'9'||'0'>ch){
        if(ch=='-')w=-1;
        ch=getchar();
    }
    while(ch>='0'&&'9'>=ch){
        s=s*10+ch-'0';
        ch=getchar();
    }
    x=s*w;
    return ;
} 
signed main(){
    //freopen("subsequence.in","r",stdin);
    //freopen("subsequence.out","w",stdout);
    read(n),read(m);
    for(int i=1;i<=n;i++)read(s[i]);
    for(int i=1;i<=n;i++)s[i]^=s[i-1];
    insert(0);
    for(int i=1;i<=n;i++)insert(s[i]);
    for(int i=1;i<=n;i++){
        ans+=find(s[i],m-1);
    }
    ans+=find(0,m-1);
    cout<<ans/2;
    return 0;
}

播放列表

首先明确一个性质:设\(ans_i\)表示从\(i\)开始听,最后一首歌的序号,对于\(ans_i,ans_{i+1}\),如果\(a_i\le a_{i+1}\),则\(ans_i=ans_{i+1}\)

很显然的对吧。

那么,就可以求出所有的\(a_i>a_{i+1},ans_{i}\)的答案,就可以算出了。

所以可以设\(f_i\)表示\(ans_i=ans_{f_i}\)\(f_i\)是满足要求的最小的。

那么维护一个单调队列搞最大值,不满足要求的就出队,然后\(ans[q[h]]=i\)即可。

接着,我们再将\(i\)插入队列,插入过程中,由于小于\(a_i\)而出队的\(q[t]\),就满足我们上述说的\(f\)的意义,也即\(f[q[t]]=i\)

所以代码非常简洁。需要注意的是,由于需要跑两圈,所以应该复制为三倍。

	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		a[i+n]=a[i+n+n]=a[i];
		ans[i+n]=ans[i+n+n]=ans[i]=-1;
	} 
	for(int i=1;i<=3*n;i++){
		while(h<=t&&a[q[h]]>a[i]*2)ans[q[h++]]=i;
		while(h<=t&&a[q[t]]<=a[i])f[q[t]]=i,t--;
		q[++t]=i;
	}
	for(int i=3*n;i;--i)if(f[i])ans[i]=ans[f[i]];
	for(int i=1;i<=n;i++){ans[i]-=i;if(ans[i]<0)ans[i]=-1;}
	for(int i=1;i<=n;i++)cout<<ans[i]<<" ";

特别行动队

板子题,划分区间。

\(f_i\)表示前\(i\)个人组队的最大值,容易得到:

\[f_i=\max_{j<i}\lbrace f_j+a(s_i-s_j)^2+b(s_i-s_j)+c\rbrace \]

按照斜率优化套路,整理式子,提出仅含\(i\)的项,比较决策\(j,k\),得到:

\(f_j+as_j^2-2as_is_j-bs_j>f_k+as_k^2-2as_is_k-bs_k\)

稍微整理,设\(Y(n)=f_n+as_n^2-bs_n,X(n)=2s_j\)

可以得到:

\[\frac{Y(j)-Y(k)}{X(j)-X(k)}>as_i \]

注意到\(as_i\)递减,\(X\)递增,是大于号,使用单调队列维护上凸壳,更新答案即可。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 1006060
#define int long long
int q[N],h=1,t=1,n,m,a,b,c,s[N],f[N];
int X(int x){
	return 2*s[x];
}
int Y(int x){
	return f[x]+a*s[x]*s[x]-b*s[x];
}
int up(int i,int j){
	return Y(i)-Y(j);
}
int down(int i,int j){
	return X(i)-X(j);
}
long double slope(int i,int j){
	return down(i,j)==0?1e18:1.0*up(i,j)/down(i,j);
}
void get(int i,int j){
//	cout<<i<<" "<<j<<endl;
	f[i]=f[j]+a*(s[i]-s[j])*(s[i]-s[j])+b*(s[i]-s[j])+c;
}
void read(int &x){
	int s=0,w=1;
	char ch=getchar();
	while(ch>'9'||'0'>ch){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch>='0'&&'9'>=ch){
		s=s*10+ch-'0';
		ch=getchar();
	}
	x=s*w;
} 
signed main(){
	//freopen("special.in","r",stdin);
	//freopen("special.out","w",stdout);
	read(n),read(a),read(b),read(c);
	for(int i=1;i<=n;i++)f[i]=-0x3f3f3f3f3f3f3f;
	for(int i=1;i<=n;i++)read(s[i]);
	for(int i=1;i<=n;i++)s[i]+=s[i-1];
	for(int i=1;i<=n;i++){
		while(h<t&&slope(q[h],q[h+1])>=1.0*a*s[i])h++;
		get(i,q[h]);
		while(h<t&&slope(q[t],q[t-1])<=slope(i,q[t]))t--;
		q[++t]=i;
	} 
	cout<<f[n];
	return 0;
}

CF631E

首先,设\(S=\sum_{i=1}^na_i\times i\),我们仅仅需要考虑变化量,并且将其最大化。

\(s_i=\sum_{k=1}^ia_k\)

所以,分类讨论。

第一个情况,\(i\)移动到\(j\)的后面,且\(j<i\),这时候的贡献变化为:\(-i\times a_i+(j+1)\times a_i+s_{i}-s_{j}-a_i\)

第二个情况,\(i\)移动到\(j\)的后面,且\(j>i\),这时候的贡献变化为:\(-i\times a_i+(j+1)\times a_i-s_j+s_{i}-a_i\)

二者等价,都为:\((j-i)\times a_i+s_i-s_j\)

所以,设\(f_i\)表示将第\(i\)个数进行移动,可以增加的最大权值,可以得到:

\[f_i=\max\lbrace (j-i)\times a_i+s_i-s_j\rbrace \]

套路:比较决策\(j>k\)\(j\)优于\(k\),得到:

\[j\times a_i-s_j>k\times a_i-s_k \]

进行移项,变式,设\(Y(n)=s_n,X(n)=n\)

\[\frac{Y(k)-Y(j)}{X(k)-X(j)}<a_i \]

小于号,下凸壳。

需要注意的是,\(a_i\)不具备单调性,故需要二分处理。

而在此题中有特殊情况,也即所有的\(j\)都可以对\(i\)产生贡献,故我们不妨先将所有的\(j\)插入凸壳里,然后对于每一个\(a_i\)进行处理。

这里有两种处理方式,第一种是对\(a_i\)进行排序,使用单调队列进行维护,第二种是直接进行二分,问题都不大。

#include<bits/stdc++.h>
using namespace std;
#define N 500500
#define int long long
int n,f[N],g[N],q[N],s[N],h=1,t=1,a[N],S;
int Y(int x){
	return s[x];
}
int X(int x){
	return x;
}
int up(int i,int j){
	return Y(i)-Y(j);
}
int down(int i,int j){
	return X(i)-X(j);
}
long double slope(int i,int j){
	return down(i,j)==0?1e18:1.0*up(i,j)/down(i,j);
}
void get1(int i,int j){
//	cout<
	f[i]=s[i]-s[j]+(j-i)*a[i];
}
void read(int &x){
	int s=0,w=1;
	char ch=getchar();
	while(ch>'9'||'0'>ch){
		if(ch=='-')w=-1;
		ch=getchar();
	}
	while(ch>='0'&&'9'>=ch){
		s=s*10+ch-'0';
		ch=getchar();
	}
	x=s*w;
} 
inline int find (int val){//二分找斜率
    int L=h+1, R=t, cur=1;
    while(L<=R){
        int mid=(L+R)>>1;
        if(slope(q[mid],q[mid-1])<=1.0*val)L=mid+1,cur=mid;//注意这个二分别挂掉了。
        else R=mid-1;
    }
    return q[cur];
}
signed main(){
	//freopen("maxweight.in","r",stdin);
	//freopen("maxweight.out","w",stdout);
	read(n);
	for(int i=1;i<=n;i++)read(a[i]);
	for(int i=1;i<=n;i++)S+=a[i]*i,s[i]=s[i-1]+a[i];
	for(int i=1;i<=n;i++){ 
		while(h<t&&slope(i,q[t])<=slope(q[t],q[t-1]))--t;//下凸壳,斜率递减
		q[++t]=i;
	}
	for(int i=1;i<=n;i++){
		get1(i,find(a[i]));
	} 
	int ans=0;
	for(int i=1;i<=n;i++)ans=max(ans,f[i]);
	cout<<S+ans;
	return 0; 
}

总结

事实上,在本次测试中,鄙人呈现出以下错误:

  1. 粗心大意,T2并未注意到答案需要long long,100->15

  2. 盲目自信,在调试T5时对自己推的式子过于自信,导致调试1h+无果,事实上下来重新推一次式子轻松AC

  3. 转化能力弱,思维能力弱,需要加强,在T3并未发现性质,导致对于自己的单调队列做法过分怀疑,只打了暴力

解决方案:

  1. 每次考试预留10min左右时间检查答案是否爆int,数组大小是否不当,函数返回值是否没写,能否通过编译,使用函数的头文件是否写上去了,是否存在越界风险和除0,
  2. 在检查代码无问题后,若无果,需要对思路重新进行梳理
  3. 多练题,独立思考
  4. 每次对拍必须搞几组极限数据
posted @ 2023-03-05 07:40  spdarkle  阅读(12)  评论(0编辑  收藏  举报