智算之道初赛第三场解析
解析:
对于 100% 的数据,直接按照题意模拟即可。
代码:
int main()
{
int n,L,A,B;
int opt,l=0,a=0,i;
scanf("%d %d %d %d",&n,&L,&A,&B);
for(i=0;i<n;i++)
{
scanf("%d",&opt);
if(opt==1)
{
scanf("%d",&a);
}
else if(opt==2)
{
scanf("%d",&l);
}
else if(opt==3)
{
if(l>=L&&a>=A&&a<=B)
printf("%d\n",a);
else
printf("GG\n");
}
}
}
解析:
对于 50% 的数据,直接开数组储存所有手牌,每次暴力遍历整个数组找到第一个相同的删 除,模拟即可。
对于 100% 将手牌放进队列,开桶记录每种颜色的第一个位置,每插入一个就找第一个位置 删除,在那个位置打上标记,每次从队头弹出打了标记的元素,模拟即可。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+7;
int A[maxn],B[maxn];
int qA[maxn<<5],qB[maxn<<5];
int lA,lB,rA,rB;
int cntA,cntB,cnt;
int ans,n;
pair<int,int> Tmp[maxn];
int main()
{
int i,x;
scanf("%d",&n);
for(i=0;i<4*n-2;i++)
{
scanf("%d",&x);
if(i&1)
{
if(A[x])
{
cntA--;
A[x] = 0;
}
else
{
A[x] = i+1;
cntA++;
}
}
else
{
if(B[x])
{
cntB--;
B[x] = 0;
}
else
{
B[x] = i+1;
cntB++;
}
}
}
int tot = 0;
for(i=1;i<=n;i++)
if(A[i])
Tmp[++tot] = make_pair(A[i],i);
sort(Tmp+1,Tmp+1+tot);
for(i=0;i<tot;i++)
qA[i] = Tmp[i-1].second;
rA = tot-1;
tot = 0;
for(i=1;i<=n;i++)
if(B[i])
Tmp[++tot] = make_pair(B[i],i);
sort(Tmp+1,Tmp+1+tot);
for(i=0;i<tot;i++)
qB[i] = Tmp[i].second;
rB = tot-1;
while(cntA&&cntB)
{
cnt++;
if(cnt&1)
{
while(!A[qA[lA]]&&lA<=rA)
lA++;
--cntA;
A[qA[lA]] = 0;
if(B[qA[lA]])
{
--cntB;
B[qA[lA]] = 0;
}
else
{
++cntB;
B[qA[lA]] = 1;
qB[++rB] = qA[lA];
lA++;
}
}
else
{
while(!B[qB[lB]]&&lB<=rB)
lB++;
--cntB;
B[qB[lB]] = 0;
if(A[qB[lB]])
{
--cntA;
A[qB[lB]] = 0;
}
else
{
++cntA;
A[qB[lB]] = 1;
qA[++rA] = qB[lB];
lB++;
}
}
}
printf("%d\n",cnt);
return 0;
}
解析:
对于 10% 的数据,爆搜顺序按照规则算代价即可。对于另外 40% 的数据,发现顺序就是链的顺序,直接按照规则算代价即可。先按照子树大小排序,然后跑一遍 DFS 序就是最优序列的一种,证明如下:
1.先尝试证明 DFS 序一定最优,对于以同一深度的节点为根的子树,在一棵中找一个叶 子节点 j ,在另一棵里找一个根节点 i (当前序列中 j 所在的子树的根在 i 之前出现)。
2.假设 j < i 的时候代价最小,尝试将 j 放在 i 的后面,此时 i < j。那么我们发现如果 j 放在 i 与 i 的孩子们之间,i 的孩子们和 i 的距离都会加 1,所以总代价会增加,而 j 和 j 的父亲的距离一定也会增加,所以总代价也会增加。
所以对于 j 所在子树的根比 i 在序列中出现的早的情况,j < i 的情况是最优的。
3.那么每个子树形成了一段连续的区间,对于每个节点进行调整,最后我们发现形成了一个DFS 序(有时别的序列也会和 DFS 序列一样优秀,但是此处证明的是dfs序列是最优解之一,不是唯一解。)
4.接下来再证明先遍历子树小的更优秀。
5.所有的 size 排序之后,第 i 个根节点的贡献是前面所有树的 size 之和,所以要排序。综上所述,当我们进行 DFS 序且按子树大小排序时,求出代价即为最优
代码:
#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;
const int maxn = 1e6 + 5;
int cnt[maxn];
vector<int> v[maxn], p;
// 求子树结点的个数
void dfs(int u)
{
int i,t,e;
cnt[u]=1;
t=v[u].size();
for (i=0;i<t; i++)
{
e=v[u][i];
dfs(e);
cnt[u]+=cnt[e];
}
}
bool cmp(int a,int b)
{
return cnt[a]<cnt[b];
}
int main()
{
int n,m,x,i,j,t;
long long temp,ans;
scanf("%d %d",&n,&m);
for (i=2;i<=n;i++)
{
scanf("%d",&x);
v[x].push_back(i);
}
dfs(1); //求出每棵子树结点的个数 cnt[]
ans=1; //根节点1的贡献点是1
// 分别对 每个结点作为根节点时 计算它的子节点的贡献值
for (i=1;i<=n;i++)
{
p.clear();
t=v[i].size();
for (j=0;j<t;j++)
p.push_back(v[i][j]);
// 把结点i的所有儿子结点按照子树结点个数从小到大排序
sort(p.begin(),p.end(),cmp);
temp=1;
// 计算结点i的子节点到i的贡献点
t=p.size();
for (j=0;j<t;j++)
{
x=p[j];
ans+=temp;
temp+=cnt[x];
}
}
printf("%lld\n",ans*m);
return 0;
}