10.14 一中模拟高中2

· · 个人记录

T1

给定 n,m,表示有 n 个数,每个数可以为 [1,m] 中的数,最大化

注意到这个式子的本质是将两两数字进行匹配,求出异或和。

对每一位进行考虑,也就是算出每一位的贡献。
注意到异或的性质,当一位的异或值是 1 时,一定是 0\oplus1=1,所以每一位的贡献就是一的个数乘零的个数乘当前位置 p 的权值 cnt_{1} \times cnt_{0} \times 2 ^ p

所以我们将每个数字二进制分解,统计每一位 1 的个数(0的个数可以用 n-cnt_{0} 计算),乘以权值即可,异或和就等于每一位的贡献之和。

这样我们将复杂度从 O(n^2) 降到了 O(n\times\log{V})

再来看到这道题,答案很显然,逐位考虑。要使和最大我们要让 cnt_{0}cnt_{1} 的差值尽可能小(和为定值,差值越小乘积越大)。

```cpp #include<bits/stdc++.h> #define ll long long using namespace std; const int MOD = 1e9 + 7; template<typename TY> inline void read(TY &s){ ll x = 0,f = 1; char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } s = x * f; } ll n,m; ll count(ll m){ ll cnt = 0; while(m){ m/=2; cnt++; } return cnt; } int main(){ freopen("testdata.in","r",stdin); freopen("testdata.out","w",stdout); int T; read(T); while(T--){ read(m); read(n); if(m == 1){ cout << "0\n"; continue; } ll k = count(m); k = 1ll << k; k = (k - 1 + MOD) % MOD; cout << ((n * n) / 4ll) % MOD * k % MOD << "\n"; } return 0; } ``` 而异或还有许多美妙的性质,像可以进行前缀和运算。 计算区间异或和的和是要用这两种方法相结合,不同的是此时有 $(n+1)$ 个数(前缀)。 [例题](https://www.luogu.com.cn/problem/P9236) # T2 有 $n$ 台密码机,需要破解 $k$ 台,每台有两个值 $a_{i},b_{i}$。如果破译时间达到 $a_{i}$ 就破译成功,然后可以选择继续破译到 $b_{i}$ 获得一个机械人偶,机械人偶拥有和你一样的效率,你们可以同时破译也可分开破译。特别地,如果一个密码机被破译后没有机械人偶则 $b_{i}=-1$,否则 $b_{i}>=a_{i}$,问破译 $k$ 台密码机的最小时间。 第一步转化:发现人和机械人偶同时破译优于分开破译。 设破译速度为 $x$,破译 $a,b$ 时间的密码机,显然 $\frac{a+b}{2} <= \max(a,b)$,或者说这样操作至少不劣。所以人偶问题我们转化为每多一个人偶,破译的效率增加一倍。 第二步转化:如果我们已经定下来选几个密码机破译到 $a_{i}$,选几个密码机破译到 $b_{i}$,我们一定是先破译所有的 $b$,再破译所有的 $a$,且破译 $b$ 的时候排序去做,因为破译 $b$ 可以看作获得一样多的效率,把更多的效率攒到需要更长时间的时候去用,不能获得效率的 $a$ 自然放在效率攒够后再做最优,问题转化为贪心问题。 策略有了,如何统计答案? 可以枚举破译了几个 $b$(这样可以知道破译 $a$ 时的效率),然后考虑到当 $b_{i} < b_{j}$ 的时候,$b_{i}$ 太长的操作时间可能会影响到后续 $a_{j}$ 的最小破译时间,比如[这样](https://cdn.luogu.com.cn/upload/image_hosting/70z20gdp.png)。所以不是贪心的选最前面的几个 $b$,而是进行 $dp$ 记录前面破译了几个 $a$,几个 $b$,所用的最小时间。 复杂度 $O(n^4)$,考虑优化。 第三步转化:注意到关键信息,如果破译了最后一个 $b$,则前面没有啥都不破译的密码机,因为如果有,交换他们显然会让后面的破译效率更高。 所以我们不用记录前面破译了几个 $a$,当破译好了 $x$ 个 $b$ 后,后面几个 $a$ 的贡献也可以用后缀的前缀和 $O(1)$ 计算,总复杂度 $O(n^3)$。 ```cpp #include<bits/stdc++.h> #define ll long long using namespace std; const int N = 510; template<typename TY> inline void read(TY &s){ ll x = 0,f = 1; char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } s = x * f; } int n,k; struct node{ double a,b; }z[N]; bool cmp1(node a,node b){ return a.a < b.a; } bool cmp2(node x,node y){ return x.b < y.b; } bool is_A,is_B; void solve_is_A(){ sort(z+1,z+n+1,cmp1); double res = 0; for(int i=1;i<=k;i++){ res += z[i].a; } printf("%.10lf",res); } double c[N],s[N][N],f[N][N],ans; void chkmin(double &x,double y){ if(x > y) x = y; } int main(){ // freopen("mechanic.in","r",stdin); // freopen("mechanic.out","w",stdout); read(n); read(k); for(int i=1;i<=n;i++){ cin >> z[i].a >> z[i].b; if(z[i].b != -1) is_A = true; if(z[i].b != -1 && z[i].b != z[i].a) is_B = true; } if(!is_A){ solve_is_A(); return 0; } for(int i=1;i<=n;i++){ if(z[i].b == -1) z[i].b = 1e9; } for(int i=1;i<=n;i++){ for(int j=i;j<=n;j++){ c[j] = z[j].a; } sort(c+i,c+n+1); for(int j=i;j<=n;j++){ s[i][j-i+1] = s[i][j-i] + c[j]; } } ans = s[1][k]; for(int u=0;u<=k;u++){ for(int i=0;i<=n;i++){ for(int j=0;j<=n;j++){ f[i][j] = 1e18; } } f[0][0] = 0; for(int i=1;i<=k;i++){ for(int j=0;j<i;j++){ chkmin(f[i][j],f[i-1][j] + z[i].a / (u + 1)); if(z[i].b < 1e9) chkmin(f[i][j+1],f[i-1][j] + z[i].b / (j + 1)); } chkmin(ans,f[i][u] + s[i+1][k-i] / (u + 1)); // cerr << "dp: " << f[i][u] + s[i+1][k-i]/(u+1) <<"\n"; } } printf("%.10lf",ans); return 0; } ```