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\) 的补完。

浙公网安备 33010602011771号