A. 旅游
设连通块的大小为 $sz_i$,要求计算 $\sum sz_i(sz_i-1)$。
离线,排序,并查集维护。
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 1e5 + 5;
struct Edge{
int u,v,w;
inline bool operator < (const Edge &t) const {
return w < t.w;
}
}e[MAXN];
int n,m,q;
int f[MAXN],sz[MAXN];
inline int find(int x){
return x == f[x] ? x : f[x] = find(f[x]);
}
LL now = 0;
inline LL C(int n){
return 1ll*n*(n-1);
}
inline void merge(int x,int y){
x = find(x);y = find(y);
if(x == y) return;
now -= C(sz[x])+C(sz[y]);
sz[x] += sz[y];f[y] = x;
now += C(sz[x]);
}
LL ans[MAXN];
P qry[MAXN];
inline void Solve(){
scanf("%d%d%d",&n,&m,&q);
now = 0;FOR(i,1,n) f[i] = i,sz[i] = 1;
FOR(i,1,m) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
std::sort(e+1,e+m+1);
FOR(i,1,q){
scanf("%d",&qry[i].fi);qry[i].se = i;
}
std::sort(qry+1,qry+q+1);
int tp = 1;
FOR(i,1,q){
while(tp <= m && e[tp].w <= qry[i].fi) merge(e[tp].u,e[tp].v),++tp;
ans[qry[i].se] = now;
}
FOR(i,1,q) printf("%lld\n",ans[i]);
}
int main(){
int T;scanf("%d",&T);
while(T--) Solve();
return 0;
}
B. Tom
显然一定是一个子树放早餐,另一个子树放晚餐。
找到这样的子树后分别从下往上放即可。
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 1e5 + 5;
int n,a,b;
std::vector<int> G[MAXN];
int sz[MAXN],fa[MAXN];
int ps = 0;
inline void dfs(int v,int fa=0){
sz[v] = 1;::fa[v] = fa;
for(auto x:G[v]){
if(x == fa) continue;
dfs(x,v);sz[v] += sz[x];
}
if(sz[v] == a) ps = v;
else if(sz[v] == b) ps = -v;
}
std::vector<int> node[MAXN];
inline void dfs2(int v,int d=1,int fa=0){
node[d].pb(v);
for(auto x:G[v]){
if(x == fa) continue;
dfs2(x,d+1,v);
}
}
int ans[MAXN];
int main(){
scanf("%d%d%d",&n,&a,&b);
FOR(i,2,n){
int u,v;scanf("%d%d",&u,&v);
G[u].pb(v);G[v].pb(u);
}
dfs(1);
dfs2(std::abs(ps),1,fa[std::abs(ps)]);
int m = 1;while(!node[m+1].empty()) ++m;
int t = 1;
ROF(i,m,1){
for(auto x:node[i]) ans[x] = (ps>0?1:-1)*t,++t;
node[i].clear();
}
dfs2(fa[std::abs(ps)],1,std::abs(ps));
t = m = 1;while(!node[m+1].empty()) ++m;
ROF(i,m,1){
for(auto x:node[i]) ans[x] = (ps>0?-1:1)*t,++t;
node[i].clear();
}
FOR(i,1,n) printf("%d\n",ans[i]);
return 0;
}
C. Jerry
只有减法后面会加括号,并且可以发现加括号后可以通过里面再加一层让括号内除了一开始一段的 +
之外的数字贡献都为正。维护前缀最小值 直接 dp 即可。
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 2e5 + 5;
int n;
char opt[MAXN];
int a[MAXN];
// 括号只可能嵌套一层
// f[i] = f[j-1] -sm[i]+sm[j]-a[j] (opt[j]=='-')
LL f[MAXN],sm[MAXN],pre[MAXN];
inline void Solve(){
scanf("%d",&n);
scanf("%d",a+1);
FOR(i,2,n){
char str[23];
scanf("%s",str);
opt[i] = str[0];
scanf("%d",a+i);
}
sm[1] = a[1];FOR(i,2,n) sm[i] = sm[i-1]+a[i];
pre[n] = a[n];
ROF(i,n-1,1){
if(opt[i+1] == '-') pre[i] = a[i];
else pre[i] = pre[i+1]+a[i];
}
// f[i] = f[j-1]-2*pre[j]+sm[i]-sm[j-1] (opt[j]=='-')
LL mx = -1e18;f[1] = a[1];
FOR(i,2,n){
f[i] = f[i-1]+(opt[i]=='-'?-1:1)*a[i];
if(opt[i] == '-') mx = std::max(mx,f[i-1]-2*pre[i]-sm[i-1]);
f[i] = std::max(f[i],mx+sm[i]);
}
printf("%lld\n",f[n]);
}
// -(a+b-(c+d)-(e+f))
// -a-b+c+d+e+f
//前面最连续的一段+不能取
int main(){
freopen("jerry.in","r",stdin);
freopen("jerry.out","w",stdout);
int T;scanf("%d",&T);
while(T--) Solve();
return 0;
}
D. Speike
首先发现边界是可以走的并且矩形之间无交,所以我们可以推出只有边界上四个点有用。
首先贪心地想,路径上一定不会往左走,所以我们只需要关注 $y$ 坐标上的走动即可。
考虑走最短路的过程:$x$ 轴如果和矩阵无交,那么就可以直接走过去,否则考虑最后一次走到的一定是最后一个相交的矩形的左边界,然后绕过去。
这种题要考虑反过来,从最后一个去观察,发现从终点开始每次都要从横坐标上距离当前点最近的,覆盖了当前 $y$ 坐标的矩形转移过来。所以这个形式可以倒过来 dp:只从前面最后一个覆盖 $y$ 坐标的地方转移即可(可以用线段树或者 set 维护)
这种观察走路性质然后倒过来挺妙的。。不太会。
#include <bits/stdc++.h>
#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const int MAXN = 1e6 + 5;
int n,xt;
struct Node{
int x1,y1,x2,y2;
Node(int x1=0,int y1=0,int x2=0,int y2=0) : x1(x1),y1(y1),x2(x2),y2(y2) {}
inline bool operator < (const Node &t) const {
return x1 == t.x1 ? x2 < t.x2 : x1 < t.x1;
}
}a[MAXN];
int mx[MAXN<<2],tag[MAXN<<2];
#define lc ((x)<<1)
#define rc ((x)<<1|1)
inline void cover(int x,int d){
mx[x] = d;tag[x] = d;
}
inline void pushdown(int x){
if(tag[x] != -1){
cover(lc,tag[x]);
cover(rc,tag[x]);
tag[x] = -1;
}
}
inline void build(int x,int l,int r){
tag[x] = -1;mx[x] = 1;
if(l == r) return;
int mid = (l + r) >> 1;
build(lc,l,mid);build(rc,mid+1,r);
}
inline void modify(int x,int l,int r,int L,int R,int d){
if(l == L && r == R) return cover(x,d);
int mid = (l + r) >> 1;pushdown(x);
if(R <= mid) modify(lc,l,mid,L,R,d);
else if(L > mid) modify(rc,mid+1,r,L,R,d);
else modify(lc,l,mid,L,mid,d),modify(rc,mid+1,r,mid+1,R,d);
mx[x] = std::max(mx[lc],mx[rc]);
}
inline int query(int x,int l,int r,int p){
if(l == r) return mx[x];
int mid = (l + r) >> 1;pushdown(x);
if(p <= mid) return query(lc,l,mid,p);
else return query(rc,mid+1,r,p);
}
std::vector<int> S;
LL f[MAXN][2];
int main(){
#ifndef RainAir
freopen("speike.in","r",stdin);
freopen("speike.out","w",stdout);
#endif
scanf("%d%d",&n,&xt);S.pb(0);
FOR(i,1,n){
scanf("%d%d%d%d",&a[i].x1,&a[i].y1,&a[i].x2,&a[i].y2);
if(a[i].x1 > a[i].x2) std::swap(a[i].x1,a[i].x2);
if(a[i].y1 > a[i].y2) std::swap(a[i].y1,a[i].y2);
S.pb(a[i].y1);S.pb(a[i].y2);
}
a[++n] = Node(0,0,0,0);a[++n] = Node(xt,0,xt,0);
std::sort(a+1,a+n+1);
std::sort(all(S));
FOR(i,1,n) a[i].y1 = std::lower_bound(all(S),a[i].y1)-S.begin()+1,
a[i].y2 = std::lower_bound(all(S),a[i].y2)-S.begin()+1;
int M = S.size();
build(1,1,M);
assert(a[1].x1 == 0 && a[1].x2 == 0);
FOR(i,2,n){
int p = query(1,1,M,a[i].y1);
f[i][0] = std::min(f[p][0]+std::abs(S[a[i].y1-1]-S[a[p].y1-1]),f[p][1]+std::abs(S[a[i].y1-1]-S[a[p].y2-1]));
p = query(1,1,M,a[i].y2);
f[i][1] = std::min(f[p][0]+std::abs(S[a[i].y2-1]-S[a[p].y1-1]),f[p][1]+std::abs(S[a[i].y2-1]-S[a[p].y2-1]));
modify(1,1,M,a[i].y1,a[i].y2,i);
}
printf("%lld\n",f[n][0]+xt);
return 0;
}