非常感谢YLM大佬的指导。
https://ac.nowcoder.com/acm/contest/3732/M
首先是按幂分组,这和我当时和CZL说的是一样的。
#include<bits/stdc++.h> typedef long long ll; using namespace std; const int N=1e5+5; ll a[N],b[N],c[N],d[N],val[N],n; bool vis[N]; int main(){ cin>>n; for(int i=1;i<=n;i++)cin>>a[i]; for(int i=1;i<=n;i++)cin>>b[i]; ll ans=a[1]; for(int i=2;i<=n;i++)if(!vis[i]){ int t=0;ll res=0; for(ll j=i;j<=n;j*=i,t++) vis[j]=1,c[t]=a[j],d[t]=b[j]; /*对每一个数的x次方进行遍历 遍历后对遍历过了的标记 把基数对应的x次方 的 收益 和 负影响分别记入数组cd 比如 i=2 j=2 t=0 c[0]=a[2] c[1]=a[4] c[2]=a[8] c[3]=a[16] d[0]=b[2] d[1]=b[4] d[2]=b[8] d[3]=b[16]*/ for(int j=0;j<(1<<t);j++) val[j]=0; //对0-2^(t-1) val数组归零 值得说明的是这里的t就是上面的存过的数的个数 //t=0的时候存的是自己 然后这里刚好和t-1抵消 也就是说 有几个抉择关联点 就是2的几次方个数归零 for(int j=1;j<(1<<t);j++){//枚举01串的每一种情况 for(int k=t-1;k>=0;k--) if(j>>k&1){ //从左往右检索 找第一个1 也就是选取的位置 只执行第一次 int f=0; for(int s=0;s<k;s++) if((k+1)%(s+1)==0&&(j>>s&1))f++; //在串里找幂组 找到一个f就++ val[j]=max(val[j],val[j^(1<<k)]+c[k]-d[k]*f); //j^(1<<k): 把首位的1变0 不能理解为什么是+c[3]-d[3] res=max(res,val[j]); break; } } ans+=res; } cout<<ans<<endl; }
然后还有几个点
for(ll j=i;j<=n;j*=i,t++) vis[j]=1,c[t]=a[j],d[t]=b[j];
这里j必须是ll 1e10超int范围
for(int j=0;j<(1<<t);j++) val[j]=0;
不知道为什么这个置零不能用memset换,换了就TLE,通过率51%。
val[j]=max(val[j],val[j^(1<<k)]+c[k]-d[k]*f);
这一行的核心操作我很可能还没看懂。
拿我的例子,n=16
幂集2 4 8 16
选取1 1 0 1 (J)
t=4 k=3
s=0 f++ s=1 f++ f=2
1101异或1000=0101
+c[3]-2d[3]
for(int k=t-1;k>=0;k--) if(j>>k&1){ int f=0; for(int s=0;s<k;s++) if((k+1)%(s+1)==0&&(j>>s&1))f++; val[j]=max(val[j],val[j^(1<<k)]+c[k]-d[k]*f); res=max(res,val[j]); break; }
这一层循环只会走一次,找到了从左往右第一个确认会拿的位点,就进入了循环而且break了。
这一块代码,负责找出来相应的J的情况下的取值。也就是把一个幂的所在组的取法对应值得出来。
但是他是以最低次幂来找的,也就是永远拿最小的作为一个标靶
好的我明白了。的确应该永远拿最小的一个作为标靶,但是为什么只加d[k],也只减d[k]的f倍呢?
k是从右往左数第一个k的位置,如果按照上面的案例
n=16
幂集2 4 8 16
选取1 1 0 1 (J)
t=4 k=3
s=0 f++ s=1 f++ f=2
1101异或1000=0101
+c[3]-2d[3]
那么val[j=1101]=val[0101]+c[3]-2d[3]
相当于递归下枚举{1,2,....,n}的所有子集——YLM大佬
这个递归的话那么就是说是反着的
实际上是
幂集2 4 8 16
选取1 0 1 1 (J)
然后用不选来递归
幂集2 4 8 16
选取1 1 0 0 (J)
在这个基础上来修改
我大概明白了,但是可能还是要自己写一下,或者找一道别的二进制枚举来练练。
查了一下memset可能时空效率偏低
memset是按字节进行赋值的(这也是为什么不能用memset对多个字节的变量赋值1的原因,-1就可以,因为-1是补码形式,所有位均为1),一个double型变量需要memset执行8次赋值才能将所有位置零,int需要4次。