2025牛客暑期多校训练营1

2025牛客暑期多校训练营1

根据赛时出题人数排序

G

solution

题目需要查询从给定位置开始,查询字符串和给定字符串有多少个区间是相同的。
那么单次查询复杂度好像是 \(O(n)\)

\(\because\) \(\sum |T| \le 10^6\),所以暴力查询每次的结果即可。

如果新增的字符与前面的对称字符相邻,那么可以新增相邻字符个数的答案.

code

void func(void)
{
	int n,q;
	cin >> n >> q;
	string s;	cin >> s;
	while(q --)
	{
		string st;
		int a;
		cin >> st >> a;
		a --;
		int len = 0, ans = 0;
		for(int i=0;i<st.size();++i)
		{
			if(st[i] == s[i+a])
			{
				len ++;
				ans += len;
			}
			else	len = 0;
		}
		cout << ans << '\n';
	}
}

E

solution

\(a = k, b = k+x\)
\(b^2 - a^2 = 2kx + x^2\)
\(= x(2k+x)\)

  • \(x\) 为奇数时,式子等效为 \(2k+1, k \ge 1\)
  • \(x\) 为偶数时,式子等效为 \(4k+4,k \ge 1\)

那么可以得出,除了 \(4\) 和形如 \(4k+2\) 的数,都能表示。

那么从\(4\) 开始,每四个数只有三个数可以出现

也可以通过打表得出(事实上补题也是打表求的)

code

void func(void)
{
	int a,b;
	cin >> a >> b;
	if(a < b)	swap(a,b);
	int t = a*a - b*b, ans = 1;
	if(t > 4)	ans += (t-4)/4*3 + t%4 - (t%4 > 1);
	cout << ans << '\n';
}

L

权值线段树板子题

题解说是可以用 std::map,但是作为线段树糕手,还是要上我无敌的线段树了

solution

找到第 \(\lceil \frac{n-1}{2} \rceil\) 的数即可。

因为只涉及单点修改,所以用权值线段树/树状数组都可以。

因为整理过线段树板子,就直接粘了个线段树

需要离散化

code

int n,q,L;
int a[N],b[N],t[N<<2];
PII eq[N];
vector<int> dis;

int find(int x)
{
	return (lower_bound(dis.begin(),dis.end(),x) - dis.begin());
}

void push_up(int p)
{
	t[p] = t[p<<1] + t[p<<1|1];
}

void build_tree(int be=1,int ed=L,int p=1)
{
	t[p] = 0;
	if(be == ed) return;
	int mid = (be + ed) >> 1;
	build_tree(be,mid,p<<1), build_tree(mid+1,ed,p<<1|1);
}

void put(int k,int z,int be=1,int ed=L,int p=1)
{
	if(be == ed)
	{
		t[p] += z;
		return;
	}
	int mid = (be + ed) >> 1;
	if(k <= mid)	put(k,z,be,mid,p<<1);
	else			put(k,z,mid+1,ed,p<<1|1);
	push_up(p);
}

int query_k(int k,int be=1,int ed=L,int p=1)
{
	if(be == ed)	return be;
	int mid = (be+ed) >> 1, lsum = t[p<<1];
	if(lsum >= k)	return query_k(k,be,mid,p<<1);
	else 			return query_k(k-lsum,mid+1,ed,p<<1|1);
}

int query_cnt(int l,int r,int be=1,int ed=L,int p=1)
{
	if(l <= be && ed <= r)	return t[p];
	int mid = (be+ed) >> 1,cnt = 0;
	if(l <= mid)	cnt += query_cnt(l,r,be,mid,p<<1);
	if(mid+1 <= r)	cnt += query_cnt(l,r,mid+1,ed,p<<1|1);
	return cnt;
}

void func(void)
{
	cin >> n >> q;
	dis.clear();
	dis.push_back(-1);
	for(int i=1;i<=n;++i)
	{
		cin >> a[i];
		b[i] = a[i];
		dis.push_back(b[i]);
	}
	for(int i=1;i<=q;++i)
	{
		auto &[x,y] = eq[i];
		cin >> x >> y;
		b[x] += y;
		dis.push_back(b[x]);
	}
	sort(dis.begin(),dis.end());
	dis.erase(unique(dis.begin(),dis.end()),dis.end());
	L = dis.size()-1;
	build_tree();
	for(int i=1;i<=n;++i)	put(find(a[i]),1);
	for(int i=1;i<=q;++i)
	{
		auto &[x,y] = eq[i];
		put(find(a[x]),-1);
		a[x] += y;
		put(find(a[x]),1);
		int mk = (n+1)/2+1;
		cout << n - query_cnt(query_k(mk),L) << '\n';	
	}
}

K

就是 dfs 然后略微优化,赛时居然以为很难

solution

游客走得足够长,当且仅当游客走到了一个环中。

若是把无向边理解为两条单向边,那么最多有 \(3n\) 条边

每条边至多被一个环使用,也至少被使用一次。

那么我们只需要把各个环处理出来:

  • 如果某个点第一次踏入该环,dfs找环大小,复杂度 \(O(n)\)
  • 如果第 \(\ge 2\) 次进入该环,只需要 \(O(1)\)

    第一步把环的信息记录到每条边上,一条边被二次访问,就直接输出信息。

因为每条边只访问一次,复杂度\(O(n)\),因为用 std::map 维护的信息,所以复杂度变为 \(O(n \log n)\)

code

int n;
vector<int> v[N];
map<PII,bool> vis;
map<PII,int> res;
vector<PII> b;

void dfs(int p,int lp)
{
	if(vis[{lp,p}])	return;
	b.push_back({lp,p});
	vis[{lp,p}] = true;
	int d = v[p].size();
	for(int i=0;i<d;++i)
	{
		if(v[p][i] == lp)	dfs(v[p][(i+1)%d],p);
	}
}

void func(void)
{
	cin >> n;
	for(int i=1;i<=n;++i)
	{
		int d;	cin >> d;
		while(d --)
		{
			int x;	cin >> x;
			v[i].push_back(x);
		}
	}
	for(int i=1;i<=n;++i)
	{
		if(!res.count({i,v[i][0]}))	
		{
			dfs(v[i][0],i);
			set<PII> st;
			for(auto &[x,y] : b)	st.insert({min(x,y),max(x,y)});
			for(auto &[x,y] : b)	res[{x,y}] = st.size();
			b.clear();
		}
		cout << res[{i,v[i][0]}] << '\n';
	}
}

I

区间dp
赛时根本没看这题,虽然以当时的dp能力也开不了就是了。

solution

如果用 \(O(n^4)\) 的区间dp写,是很简单的。
\(dp_{l,j,k}\) 表示合并 \(l,j\)\(j+1,k\) 的铁棒的最小代价,然后 \(dp_{l,j,k}\) 从所有 \([l,j]\) 的所有可行区间和 \([j+1,k]\) 的所有可行区间转移即可。

但是 \(n \le 420\),只能勉强接受 \(O(n^3 \log n)\)

那么我们考虑怎么把查找可行区间进行优化。

合并区间本质是一棵二叉树,根节点的不平衡度一定大于两个子节点的。
那么一直归并下去,根节点的不平衡度一定大于所有子节点,两个子节点随便排下先后顺序就可以保证操作时 \(b\) 非递减。

那么我们只需要在 \(1 \sim \log n\) 的时间内找到可以满足条件的最小值。

这里我们使用二分:

对于状态 \(l,k,r\)
\([l,k]\)\([k+1,r]\) 内只要 不平衡度 \(\le b_{l,k,r}\) 就可以转移。
那么我们队 \([l,k]\)\([k+1,r]\) 的dp结果根据 \(b\) 排序,然后二分找到分界点 \(p\)
\(p\) 左边的所有区间可以取用。

然后我们来考虑怎么取到最小值:
\(\because\) 区间从起始点开始,那么我们可以使用前缀最小值维护,这样单个节点的转移就变为 \(\log n \times 1\)

因为答案 \(\ge 10^9\) 需要开 long long,而时间空间都很极限,所以我用 vector 动态开点加上把前缀数组和值数组开在一起进行了一些优化,最后 \(3500ms\) 滑过

注意各种边界

题解显示还有 \(O(n^3)\) 的解法,但是我燃尽了,加上我直接 #define int long long,多开一维空间应该更难过一些。毕竟我空间也花了 \(4/5\)

code

#define int long long
const ll inf = 1e18;
const int N = 425;

int a[N],s[N];
vector<vector<vector<PII>>> d(N,vector<vector<PII>>(N));
vector<vector<vector<int>>> dp(N,vector<vector<int>>(N));

int log_2(int x)
{
	int res = 0;
	while((1ll<<res) < x)	res ++;
	return res;
}

void func(void)
{
	int n;	cin >> n;
	for(int i=1;i<=n;++i)	cin >> a[i];
	for(int i=1;i<=n;++i)	s[i] = s[i-1] + a[i];
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=n;++j)
		{
			dp[i][j].clear();
			d[i][j].clear();
		}
	}
	for(int i=1;i<=n;++i)	d[i][i].push_back({0,0});
	for(int i=2;i<=n;++i)
	{
		for(int l=1;l+i-1<=n;++l)
		{
			int r = l+i-1;
			dp[l][r].assign(r-l,inf);
			for(int k=0;k<i;++k)
			{
				int x = l+k;
				int l1 = s[x]-s[l-1], l2 = s[r]-s[x];
				int b = llabs(l1-l2);
				auto it1 = upper_bound(d[l][x].begin(),d[l][x].end(),make_pair(b,inf));
				auto it2 = upper_bound(d[x+1][r].begin(),d[x+1][r].end(),make_pair(b,inf));
				if(it1 == d[l][x].begin() || it2 == d[x+1][r].begin())	continue;
				int p1 = it1-d[l][x].begin()-1, p2 = it2-d[x+1][r].begin()-1;
				int cost1 = (d[l][x][p1].X > b ? inf : d[l][x][p1].Y);
				int cost2 = (d[x+1][r][p2].X > b ? inf : d[x+1][r][p2].Y);
				dp[l][r][k] = min(dp[l][r][k],cost1+cost2+min(l1,l2)*log_2(l1+l2));
				if(dp[l][r][k] < inf)	d[l][r].push_back({b,dp[l][r][k]});
			}
			auto &res = d[l][r];
			if(res.size())
			{
				sort(res.begin(),res.end());
				for(int k=1;k<res.size();++k)	res[k].Y = min(res[k].Y,res[k-1].Y);
			}
		}
	}
	for(int i=0;i<n-1;++i)
	{
		cout << (dp[1][n][i] < inf ? dp[1][n][i] : -1) << ' ';
	}
	cout << '\n';
}

other

先把各场通过 \(\ge 100\) 的补完。

posted @ 2025-08-20 16:50  zerocloud01  阅读(9)  评论(0)    收藏  举报