Codeforces Round 1077 (Div. 1)
我是路边,不知为何粉兔这场爆成和我坐一桌了
Codeforces Round 1077 (Div. 1)
A,B
A 二分。
B 调整+枚举。
C
dsu on tree 都不会了😅😅😅
贪心建一个最短路树然后考虑点对 \(x,y\) 的贡献,式子很容易列,额外的是对于相同深度的点贡献要计算两次。
额外计算相同深度的点的贡献可以按照不同的深度个数 dsu on tree。
树上计算相同深度的所有点对贡献的这种问题,按深度 dsu on tree 复杂度是对的。证明不会😭
但是 y1s1,这个 trick 很容易识别到吧。那我在干什么呢?🙃
复杂度 \(O(nlog^2n)\)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pb push_back
#define pll pair<ll,ll>
#define fi first
#define se second
#define lowbit(i) (i&(-i))
const int N = 5e5+10;
ll n,m,ans,to[N],dep[N],siz[N],p[N];
vector<int> e[N];
void dfs(int u){
siz[u] = 1;
for(int v:e[u]){
dep[v] = dep[u]+1;
dfs(v);
siz[u] += siz[v];
}
}
void calc(int u){
ll pre = 1;
for(int v:e[u]){
calc(v);
ans -= pre*siz[v]*dep[u];
pre += siz[v];
}
}
map<ll,ll> ex_calc(int u){
map<ll,ll> cnt;
cnt[dep[u]] = 1;
for(int v:e[u]){
map<ll,ll> tmp = ex_calc(v);
if(tmp.size()>cnt.size()) swap(tmp,cnt);
for(auto x:tmp){
ans += (x.fi-dep[u])*x.se*cnt[x.fi];
cnt[x.fi] += x.se;
}
}
return cnt;
}
void solve(){
cin >> n >> m;
for(int i=1;i<=n;i++) e[i].clear();
for(int i=1;i<n;i++) to[i] = i+1;
for(int i=1;i<=m;i++){
ll u,v;
cin >> u >> v;
to[u] = max(to[u],v);
}
for(int i=1;i<n;i++) e[to[i]].pb(i);
dep[n] = 0;
dfs(n);
for(int i=1;i<=n;i++) p[i] = i;
sort(p+1,p+1+n,[&](int x,int y){
return dep[x]>dep[y];
});
ans = 0;
for(int i=1;i<=n;i++){
ans += dep[p[i]]*(i-1);
}
calc(n);
ex_calc(n);
cout << ans << '\n';
}
int main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
int T = 1;
cin >> T;
while(T--) solve();
return 0;
}
D
我觉得挺好的题。
不会,看 jiangly的视频 学的。
jiangly 通过打表就把结论看出来了,不愧是蒋老师,吓哭了。
想了想这个结论如果要推该怎么推,我感觉是这样的:
我们按照 \(1\) 划分若干段,以 \(0\) 开头的话,序列大概是长这样的 \(x,2x,3x...,ax\),然后遇到了一个 \(1\),后续就会变成 \(y-ax,y-(a-1)x,....\)
最终答案可以表示成 \(ax+by\) 的形式,考虑对于固定的序列 \(s\),尝试找到系数 \(a,b\) 一种容易处理的计算方式。
观察到如果遇到了一个 \(1\) ,\(y\) 就会一直产生贡献,直到遇到下一个 \(1\) 停止。这样的规律与异或前缀和的计算是类似的。
相当于对于 \(s\),当前位置的异或前缀和为 \(1\) 时,\(y\) 有贡献;否则就没有贡献。所以 \(y\) 的贡献系数就是 \(s\) 做一遍前缀和后 \(1\) 的个数,记为 \(s_y\)
然后分析 \(x\) 的贡献系数,手玩一下这种情况 \(0...010...010...0\),会发现关于 \(x\) 的贡献序列是这个样子的:
\(x,2x,3x,...,bx...,ax,-ax,-(a-1)x,...,-bx,bx,(b+1)x,(b+2)x,....\)
可以看到,每次遇到一个 \(1\),系数就取反一次。于是放在前缀和上面分析。
对应的前缀和情况就是 \(0...01...10...0\),很容易观察到 \(1\) 的部分的系数会将前面 \(0\) 部分的系数的一段后缀抵消掉,而在后面的 \(0\) 部分又会正好从抵消掉的值那里开始重新增加。
假设我们一开始是全 \(0\),贡献就是 \(\frac{n(n+1)}{2} x\),而前缀和中每出现一次 \(1\),就会让这个 \(n\) 减去 \(2\),因为它占一个位置的同时还会抵消掉前面的一个位置。
所以 \(x\) 的贡献系数 \(s_x = \frac{(n-2s_y)(n-2s_y+1)}{2}\)
由此可以看出,贡献仅由前缀和中 \(1\) 的个数决定,所以至多 \(n+1\) 种。
因此我们只需要找出所有可能的 \(s_y\) 即可。
本题 \(10^5~~4s\),可以用 bitset 维护当前前缀可能的 \(1\) 的个数,顺次转移即可。
复杂度 \(O(\frac{n^2}{w})\)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pb push_back
const ll mod = 998244353;
const int N = 1e5+10;
ll n,x,y,ans;
char s[N];
bitset<N> state[2],tmp[2];
void solve(){
cin >> n >> x >> y;
for(int i=1;i<=n;i++) cin >> s[i];
ans = 0;
state[0].reset(),state[0].set(0);
state[1].reset(),tmp[0].reset(),tmp[1].reset();
for(int i=1;i<=n;i++){
if(s[i] != '?'){
if(s[i] == '1'){
tmp[0] = state[0];
state[0] = state[1];
state[1] = (tmp[0]<<1);
}else{
state[1] = (state[1]<<1);
}
}else{
tmp[0] = state[0],tmp[1] = state[1];
state[0] = tmp[0]|tmp[1];
state[1] = (tmp[0]<<1)|(tmp[1]<<1);
}
}
map<ll,int> vis;
for(int i=0;i<=n;i++) {
if(state[0][i] == 0 && state[1][i] == 0) continue;
ll val = y*i+x*((n-2*i)*(n-2*i+1)/2);
if(vis[val]) continue;
vis[val] = 1,val %= mod;
ans += val;
if(ans >= mod) ans -= mod;
}
cout << ans << '\n';
}
int main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
int T;cin >> T;
while(T--) solve();
return 0;
}

浙公网安备 33010602011771号