总结
这把应该是多校以来打得最差的一把了,开局我和队友1签了1006,又把1012讨论出来之后,就跟队友2看计算几何去了,队友2看了一会提出一个“看上去正确但贼复杂的做法”,然后我就听信了他的谗言开写,写了一个多小时之后发现写挂了,就开始了疯狂debug,最后写好之后发现方法假了,但队友2一直坚持自己是对的(他不知道计算几何做这么多操作会失精度),于是我就陪他一直在这道题上磨到了最后,也没有看其他题,队友1期间也一直在自闭,总的来说,这场的策略问题很大,不应该因为队友的一个思路就一直死做一个题不看其他,也不应该在签完到之后就跑去死磕不熟练的计算几何,一场打完大家心态都崩了。
题解
1006 - Maex
题意:
给定一棵由从 1 1 1 到 n n n 编号的 n n n 个顶点组成的有根树,根为顶点 1 1 1 。顶点 i i i 有一个自然数权重 a i a_i ai,并且没有两个不同的顶点具有相同的权重(这个权重由我们自己分配)。定义一个点的 b u = M E X { x ∣ ∃ v ∈ s u b t r e e ( u ) , x = a v } b_u=MEX\left \{ x \space | \space \exists v \in subtree(u),x=a_v \right \} bu=MEX{x ∣ ∃v∈subtree(u),x=av} ,即一个点的 b b b 值等于其子树(包括它自己)所有权重集合的 M E X MEX MEX 值,问 ∑ i = 1 n b i \sum_{i=1}^{n} b_i ∑i=1nbi 的最大值是多少。
做法:
由 M E X MEX MEX 的性质和观察我们可以知道,一个节点的 b i b_i bi 值为其子树中所有节点的个数, ∑ i = 1 n b i \sum_{i=1}^{n} b_i ∑i=1nbi 的值即为一条链中所有节点的 b i b_i bi 值之和
,因此我们可以采用树形 d p dp dp 的方法, d p [ u ] dp[u] dp[u] 表示子树 s u b t r e e ( u ) subtree(u) subtree(u) 中 s u m ( b i ) sum(b_i) sum(bi) 能达到的最大值, s o n son son 数组记录 u u u 节点子树节点的个数,那么就有 d p dp dp 方程:
d p [ u ] = s o n [ u ] + m a x ( d p [ v ] ) ( v ∈ s u b t r e e ( u ) ) dp[u]=son[u]+max(dp[v])(v\in subtree(u)) dp[u]=son[u]+max(dp[v])(v∈subtree(u))
代码:
/*
author:wuzx
*/
#include<bits/stdc++.h>
#define ll long long
#define int long long
#define endl "\n"
#define P pair<int,int>
#define f first
#define s second
using namespace std;
const int inf = 0x3f3f3f3f;
int t;
int n,m,k;
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>t;
while(t--)
{
cin>>n;
vector<vector<int>> g(n);
for(int i=1;i<n;i++)
{
int u,v;
cin>>u>>v;
u--;v--;
g[u].push_back(v);
g[v].push_back(u);
}
vector<int> dp(n,0);
function<int(int,int)> dfs = [&](int now,int fr)
{
int son=1;
int ma=0;
for(int x:g[now])
{
if(x==fr)
continue;
son+=dfs(x,now);
ma=max(ma,dp[x]);
}
dp[now]=son+ma;
return son;
};
dfs(0,-1);
cout<<dp[0]<<endl;
}
return 0;
}
1009 - Map
题意:
给你一个长方形以及这个长方形按比例 k ( 0 < k < 1 ) k(0<k<1) k(0<k<1) 缩小后的小长方形,将两长方形的 A − a , B − b A-a\space ,B-b A−a ,B−b 相连形成的直线相交于点 P P P ,再将小长方形围绕点 P P P 旋转任意角度,现给出原长方形的四个顶点 A B C D A\space B \space C \space D A B C D 以及旋转后小长方形的四个顶点 a b c d a\space b \space c \space d a b c d ,求点 P P P 的坐标。
做法:
我们假设点 P ,由于在长方形中有 A B ⊥ A D AB \perp AD AB⊥AD , 因此可设
O P ⃗ = O A ⃗ + λ A B ⃗ + μ A D ⃗ \vec{OP}=\vec{OA}+\lambda \vec{AB}+ \mu \vec{AD} OP=OA+λAB+μAD
其中 λ , μ \lambda,\mu λ,μ 为待定参数,又因为点 P P P 在两个长方形中的对应关系相同,因此又有:
O P ⃗ = O a ⃗ + λ a b ⃗ + μ a d ⃗ \vec{OP}=\vec{Oa}+\lambda \vec{ab}+ \mu \vec{ad} OP=Oa+λab+μad
联立上式可得:
λ ( A B ⃗ − a b ⃗ ) + μ ( A D ⃗ − a d ⃗ ) = O a ⃗ − O A ⃗ \lambda(\vec{AB}-\vec{ab})+\mu(\vec{AD}-\vec{ad})=\vec{Oa}-\vec{OA} λ(AB−ab)+μ(AD−ad)=Oa−OA
解出 λ , μ \lambda,\mu λ,μ 后带入一式可得 P P P 的坐标
代码:
/*
author:wuzx
*/
#include<bits/stdc++.h>
#define ll long long
#define int long long
#define endl "\n"
#define P pair<int,int>
#define f first
#define s second
using namespace std;
const int inf = 0x3f3f3f3f;
int t;
int n,m,k;
struct point{
double x,y;
};
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>t;
int a1,a2,b1,b2,c1,c2,d1,d2;
point A,B,C,D,a,b,c,d;
while(t--)
{
cin>>a1>>a2>>b1>>b2>>c1>>c2>>d1>>d2;
A.x=a1;A.y=a2;B.x=b1;B.y=b2;C.x=c1;C.y=c2;D.x=d1;D.y=d2;
cin>>a1>>a2>>b1>>b2>>c1>>c2>>d1>>d2;
a.x=a1;a.y=a2;b.x=b1;b.y=b2,c.x=c1;c.y=c2;d.x=d1;d.y=d2;
double x1=B.x-A.x-b.x+a.x;
double y1=B.y-A.y-b.y+a.y;
double x2=D.x-A.x-d.x+a.x;
double y2=D.y-A.y-d.y+a.y;
double AA=a.x-A.x;
double BB=a.y-A.y;
double u=(BB*x2-AA*y2)/(x2*y1-x1*y2);
double r=(BB*x1-AA*y1)/(x1*y2-x2*y1);
cout<<fixed<<setprecision(8)<<A.x+u*(B.x-A.x)+r*(D.x-A.x)<<" "<<
A.y+u*(B.y-A.y)+r*(D.y-A.y)<<endl;
}
return 0;
}
1010 - Planar graph
题意:
给你一个平面图,图中一些边,每条边的编号为题目给出的顺序,问最少要消除那些边,使得原来平面图中的平面全部连通,给出字典序最小的消除边的解。
做法:
由于我们要消去一些边使原图中的平面两两连通,我们可知在消去边之后原来的图中不存在环,那么消去边后的图应该是一棵树,又因为我们要给出消去边字典序最小的解,因此我们可以把边的序号作为边权,构造一棵最大生成树,未被使用的边即为字典序最小的要消除的边。
代码:
/*
author:wuzx
*/
#include<bits/stdc++.h>
#define ll long long
#define int long long
#define endl "\n"
#define P pair<int,int>
#define f first
#define s second
using namespace std;
const int inf = 0x3f3f3f3f;
int t;
int n,m,k;
struct dsu{
const int nn;
vector<int> fa;
dsu(int n1):nn(n1),fa(n+1)
{
for(int i=0;i<=n;i++)
fa[i]=i;
}
int find(int x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void merge(int x,int y)
{
int aa=find(x),bb=find(y);
if(aa!=bb)
fa[aa]=bb;
}
};
#define tp3 tuple<int,int,int>
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>t;
while(t--)
{
cin>>n>>m;
dsu ds(n);
vector<tp3> a;
for(int i=1;i<=m;i++)
{
int u,v;
cin>>u>>v;
a.emplace_back(i,u,v);
}
function<bool(tp3,tp3)> cmp = [&](tp3 aa,tp3 bb)
{
return get<0>(aa)>get<0>(bb);
};
sort(a.begin(),a.end(),cmp);
vector<int> vis(m+1,0);
for(int i=0;i<m;i++)
{
int u,v,w;
tie(w,u,v)=a[i];
if(ds.find(u)!=ds.find(v))
{
ds.merge(u,v);
vis[w]=1;
}
}
int ans=count(vis.begin()+1,vis.end(),0);
cout<<ans<<endl;
for(int i=1;i<=m;i++)
if(!vis[i])
cout<<i<<" ";
cout<<endl;
}
return 0;
}
1012 - Loop
题意:
给你一个数组 a a a ,你可以对数组 a a a 做 k k k 次以下操作:
选定 [ l , r ] [l,r] [l,r] ,将 a [ l , … , r ] a[l,…,r] a[l,…,r] 中所有元素向左移动一个单位,移动后 a [ l ] a[l] a[l] 在 a [ r ] a[r] a[r] 的位置上,问:在 k k k 次操作后,输出字典序最大的数组 a a a
做法:
考虑贪心做法,每次移动我们都把当前剩余序列中第一个 a [ i ] < a [ i + 1 ] a[i]<a[i+1] a[i]<a[i+1] 中第一个 a [ i ] a[i] a[i] 放入优先队列 q q q 中(这里找第一个 a [ i ] < a [ i + 1 ] a[i]<a[i+1] a[i]<a[i+1] 使用的是用并查集实现的链表,每次删除当前元素就用前一个元素指向后一个元素),用完 k k k 次操作或没有满足条件的 a [ i ] a[i] a[i] 时,我们开始将优先队列中的数插入回原序列中(也就是将 a [ l ] a[l] a[l] 放到原来 a [ r ] a[r] a[r] 的位置),在原序列中从头开始,如果 a [ i ] < q . t o p ( ) a[i] < q.top() a[i]<q.top() ,则将优先队列的堆首元素插入到 a[i] 前面,否则 i i i++ ,原剩余序列如果遍历完,就按顺序将优先队列中元素放到原序列后方,若优先队列中无元素,就直接把构造好的序列输出即可。
代码:
/*
author:wuzx
*/
#include<bits/stdc++.h>
#define ll long long
// #define int long long
#define endl "\n"
#define P pair<int,int>
#define f first
#define s second
using namespace std;
const int inf = 0x3f3f3f3f;
int t;
int n,m,k;
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>t;
while(t--)
{
cin>>n>>k;
vector<int> a(n+1),pre(n+1),bak(n+2);
for(int i=1;i<=n;i++)
{
cin>>a[i];
pre[i]=i;
bak[i]=i;
}
bak[n+1]=n+1;
priority_queue<int> q;
function<int(int)> find1 = [&](int x)
{
return pre[x]==x?x:pre[x]=find1(pre[x]);
};
function<int(int)> find2 = [&](int x)
{
return bak[x]==x?x:bak[x]=find2(bak[x]);
};
int idx=1;
int fst=inf;
while(k)
{
int now=find2(idx);
if(now==n)
break;
int nxt=find2(now+1);
if(a[now]<a[nxt])
{
q.push(a[now]);
// cout<<a[now]<<" "<<now<<endl;
k--;
fst=min(fst,now);
int n1=now;
bak[now]=nxt;
if(find1(now-1)!=0)
{
pre[find1(now)]=find1(now-1);
idx=pre[now];
}
else
idx=bak[now];
}
else
idx=nxt;
}
int now=find2(1);
vector<int> ans;
while(now<fst&&now<=n)
{
// cout<<"out "<<a[now]<<endl;
ans.push_back(a[now]);
now=find2(now+1);
}
now=find2(now);
while(!q.empty())
{
if(a[now]>=q.top())
{
ans.push_back(a[now]);
now=find2(now+1);
}
else
{
ans.push_back(q.top());
q.pop();
}
if(now==n+1)
break;
}
while(!q.empty())
{
ans.push_back(q.top());
q.pop();
}
while(now<=n)
{
ans.push_back(a[now]);
if(now==n)
break;
now=find2(now+1);
}
for(int i=0;i<n;i++)
cout<<ans[i]<<" \n"[i==n-1];
}
return 0;
}