非常感谢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次。