信友队 2023 游记

\(7.10\text{ Day }1\)

几乎没睡着。上午讲主定理和复杂度分析,根本不会,用递归树和单点乱搞,甚至还上去讲了一下。然后讲了 CF949E\(*2700\) 的构造,听懂了,胡了一个证明,但是写假了 \(\text{TLE on test }15\)。wtcl /kk。

下午是模拟赛。由于一直在调 CF949E,晚了 \(10\text{min}\)。/fn。

先看 \(\text{A}\)

  • 给出长为 \(n\) 的数组 \(A,B\),求前 \(n\) 大的乘积。

  • \(n\le 10^5\)

\(n\) 路归并模板,排序以及优先队列乱搞。

$\color{orange}赛时代码$
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int n,a[N],b[N];
priority_queue<pair<int,pair<int,int>>>q;
signed main(){
	cin.tie(0);
	cout.tie(0);
	ios::sync_with_stdio(0);
	cin>>n;
	for(int i=1;i<=n;++i){
		cin>>a[i];
	}
	for(int i=1;i<=n;++i){
		cin>>b[i];
	}
	sort(b+1,b+1+n,greater<int>());
	for(int i=1;i<=n;++i){
		q.push({a[i]*b[1],{1,i}});
	}
	for(int i=1;i<=n;++i){
		pair<int,pair<int,int>>p=q.top();
		q.pop();
		cout<<p.first<<' ';
		q.push({a[p.second.second]*b[p.second.first+1],{p.second.first+1,p.second.second}});
	}
} 

\(score=100+0+0+0=100\)

然后看了一会 \(\text{B}\)。感觉题意暧昧不清 ,于是去看 \(\text{D}\)

  • 给出 \(n\) 个点的树,根为 \(1\)。点有点权 \(a_i\),有两种操作:

    • \(1,x,v\)\(a_x\leftarrow a_x+v\)

    • \(2,x\),查询 \(x\) 到根路径上的点的点权和。

这不是树剖模板题吗?过了样例,兴致勃勃交了一发结果爆 \(0\)。自己造了个二叉树,发现边界搞错了,改了就过了。

$\color{orange}赛时代码$
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int n,m,a[N],d[N],sz[N],f[N],t[N],h[N],dfn[N],cnt,bit[N];
vector<int>g[N];
void modify(int x,int k){
	for(int i=x;i<=n;i+=i&(-i)){
		bit[i]+=k;
	}
}
int query(int x){
	int ret=0;
	for(int i=x;i;i-=i&(-i)){
		ret+=bit[i];
	}
	return ret;
}
void dfs1(int u,int fa){
	sz[u]=1;
	for(int v:g[u]){
		if(v^fa){
			d[v]=d[u]+1;
			dfs1(v,f[v]=u);
			sz[u]+=sz[v];
		}
	}
}
void dfs2(int u,int fa){
	for(int v:g[u]){
		if(v^fa){
			if((sz[v]<<1)>sz[u]){
				t[h[u]=v]=t[u];
			}else{
				t[v]=v;
			}
			dfs2(v,u);
		}
	}
}
void dfs3(int u,int fa){
	dfn[u]=++cnt;
	modify(cnt,a[u]);
	if(h[u]){
		dfs3(h[u],u);
	}
	for(int v:g[u]){
		if((v^fa)&&(v^h[u])){
			dfs3(v,u);
		}
	}
}
int Query(int x){
	int ret=0;
	while(x){
		ret+=query(dfn[x])-query(dfn[t[x]]-1);
		x=f[t[x]];
	}
	return ret;
}
signed main(){
	cin.tie(0);
	cout.tie(0);
	ios::sync_with_stdio(0);
	cin>>n>>m;
	for(int i=1;i<=n;++i){
		cin>>a[i];
	}
	for(int i=1,u,v;i^n;++i){
		cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	dfs1(1,0);
	dfs2(t[1]=1,0);
	dfs3(1,0);
	for(int op,x,v;m--;){
		cin>>op>>x;
		if(op&1){
			cin>>v;
			modify(dfn[x],v);
		}else{
			cout<<Query(x)<<'\n';
		}
	}
}

\(score=100+0+0+100=200\)

然后 \(\text{B}\) 修正过来了,始做之。

  • 给出 \(n\) 个点的序列 \(h_1\sim h_n\),求出最长的波形序列长度(即对于非两侧的数均大于或小于其两边的数)。

  • \(n\le 10^5\)\(0\le h_i\le 10^6\)

像 dp。设以 \(f_{i,0/1}\) 表示以 \(i\) 结尾且 \(i\) 为最小于/大于前一个数的最长波形序列。有 \(f_{i,0}=\max\limits_{j<i\land h_j>h_i}\{f_j\}+1\)\(f_{i,1}\) 类似。树状数组维护之。答案为 \(\max\limits_{i=1}^n\{\max\{f_{i,0},f_{i,1}\}\}\)。一开始把 \(i\) 打成 \(x\) 了,只有 \(10\text{pts}\),改了过了发现还是只有 \(50\text{pts}\),特判了一下发现 \(h\) 的值域是自然数题面又出锅了。然后经过调整值域就过了。

$\color{orange}赛时代码$
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,h[N],bit[6][N*10],f[N][2],ans=1;
void modify(int id,int x,int k){
	for(int i=x;i<=1e6+2;i+=i&(-i)){
		bit[id][i]=max(bit[id][i],k);
	}
}
int query(int id,int x){
	int ret=0;
	for(int i=x;i;i-=i&(-i)){
		ret=max(bit[id][i],ret);
	}
	return ret;
}
int main(){
	cin.tie(0);
	cout.tie(0);
	ios::sync_with_stdio(0);
	cin>>n;
	for(int i=1;i<=n;++i){
		cin>>h[i];
		++h[i];
		f[i][0]=f[i][1]=1;
	}
	for(int i=1;i<=n;++i){
		f[i][0]=max(f[i][0],query(1,1e6+1-h[i])+1);
		f[i][1]=max(f[i][1],query(0,h[i]-1)+1);
		modify(0,h[i],f[i][0]);
		modify(1,1e6-h[i]+2,f[i][1]);
	}
	for(int i=1;i<=n;++i){
		ans=max({ans,f[i][0],f[i][1]});
	}
	cout<<ans;
}

\(score=100+100+0+100=300\)

最后看 \(\text{C}\)

  • 给出长度为 \(n\) 的字符串 \(A\) 和长度为 \(m\) 的字符串 \(B\)。从左至右依次选取 \(K\)\(A\)不相交子串,然后将它们依次首尾相连。求出使得连接后的串等于 \(B\) 的方案数,模 \(10^9+7\)

  • \(n\le 10^3\)\(K\le m\le 200\)

又是 dp,还是计数。以为自己要寄。还是硬着头皮推了一下方程。设 \(f_{i,j,k}\)\(A\) 中前 \(i\) 个字符,选取 \(k\) 段,使得得到的串等于 \(B\) 的前 \(j\) 个字符。

考虑是否选取 \(A\)\(i\) 位,不选则 \(f_{i,j,k}\leftarrow f_{i,j,k}+f_{i-1,j,k}\),若选取,则找到 \(A\) 的第 \(i\) 位和 \(B\) 的第 \(j\) 位往前完全匹配的子段的最大长度 \(p\)\(f_{i,j,k}\leftarrow f_{i,j,k}+\sum\limits_{q=1}^pf_{i-q,j-q,k-1}\)。测了一发样例,没过。慌了,发现初始化忘了。改了一下,这次过了两个。先交一发,\(30\text{pts}\),又 \(\text{WA}\)\(\text{TLE}\),还没卡过。/fn。原来初始化错了,应该是 \(f_{i,0,0}=1\)。交了一发,\(\text{TLE }90\text{pts}\)。/fn。原来这玩意时间复杂度是 \(\mathcal{O}(nm^2k)\) 的,寄。

真的寄了吗?发现第二类转移是一个求区间和的形式。具象成网格图,就是过格子 \((i,j)\) 往左上、右下走的一条斜线上,求一段连续格子权值的和,格子 \((x,y)\) 权值是 \(f_{x,y,k-1}\)。维护斜线的前缀和。把 \(k\) 的循环放在最外面。设 \(s_{i,j}\) 表示这条斜线一直到 \((0,j-i)\) 这些格子的值的和,有 \(s_{i,j}=s_{i-1,j-1}+f_{i,j,k-1}\),注意一下边界 \(0\)。然后还要处理一下 \(A_i\)\(B_j\) 往前的最长匹配 \(pip_{i,j}\),就是预处理之前的 \(p\)。这玩意写寄了调了好久。最后把那个 \(\sum\) 表示成差分就行了。

测一发样例,挂得离谱。啊,每次求 \(s\) 忘记清空了。还不对!额,右下端点搞错了应该是 \((i-1,j-1)\)。所以第二个转移应该是 \(f_{i,j,k}\leftarrow f_{i,j,k}+s_{i-1,j-1}-s_{i-pip_{i,j}-1,j-pip_{i,j}-1}\),同样注意边界,以及负数取模。时间、空间复杂度均为 \(\mathcal{O}(nmk)\)。终于过了。

$\color{orange}赛时代码$
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1005,M=205;
const ll mod=1e9+7;
ll f[N][M][M],s[N][M];
int n,m,K,pip[N][M];
string A,B;
signed main(){
	cin.tie(0);
	cout.tie(0);
	ios::sync_with_stdio(0);
	cin>>n>>m>>K>>A>>B;
	A=' '+A;
	B=' '+B;
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			bool ok=1;
			for(int k=1;k<=min(i,j);++k){
				if(A[i-k+1]^B[j-k+1]){
					pip[i][j]=k-1;
					ok=0;
					break;
				}
			}
			if(ok){
				pip[i][j]=min(i,j);
			}
		}
	}
	for(int i=0;i<=n;++i){
		f[i][0][0]=1;
	}
	/*for(register int i=1;i<=n;++i){
		for(register int j=1;j<=m;++j){
			for(register int k=1;k<=min({K,j,i});++k){
				f[i][j][k]+=f[i-1][j][k];
				f[i][j][k]%=mod;
				int y,res=0;
				for(register int p=1;p<=min(i,j)&&A[i-p+1]==B[j-p+1];++p){
					f[i][j][k]+=f[i-p][j-p][k-1];
					res+=f[i-p][j-p][k-1];
					f[i][j][k]%=mod;
					y=p;
				}
				cout<<i<<' '<<j<<' '<<k-1<<' '<<res<<'\n';
			}
		}
	}*/
	for(int k=1;k<=min({n,m,K});++k){
		memset(s,0,sizeof s);
		for(int i=k-1;i<=n;++i){
			for(int j=k-1;j<=m;++j){
				s[i][j]=f[i][j][k-1]+(i&&j?s[i-1][j-1]:0);
				s[i][j]%=mod;
				//cout<<i<<' '<<j<<' '<<k-1<<' '<<s[i][j]<<'\n';
			}
		}
		for(int i=k;i<=n;++i){
			for(int j=k;j<=m;++j){
				f[i][j][k]+=f[i-1][j][k];
				f[i][j][k]%=mod;
				f[i][j][k]+=s[i-1][j-1]-(i-pip[i][j]&&j-pip[i][j]?s[i-pip[i][j]-1][j-pip[i][j]-1]:0)+mod;
				f[i][j][k]%=mod;
			}
		}
	}
	cout<<f[n][m][K];
}

\(score=100+100+100+100=400\)。AK 了!

但是老师说本来要卡 \(\mathcal{O}(nmk)\) 的空间的,于是把 \(k\) 这一维滚掉,时间复杂度不变,空间复杂度为 \(\mathcal{O}(nm)\)。一开始数组开小 \(\text{RE}\) 了一发,随后过了。

$\color{orange}滚动数组代码$
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1005,M=205;
const ll mod=1e9+7;
ll f[N][M],s[N][M],g[N][M];
int n,m,K,pip[N][M];
string A,B;
signed main(){
	cin.tie(0);
	cout.tie(0);
	ios::sync_with_stdio(0);
	cin>>n>>m>>K>>A>>B;
	A=' '+A;
	B=' '+B;
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			bool ok=1;
			for(int k=1;k<=min(i,j);++k){
				if(A[i-k+1]^B[j-k+1]){
					pip[i][j]=k-1;
					ok=0;
					break;
				}
			}
			if(ok){
				pip[i][j]=min(i,j);
			}
		}
	}
	for(int i=0;i<=n;++i){
		f[i][0]=1;
	}
	/*for(register int i=1;i<=n;++i){
		for(register int j=1;j<=m;++j){
			for(register int k=1;k<=min({K,j,i});++k){
				f[i][j][k]+=f[i-1][j][k];
				f[i][j][k]%=mod;
				int y,res=0;
				for(register int p=1;p<=min(i,j)&&A[i-p+1]==B[j-p+1];++p){
					f[i][j][k]+=f[i-p][j-p][k-1];
					res+=f[i-p][j-p][k-1];
					f[i][j][k]%=mod;
					y=p;
				}
				cout<<i<<' '<<j<<' '<<k-1<<' '<<res<<'\n';
			}
		}
	}*/
	for(int k=1;k<=min({n,m,K});++k){
		for(int i=0;i<=n;++i){
			for(int j=0;j<=m;++j){
				g[i][j]=f[i][j];
				f[i][j]=0;
			}
		}
		memset(s,0,sizeof s);
		for(int i=k-1;i<=n;++i){
			for(int j=k-1;j<=m;++j){
				s[i][j]=g[i][j]+(i&&j?s[i-1][j-1]:0);
				s[i][j]%=mod;
				//cout<<i<<' '<<j<<' '<<k-1<<' '<<s[i][j]<<'\n';
			}
		}
		for(int i=k;i<=n;++i){
			for(int j=k;j<=m;++j){
				f[i][j]+=f[i-1][j];
				f[i][j]%=mod;
				f[i][j]+=s[i-1][j-1]-(i-pip[i][j]&&j-pip[i][j]?s[i-pip[i][j]-1][j-pip[i][j]-1]:0)+mod;
				f[i][j]%=mod;
			}
		}
	}
	cout<<f[n][m];
}

总结一下,\(\text{A}<\text{B}<\text{D}<\text{C}\)。/cf。

继续调 CF949E,还是老样子 /fn。老师让我上去讲一下 \(\text{C}\) 做法。

晚自习打了把初赛,阅读程序是啥啊。/kk。根本看不懂 /lb。不过完善程序板的要死 /cf。测出来居然有 \(79\text{pts}\)

\(7.11\text{ Day }2\)

上午讲的二分,给了一堆神仙题。先是 CF1016E。一开始题目看反了 /cf。不过做法是差不多的。一开始只会 \(\mathcal{O}(nq)\) 的暴力,然后 yqc 提了一嘴前缀和,会了 /bx。\(\text{AC}\) 了。写了题解

posted @ 2023-07-09 21:23  lzyqwq  阅读(243)  评论(1)    收藏  举报