零.写在前面
第一次给比赛赛题写题解,有不足请多多指教。
原比赛地址这儿
适合普及+/提高-的人食用
一.T1 运算
看原题戳这儿
1.审题
在一行中从左到右写着 (n) 个数字 (a_1,a_2…a_n),你需要在每相邻的两个数字间填入" + "或者" ^ "(异或)中的一个。定义一种方案的权值为从左到右依次计算每个符号后得到的答案(即本题规定加法和异或的优先级相同),请你求出可以得到的最大的权值。
数据范围:(1 le n le 100000 , 0 le a_i le 100000)
既然(n)不大于(100000),那么就要往(O(n))和(O(nlogn))的算法这方面想。
2.概念
异或
异或又可以写成xor,^,⨁等,是计算机语言中的位运算之一
(a igoplus b)表示的是:在二进制的表达下,a+b但是不进位。(for example:)
3.做题
既然在两个数之间只能添加(+)和(^)而每个数都是正整数,所以进位加法一定比不进位加法得到的结果要大,如下:
所以最大的权值就是(sum_{i=1}^n a_i)了,(for)一遍轻松切掉,时间复杂度(O(n))
4.代码
#include<bits/stdc++.h>
using namespace std;
long long int a,b,c,d,ans;
int main()
{
scanf("%lld",&a);
for(int i=1;i<=a;++i)
{
long long int x;
scanf("%lld",&x);
ans+=x;
}
printf("%lld
",ans);
return 0;
}
二、T2 颜色
看原题戳这儿
1.审题
有一个长度为 n 的序列,每个位置有一个小球。每个小球有一种颜色,为 1~n 中的整数。一个区间的美观度定义为这个区间中的小球的颜色种类数。请问这个序列中的所有区间(()共 (n*(n+1)/2) 个())的美观度之和是多少(?)
数据范围:(1 le n le 1000000),时间复杂度肯定要(O(n))
2.做题
首先,我们知道前一个小球的位置会影响到后一个同颜色小球的位置,所以我们用一个数组(a_i)来存储颜色为(i)的小球最后出现的位置
因为一个小球能做的贡献就是它从当前点到末尾的点的数量乘以上一个同颜色的点当前点的点的数量,所以我们能推出公式:
于是这道题就顺利地切掉了,时间复杂度(O(n))
3.代码
#include<bits/stdc++.h>
using namespace std;
long long int a,b,c,d,ans;
long long int aa[100001],to[1000001];
int main()
{
scanf("%lld",&a);
for(int i=1;i<=a;++i)
{
long long int x;
scanf("%lld",&x);
ans=ans+(a-i+1)*(i-aa[x]);
aa[x]=i;
}
printf("%lld
",ans);
return 0;
}
三、T3 袋鼠
看原题戳这儿
1.审题
有 (n) 只袋鼠,如果一只袋鼠的体积大于等于另一只袋鼠的两倍,那么大袋鼠可以把小袋鼠装进袋子里。如果一只袋鼠被装进了袋子里, 它就不能再装其他的袋鼠了(也就是说不能套娃)。请问最多可以有多少只袋鼠被装进了袋子里?
数据范围:(1 le n le 100000,1 le a_i le 10000)
显然,时间复杂度要控制在(O(n))和(O(nlogn))之间。
2.思考
这道题显然不能直接匹配,于是便想到贪心思想
贪心是啥?
贪心算法(英文:greedy algorithm),是用计算机来模拟一个“贪心”的人做出决策的过程。这个人十分贪婪,每一步行动总是按某种指标选取最优的操作。而且他目光短浅,总是只看眼前,并不考虑以后可能造成的影响。
可想而知,并不是所有的时候贪心法都能获得最优解,所以一般使用贪心法的时候,都要确保自己能证明其正确性。
——OI-wiki
3.做题
既然已经确定了要用贪心,那就往这边想吧
首先要sort一遍,因为我们要让能装进袋子里的袋鼠数量最多,要从大到小遍历,所以我们要从大到小排序
因为不能套娃,所以最优的做法是从(a_{n/2})和(a_n)开始枚举,若(a_n<2*a_{n/2}),就继续往小的枚
4.代码
#include<bits/stdc++.h>
using namespace std;
int aa[1000100];
int a,b,c,d,ans;
int main()
{
scanf("%d",&a);
for(int i=1;i<=a;++i) scanf("%d",&aa[i]);
sort(aa+1,aa+a+1);
for(int i=a/2;i>=1;--i)
{
if(aa[i]*2<=aa[a]) ans++,a--;
}
printf("%d
",ans);
return 0;
}
四、T4 树
看原题戳这儿
1.审题
给定一棵 n 个点的树和树上的 m 条链,请问有多少对链有至少一个公共点。
题目非常明了、通俗易懂,显然这是一道关于树的题。
2.做题(如何处理树上两条链的相交)
在树上的两条链相交时有分两种情况:在LCA处相交和不在LCA处相交
在LCA处相交
将所有的链的(LCA)用一次(tarjan)求出来,用一个数组(lca)存储下来,(lca_i)表示有多少个点的(LCA)是(i)点,我们知道如果有n条链相交于一点的话,就会产生(n imes (n-1)) 对有公共点的链,所以我们(for)一遍就行啦。
不在LCA处相交
判断一条链与多少条链相交,我们需要进行一次预处理,我们需要开一个数组(sum),(sum_u)表示从(u)点到根节点总共会经过多少条链
因为我们知道子节点至根节点经过的链中必定会有其父节点至根节点经过的所有的链,而且还会包含所有经过自己且不经过其父结点的链,这显然就是(LCA)是该节点的链的数量,所以我们可以推出公式
然后用(dfs)跑一边就行了,(O(n))就能做出来
在我们求完了(sum)数组之后,我们就可以利用前缀和的思想,推出
满足:(u)和(v)在同一条以根节点为起始点的链上且(u)比(v)深
按照这样枚举每一条链,就可以求出来啦
答案
将以上的两种情况求出来之后,把答案全部求出来就行了,时间复杂度(O(nlogn)),可以
3.代码
#include<bits/stdc++.h>
using namespace std;
struct Edge{
int to,nxt;
} e[200001];
struct Node{
int to,nxt,num;
} qe[200001];
struct node{
int u,v,lca;
} l[100001];
int a,b,c,d,ans,cnt,qcnt;
int head[100001],qhead[100001],fa[100001],lca[100001],sum[100001];
bool vis[100001];
void add(int u,int v)
{
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
void qadd(int u,int v,int x)
{
qe[++qcnt].to=v;
qe[qcnt].nxt=qhead[u];
qhead[u]=qcnt;
qe[qcnt].num=x;
}
int fi(int x)
{
if(x==fa[x]) return x;
return fa[x]=fi(fa[x]);
}
void qwe(int x,int y)
{
x=fi(x),y=fi(y);
if(x!=y) fa[y]=x;
}
void Tarjan(int u)
{
fa[u]=u;
for(int i=head[u];i!=-1;i=e[i].nxt)
{
int v=e[i].to;
if(fa[v]!=-1) continue;
Tarjan(v);qwe(u,v);
}
for(int i=qhead[u];i!=-1;i=qe[i].nxt)
{
int v=qe[i].to;
if(fa[v]==-1) continue;
l[qe[i].num].lca=fi(v);
}
}
void dfs(int u,int v)
{
vis[u]=true;
sum[u]=sum[v]+lca[u];
for(int i=head[u];i!=-1;i=e[i].nxt)
{
int v=e[i].to;
if(vis[v]==false)
{
dfs(v,u);
}
}
}
int main()
{
memset(fa,-1,sizeof(fa));
memset(head,-1,sizeof(head));
memset(qhead,-1,sizeof(qhead));
scanf("%d%d",&a,&b);
for(int i=1;i<a;++i)
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y),add(y,x);
}
for(int i=1;i<=b;++i)
{
int x,y;
scanf("%d%d",&x,&y);
l[i].u=x,l[i].v=y;
qadd(x,y,i),qadd(y,x,i);
}
Tarjan(1);
for(int i=1;i<=b;++i) lca[l[i].lca]++;
dfs(1,0);
for(int i=1;i<=b;++i) ans+=sum[l[i].u]+sum[l[i].v]-2*sum[l[i].lca];
for(int i=1;i<=a;++i) ans+=lca[i]*(lca[i]-1)/2;
printf("%lld
",ans);
return 0;
}
/*
5 3
1 2
1 3
2 4
2 5
4 5
1 4
3 5
*/