11.3解题报告
T1 生成树
给一个有 \(n\) 个点(从 \(0~n-1\) 编号), \(m\) 条边的无向图,每个点有权值 \(v\),求一个生成树,令代价 \(cost\) =每个点的深度 \(h\)( 根结点深度为 \(1\) )*权值 \(v\) ,希望代价最小。求这个最小代价 \(cost\)
思路:
直接枚举点,BFS 就行。
int n, m, u, v;
struct node {
int t, n;
}e[505];
int head[105], head_size;
void ADD(int f, int t) {
e[++head_size]=(node){t, head[f]};
head[f]=head_size;
}
int val[505], ans=2e9;
bool pd[505];
struct nod {int v, dep;};
void solve(int S) {
int js=1;
int sum=0;
memset(pd, 0, sizeof(pd));
queue<nod>q;
pd[S]=1; q.push((nod){S, 1});
while(!q.empty()) {
nod op=q.front(); q.pop();
sum+=op.dep*val[op.v];
for(int i=head[op.v]; i; i=e[i].n) {
if(pd[e[i].t]) continue ;
pd[e[i].t]=1; ++js; q.push((nod){e[i].t, op.dep+1});
}
}
if(js!=n) {cout<<-1; exit(0);}
ans=min(ans, sum);
}
int main(){
n=read(); m=read();
for(int i=1; i<=n; ++i) val[i]=read();
for(int i=1; i<=m; ++i) {
u=read()+1; v=read()+1;
ADD(u, v); ADD(v, u);
}
for(int i=1; i<=n; ++i) solve(i);
cout<<ans;
}
T2 轰炸城市
\(B\) 国要轰炸 \(A\) 国的城市,\(A\) 国有 \(N\) 个城市,每个城市位置可以看作在二维坐标系中的一个点,用坐标 \((x,y)\) 表示(有可能两个城市在同一个点中),\(-10^9 \leq x,y \leq 10^9\) , \(B\) 国要投放 \(M\) 枚炸弹,每个炸弹炸毁的区域都是两条平行于同一条坐标轴的直线的内部区域。对于每次投放炸弹,\(B\) 国总统想知道炸毁了多少城市(已经炸毁的城市如果再被炸到就不再计算入内)。
\(B\) 国要轰炸 \(A\) 国的城市,\(A\) 国有 \(N\) 个城市,每个城市位置可以看作在二维坐标系中的一个点,用坐标 \((x,y)\) 表示(有可能两个城市在同一个点中),\(-10^9 \leq x,y \leq 10^9\) , \(B\) 国要投放 \(M\) 枚炸弹,每个炸弹炸毁的区域都是两条平行于同一条坐标轴的直线的内部区域。对于每次投放炸弹,\(B\) 国总统想知道炸毁了多少城市(已经炸毁的城市如果再被炸到就不再计算入内)。
思路:
两个 \(multiset\) 分别维护按照 \(x\) 排序,和按照 \(y\) 排序。
为了避免重复,删除一个点时,给他的编号打上标记,后面有标记的就当作不存在。
struct node {
int x, y, i;
bool operator < (const node &a) const {
return a.x>x;
}
};
struct nod {
int x, y, i;
bool operator < (const nod &a) const {
return a.y>y;
}
};
multiset<node>sx;
multiset<nod>sy;
int op, n1, n2, n, m;
bool pd[100005];
int main(){
n=read(); m=read();
for(int i=1; i<=n; ++i) {
int x=read(), y=read();
sx.insert((node){x, y, i});
sy.insert((nod){x, y, i});
}
multiset<nod>::iterator it;
while(m--) {
op=read(); n1=read(); n2=read();
int js=0;
if(n1>n2) swap(n1, n2);
if(op==0) {
while(1) {
multiset<node>::iterator it;
it=sx.lower_bound((node){n1, 0, 0});
if(it==sx.end()) break;
if((*it).x>n2) break;
if(pd[(*it).i]==0) {
pd[(*it).i]=1;
++js;
}
sx.erase(it);
}
}
else {
while(1) {
multiset<nod>::iterator it;
it=sy.lower_bound((nod){0, n1, 0});
if(it==sy.end()) break;
if((*it).y>n2) break;
if(pd[(*it).i]==0) {
pd[(*it).i]=1;
++js;
}
sy.erase(it);
}
}
cout<<js<<endl;
}
}
T4 最长上升子树链。
给一棵 \(n\) 个结点的树,编号 \(1~n\) ,\(1\) 为根,树上每个点有权值 \(v_i\),求一条最长上升的子树链。
上升子树链的严格定义就是:求一个由点编号构成的序列\(a[1…m]\),要求满足:
- 存在一个 \(k\),\(1 \le k \le m\),满足在 \(2 \le i \le k\) 中,\(a[i-1]\)在以 \(a[i]\) 为根的子树中,在 \(k+1 \le i \le m-1\) 中,\(a[i+1]\) 在以 \(a[i]\) 为根的子树中。
- \(2 \le i \le m\) 中,\(v[a[i]] > v[a[i - 1]]\)。
思路:
一顿思想化简后,可知。
每个点只需要维护子树信息及子树对这个点产生的贡献即可。
那么想象 \(nlogn\) 的最长上升子序列是怎么做的。
维护一个最长递增和一个最长递减,回溯的时候线段树合并。
struct node {
int t, n;
}e[100005];
int head[100005], head_size;
void ADD(int f, int t) {
e[++head_size]=(node){t, head[f]};
head[f]=head_size;
}
int ls[100005], top, tt1, tt2;
struct tree1 {
int maxx=-1, minn=2e9;
int l, r, bj=1;
}t1[100005*51], t2[100005*51];
int rt1[100005], rt2[100005];
int n, v[100005], u;
int ans=1;
void push_up1(int g) {
t1[g].maxx=max(t1[t1[g].l].maxx, t1[t1[g].r].maxx);
t1[g].minn=min(t1[t1[g].l].minn, t1[t1[g].r].minn);
t1[g].bj=(t1[t1[g].l].bj|t1[t1[g].r].bj);
}
void push_up2(int g) {
t2[g].maxx=max(t2[t2[g].l].maxx, t2[t2[g].r].maxx);
t2[g].minn=min(t2[t2[g].l].minn, t2[t2[g].r].minn);
t2[g].bj=(t2[t2[g].l].bj|t2[t2[g].r].bj);
}
void Merge1(int &x, int &y, int l, int r) {
if(!x) return ;
if(!y) {y=x; return ;}
if(l==r) {t1[y].minn=t1[y].maxx=min(t1[y].maxx, t1[x].maxx); t1[y].bj=0; return ;}
int mid=l+r>>1;
Merge1(t1[x].l, t1[y].l, l, mid);
Merge1(t1[x].r, t1[y].r, mid+1, r);
push_up1(y);
}
void Merge2(int &x, int &y, int l, int r) {
if(!x) return ;
if(!y) {y=x; return ;}
if(l==r) {t2[y].maxx=t2[y].minn=max(t2[y].minn, t2[x].minn); t2[y].bj=0; return ;}
int mid=l+r>>1;
Merge2(t2[x].l, t2[y].l, l, mid);
Merge2(t2[x].r, t2[y].r, mid+1, r);
push_up2(y);
}
void Change1(int &g, int l, int r, int val) {
if(!g) g=++tt1;
if(l==r) {t1[g].minn=t1[g].maxx=val; t1[g].bj=0; return ;}
int mid=l+r>>1;
if(t1[t1[g].l].bj) Change1(t1[g].l, l, mid, val);
else {
if(t1[t1[g].l].maxx<val) Change1(t1[g].r, mid+1, r, val);
else Change1(t1[g].l, l, mid, val);
}
push_up1(g);
}
void Change2(int &g, int l, int r, int val) {
if(!g) g=++tt2;
if(l==r) {t2[g].maxx=t2[g].minn=val; t2[g].bj=0; return ;}
int mid=l+r>>1;
if(t2[t2[g].l].bj) Change2(t2[g].l, l, mid, val);
else {
if(t2[t2[g].l].minn>val) Change2(t2[g].r, mid+1, r, val);
else Change2(t2[g].l, l, mid, val);
}
push_up2(g);
}
int SUM1(int g, int l, int r, int val) {
if(!g) return 0;
if(l==r) return l;
int mid=l+r>>1;
if(t1[t1[g].r].minn<=val) return SUM1(t1[g].r, mid+1, r, val);
else return SUM1(t1[g].l, l, mid, val);
}
int SUM2(int g, int l, int r, int val) {
if(!g) return 0;
if(l==r) return l;
int mid=l+r>>1;
if(t2[t2[g].r].maxx>=val) return SUM2(t2[g].r, mid+1, r, val);
else return SUM2(t2[g].l, l, mid, val);
}
void dfs(int x, int fa) {
for(int i=head[x]; i; i=e[i].n) {
if(e[i].t==fa) continue ;
dfs(e[i].t, x);
Merge1(rt1[e[i].t], rt1[x], 1, n);
Merge2(rt2[e[i].t], rt2[x], 1, n);
}
Change1(rt1[x], 1, n, v[x]);
Change2(rt2[x], 1, n, v[x]);
int a=SUM1(rt1[x], 1, n, v[x]), b=SUM2(rt2[x], 1, n, v[x]+1);
ans=max(ans, a+b);
}
int main(){
n=read();
for(int i=1; i<=n; ++i) {
v[i]=read(); u=read();
if(i!=1) ADD(u, i);
ls[i]=v[i];
}
ls[0]=-1; sort(ls+1, ls+n+1);
for(int i=1; i<=n; ++i) if(ls[i]!=ls[i-1]) ls[++top]=ls[i];
for(int i=1; i<=n; ++i) v[i]=lower_bound(ls+1, ls+top+1, v[i])-ls;
dfs(1, 1);
cout<<ans;
}