A. Ancient Civilization
题意:给你一个数组a(十进制),长度为n,,现在要求你拿出一个ans(十进制),,使得这a[i](二进制)与ans(二进制)对应位置差异度和最小。
举个栗子:a[i]=5的二进制是101,ans=10的二进制是1010,咱们从右往左看,发现第一位,第二位,第三位,第四位都不一样,现在的差异度就是4,而差异度和就是
思路:这题的难点就是看懂题(嘿嘿,看懂题应该大家就会了吧),那其实是这就是一个选择问题,ans(二进制)位数最多就是 l 位,我们只需要考虑当前第 i 位取与不取,为了让差异度最小,那么我们pre数组存所有数(二进制)第 i 位的 1 的个数,然后,若当前第 i 位的 1 的个数大于了就取(一半的时候随便取不取,我这里没有取)
核心代码:
if(pre[i]>n-pre[i])b[i]=1;//b[]数组用来存ans的二进制
快速幂:
int kuai(int s,int n){
int ans=1;
while(n){
if(n&1)ans*=s;
s*=s;
n>>=1;
}
return ans;
}
快读:
inline int read() {
char ch = getchar(); int x = 0, f = 1;
while(ch < '0' || ch > '9') {
if(ch == '-') f = -1;
ch = getchar();
} while('0' <= ch && ch <= '9') {
x = x * 10 + ch - '0';
ch = getchar();
} return x * f;
}
而要实现这一切我们还得有一个十进制转换二进制(都2022年了博主还是只能手搓),和最后快速幂(当然可以用pow,但是pow是double类型的,出了问题你想不到)求ans答案,那么话说了这么多,上代码
t=read();//t组数据,这里博主用了快读,减少时间
while(t--){
n=read();l=read();//n个数,l是二进制最大位数
for(i=1;i<=n;i++)a[i]=read();
for(i=1;i<=n;i++){
da1=a[i];sum=1; //
while(da1){ //手搓十进制转二进制
if(da1%2){ //
pre[sum]++; //
} //
da1>>=1; //
sum++; //
}
}
for(i=1;i<=l;i++)if(pre[i]>n-pre[i])b[i]=1;//计算ans(二进制)
ans=0;//可别忘记更新ans哦,t组数据
for(i=1;i<=l;i++)if(b[i])ans+=kuai(2,i-1);//手搓二进制转十进制,kuai(2,i-1)是快速幂这里博主就不放出来了
cout<<ans<<endl;
for(i=1;i<=l;i++)pre[i]=0;//别忘记了跟新pre数组状态
}
return 0;
B. Elementary Particles
题意:t组数据,给你一个长度为n的数组a,现在要求你在数组中截取两个不相同但是长度相等的子序列,唯一要求是使得至少存在一个对应的位置上面数字一样,求最长长度是多少(不存在则输出-1)
比如1 2 3和5 2 7就是满足条件的,因为他们第二个位置相等,而1 3 2和2 1 3就不满足条件
思路:既然这是一道存在性的题目,那么偷奸耍滑的我们肯定直接考虑只要有一个相等的存在即可,现在我们给你一个a数组此时若和相等,那么此时由他们构成的数列最大是多少呢?是和。我们发现只要的左边序列的长度,和右边序列的长度,为什么这么说呢?因为在的左边,因此左边能取到多长,一定能取到,所以我们看的左边,同理,这也是我们只看右边的理由
关键步骤:因此我们只需要利用sort进行结构体排序(利用结构题,在存下每个数的时候,将它所在的位置也存下来)
关键公式:我们现在令的位置为 l ,令的位置为 r ,则长度,因为n为已知量,所以我们只需要考虑 l - r 最小,又因为我们结构体排序过了,所以我们只要需要算相邻两个值的差绝对值最小的那一个(前提是这两个数的值相等),最后和n相加即可
关键代码:
cmp:
bool cmp(node x,node y)
{
if(x.da==y.da)return x.xu<y.xu;//其实这步有些多余,不过为了安心还是写上去了,因为如何一样cmp是不会继续排下去的
return x.da<y.da;
}
对啦,写的时候就利用尺取的思想,l,r一起动就好啦
上代码:
struct node{
int xu,da;//xu是位置,da则是值
}a[500005];
bool cmp(node x,node y)
{
if(x.da==y.da)return x.xu<y.xu;//其实这步有些多余,不过为了安心还是写上去了,因为如何一样cmp是不会继续排下去的
return x.da<y.da;
}
signed main()
{
int i,j,t;int n,l,r,sum;//n个数,t组数据,l,r当前排过序后数列的位置,sum则是值
// jian1;jian2;jian3;//l,r位置的解读,例如现在1,8,3,现在l为2则在当前数列的位置是2
t=read();//快读
while(t--){
n=read();
for(i=1;i<=n;i++){
a[i].da=read();
a[i].xu=i;//存当前数的位置
}
sort(a+1,a+1+n,cmp);//排序
sum=-1;l=1;r=2;//l一定是小于r,当然你想要让他等于改一下后面就好了
while(r<=n){//r可不能出界哦(⊙o⊙),一共就n个数诶
if(a[l].da==a[r].da)sum=max(sum,a[l].xu+n-a[r].xu);//毕竟最后的答案可是求最值,所以得比较比较
l++;r++;//l当前位置的数被用过了 ,已经没了价值所以l++
}
cout<<sum<<endl;//输出答案
}
return 0;
}
C. Road Optimization
题意:一条路长度为len,n给限速标(第一个限速标一定再起点上面),用长度为n数组a表示的坐标(单位是公里),再给一个与数组a对应的b数组,记录第 i 个标到第 i+1 个标类每公里所需要的时间,算法为:,现在允许你最多删除k个点(也可以不删(⊙.⊙)),求最小
思路:此题看一眼,嗯...发现没有思路,看第二眼,嗯...没有思路,摆了摆了
其实此题应该用动态规划,局部最优使全局最优(前提是没有后效性,即前面你无论怎么选都不会影响到后面的选择)因此我写出意思是指保留第 i 个限速标(即不删除),然后现在还有p个删除的名额(即还可以删除p个限速标),现在关键是dp方程如何写
关键代码:
dp方程:
这里我就先把dp方程给了出来,里面的 j 是什么呢,里面的 j 是从第 j 个到第 i 个之内的限速标q全部删除(不包括 i,j ),对我们令最后的终点也是一个点,即a[n+1]=len
这里你可能会问,因为是连着删除不会漏解吗?当然不会,现在解惑:
首先我们的第 i 个保留,这就使得我们解决了后效性的问题,因为无论前面怎么删除,就算捅破了天,都不会影响后面的删除,其次,j 是小于 i 的,是指从第 j 个到第 i 个之内的限速标q全部删除(不包括 i,j ),所以我们是步步最优
废话不多说,直接上代码:
signed main()
{
int i,j,t;int n,len,p,k;
n=read();
len=read();
k=read();
for(i=1;i<=n;i++)a[i]=read();
for(i=1;i<=n;i++)b[i]=read();
a[++n]=len;
for(i=1;i<=n;i++)for(j=0;j<=k;j++)dp[i][j]=1e15;//我们得先将dp数组初始话,毕竟我们是一步一步去比较最小值,所以我们初始化为最大值
dp[1][k]=0;
for(i=2;i<=n;i++)
for(j=1;j<i;j++)
for(p=0;p<=k;p++)//要使用dp方程首先要保证咱们现在删除的路标没有超出k个,i和j之间有i-j-1个
if(p+i-j-1<=k)dp[i][p]=min(dp[i][p],dp[j][p+i-j-1]+(a[i]-a[j])*b[j]);
cout<<*min_element(&dp[n][0],&dp[n][k+1])<<endl;//因为答案是一步一步更新上来的,所以我们只需要找到在n处,删除0-k个的最小值
return 0;
}
由于博主太菜,当时没有写出来,赛后补题++
希望大家看了这篇文章都有进步,加油!(⊙0⊙)!
对了推一个知乎上面大佬的题解Codeforces Round #765 (Div. 2) A~D - 知乎 (zhihu.com)
博主的dp就是和这个大佬学的,主要是大佬讲的有点深奥,博主理解了好久