专栏文章

10.14 一中模拟高中2

个人记录参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@minlvl5y
此快照首次捕获于
2025/12/02 04:32
3 个月前
此快照最后确认于
2025/12/02 04:32
3 个月前
查看原文

T1

给定 n,mn,m,表示有 nn 个数,每个数可以为 [1,m][1,m] 中的数,最大化
注意到这个式子的本质是将两两数字进行匹配,求出异或和。
对每一位进行考虑,也就是算出每一位的贡献。
注意到异或的性质,当一位的异或值是 11 时,一定是 01=10\oplus1=1,所以每一位的贡献就是一的个数乘零的个数乘当前位置 pp 的权值 cnt1×cnt0×2pcnt_{1} \times cnt_{0} \times 2 ^ p
所以我们将每个数字二进制分解,统计每一位 11 的个数(00的个数可以用 ncnt0n-cnt_{0} 计算),乘以权值即可,异或和就等于每一位的贡献之和。
这样我们将复杂度从 O(n2)O(n^2) 降到了 O(n×logV)O(n\times\log{V})
再来看到这道题,答案很显然,逐位考虑。要使和最大我们要让 cnt0cnt_{0}cnt1cnt_{1} 的差值尽可能小(和为定值,差值越小乘积越大)。
cnt0cnt_{0}n2\lfloor\frac{n}{2}\rfloor 时差值最小。而每一位都可以达到最大值,令n2\lfloor\frac{n}{2}\rfloor 的数最高位是 11,其余为是 00,另一半数最高位是 00,其余位是 11,此时的答案为 n2×(nn2)×(2p+11)=n24×(2p+11)\lfloor\frac{n}{2}\rfloor\times (n-\lfloor\frac{n}{2}\rfloor)\times(2^{p+1}-1) = \lfloor\frac{n^2}{4}\rfloor\times(2^{p+1}-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)(n+1) 个数(前缀)。 例题

T2

nn 台密码机,需要破解 kk 台,每台有两个值 ai,bia_{i},b_{i}。如果破译时间达到 aia_{i} 就破译成功,然后可以选择继续破译到 bib_{i} 获得一个机械人偶,机械人偶拥有和你一样的效率,你们可以同时破译也可分开破译。特别地,如果一个密码机被破译后没有机械人偶则 bi=1b_{i}=-1,否则 bi>=aib_{i}>=a_{i},问破译 kk 台密码机的最小时间。
第一步转化:发现人和机械人偶同时破译优于分开破译。 设破译速度为 xx,破译 a,ba,b 时间的密码机,显然 a+b2<=max(a,b)\frac{a+b}{2} <= \max(a,b),或者说这样操作至少不劣。所以人偶问题我们转化为每多一个人偶,破译的效率增加一倍。
第二步转化:如果我们已经定下来选几个密码机破译到 aia_{i},选几个密码机破译到 bib_{i},我们一定是先破译所有的 bb,再破译所有的 aa,且破译 bb 的时候排序去做,因为破译 bb 可以看作获得一样多的效率,把更多的效率攒到需要更长时间的时候去用,不能获得效率的 aa 自然放在效率攒够后再做最优,问题转化为贪心问题。
策略有了,如何统计答案?
可以枚举破译了几个 bb(这样可以知道破译 aa 时的效率),然后考虑到当 bi<bjb_{i} < b_{j} 的时候,bib_{i} 太长的操作时间可能会影响到后续 aja_{j} 的最小破译时间,比如这样。所以不是贪心的选最前面的几个 bb,而是进行 dpdp 记录前面破译了几个 aa,几个 bb,所用的最小时间。
复杂度 O(n4)O(n^4),考虑优化。
第三步转化:注意到关键信息,如果破译了最后一个 bb,则前面没有啥都不破译的密码机,因为如果有,交换他们显然会让后面的破译效率更高。
所以我们不用记录前面破译了几个 aa,当破译好了 xxbb 后,后面几个 aa 的贡献也可以用后缀的前缀和 O(1)O(1) 计算,总复杂度 O(n3)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;
}

评论

0 条评论,欢迎与作者交流。

正在加载评论...