P5016 [NOIP 2018 普及组] 龙虎斗

龙虎斗题解与long long警示

记录44

#include<bits/stdc++.h>
using namespace std;
long long f(long long a,long long b){
	long long t=a-b;
	return abs(t);
}
int main(){
	long long a[100010]={},n,m,s1,s2,p;
	long long b[100010]={};
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	cin>>m>>p>>s1>>s2;
	a[p]+=s1;
	long long D=0,T=0;
	for(int i=1;i<=n;i++){
		if(i<=m) D+=a[i]*(m-i);
		else T+=a[i]*(i-m);
	}
	if(D==T){
		cout<<m;
		return 0;
 	}
	for(int i=1;i<=n;i++){
		long long Dn=D,Tn=T;
		if(i<m) Dn+=s2*(m-i);
		if(i>m) Tn+=s2*(i-m);
		long long t=f(Dn,Tn);
		b[i]=t;
	}
	long long x=b[1],num=1;
	for(int i=2;i<=n;i++){
		if(x>b[i]){
			x=b[i];
			num=i;
		} 	
	}
	cout<<num;
	return 0;
} 

题目传送门https://www.luogu.com.cn/problem/P5016


突破点

一个兵营的气势为:该兵营中的工兵数× 该兵营到 m 号兵营的距离;参与游戏 一方的势力定义为:属于这一方所有兵营的气势之和

游戏过程中,某一刻天降神兵,共有 s1​ 位工兵突然出现在了 p1​ 号兵营。作为轩轩和凯凯的朋友,你知道如果龙虎双方气势差距太悬殊,轩轩和凯凯就不愿意继续玩下去了。为了让游戏继续,你需要选择一个兵营 p2​,并将你手里的 s2​ 位工兵全部派往 兵营 p2​,使得双方气势差距尽可能小。

👉维护游戏平衡,双方兵数*距离差不多


思路

  1. 写一个绝对值函数计算双方差距
  2. 用数组记录一下初始的情况
  3. 更新突然出现的兵,更新数组值
  4. 遍历手头的兵调往不同兵营后出现的差距(绝对值)情况
  5. 遍历所有差距,找到最小的情况

代码简析

#include<bits/stdc++.h>
using namespace std;
long long f(long long a,long long b){
	long long t=a-b;
	return abs(t);
}
int main(){
	long long a[100010]={},n,m,s1,s2,p;
	long long b[100010]={};
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	cin>>m>>p>>s1>>s2;
	a[p]+=s1;
	long long D=0,T=0;
	for(int i=1;i<=n;i++){
		if(i<=m) D+=a[i]*(m-i);
		else T+=a[i]*(i-m);
	}
	...
	return 0;
} 

f 函数 👉 计算差距,因为差距是双方累加起来的数相减,所以要用long long

依次输入兵营情况

a[p]+=s1; 👉 p兵营突然多出的兵

long long D=0,T=0;  👉 累计求双方的气势总和

#include<bits/stdc++.h>
using namespace std;
long long f(long long a,long long b){
	long long t=a-b;
	return abs(t);
}
int main(){
	long long a[100010]={},n,m,s1,s2,p;
	long long b[100010]={};
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	cin>>m>>p>>s1>>s2;
	a[p]+=s1;
	long long D=0,T=0;
	for(int i=1;i<=n;i++){
		if(i<=m) D+=a[i]*(m-i);
		else T+=a[i]*(i-m);
	}
	if(D==T){
		cout<<m;
		return 0;
 	}
	for(int i=1;i<=n;i++){
		long long Dn=D,Tn=T;
		if(i<m) Dn+=s2*(m-i);
		if(i>m) Tn+=s2*(i-m);
		long long t=f(Dn,Tn);
		b[i]=t;
	}
	long long x=b[1],num=1;
	for(int i=2;i<=n;i++){
		if(x>b[i]){
			x=b[i];
			num=i;
		} 	
	}
	cout<<num;
	return 0;
} 

if(D==T){} 👉 双方气势相等,往分界线的中立兵营派兵

for循环 👉 从头到尾遍历兵营依次派兵,看看情况

long long Dn=D,Tn=T; 👉 后面用来更新龙虎双方的气势总和

用新加入的兵直接×距离,然后加入总气势就可以了

long long t=f(Dn,Tn);  👉 t数组用来存气势的差值
b[i]=t;  👉 差值存进b数组

long long x=b[1],num=1; 👉 x存最小气势,从第一个开始向后比较,num存下标编号

循环找出最小龙虎气势之差


补充

"十年OI一场空,不开long long见祖宗"这句信奥圈的"黑话"道出了无数选手的血泪史:


1. 字面含义:十年努力一朝归零

  • "十年OI一场空":辛苦训练多年,因一个小失误全盘皆输

  • "不开longlong见祖宗":变量类型没开long long,导致答案错误,只能"去见祖宗"(回家种田)

核心本质intlong long的范围差异,是竞赛中最隐蔽、最致命的陷阱。


2. 数据范围对比:为什么必须用long long

类型占用空间范围超出后果
int4字节-2³¹ ~ 2³¹-1
约 -21亿 ~ +21亿
溢出后变为负数,答案全错
long long8字节-2⁶³ ~ 2⁶³-1
约 -9×10¹⁸ ~ +9×10¹⁸
几乎不会溢出

CSP-J典型数据n ≤ 10⁵a[i] ≤ 10⁹,累加和可达10¹⁴,远超int上限。


3. 四大翻车场景(真实血泪)

场景1:累加求和(最常见)

int sum = 0;  // ❌ 错误:int存不下
for (int i = 0; i < n; i++) {
    sum += a[i];  // a[i]最大1e9, n=1e5时,sum可达1e14,int溢出变成负数
}
cout << sum;  // 输出负值,0分!

正确

long long sum = 0;  // ✅ 正确:开long long
场景2:乘法运算(最隐蔽)

int a = 100000, b = 100000;
int c = a * b;  // ❌ 错误:a*b先int溢出,结果=1410065408(错误)
// 正确: long long c = 1LL * a * b;
场景3:函数返回值(最崩溃)

int factorial(int n) {  // ❌ 错误:返回类型int
    if (n == 0) return 1;
    return n * factorial(n - 1);  // n=20就溢出,结果错误
}

正确

long long factorial(int n) {  // ✅ 正确:返回long long
    if (n == 0) return 1;
    return n * factorial(n - 1);
}
场景4:DP状态值(最致命)

int dp[1000][1000];  // ❌ 错误:DP值可能爆int
dp[i][j] = dp[i-1][j] + dp[i][j-1];  // 组合数问题,值增长极快

正确

long long dp[1000][1000];  // ✅ 正确:状态值开long long

4. 为什么难调试?隐蔽性分析

阶段表现选手心理
编译期无错误,无警告"代码没问题,稳了"
样例测试样例小,int够用,输出正确"AC了,交卷"
评测时大数据int溢出,输出负数/异常值"WA了?不可能!我代码逻辑完美"
赛后发现是int溢出,欲哭无泪"十年OI一场空..."

致命点小数据无法暴露问题,大数据一击必杀,且溢出后结果不可预测,调试困难。


5. 正确做法:竞赛安全编码规范

原则1:凡求和、累乘、DP值,无脑开long long

// 看到题目数据范围有1e9,直接开long long
long long sum = 0;        // 求和
long long prod = 1;       // 累乘
long long dp[1005] = {0}; // DP数组
原则2:强制转换习惯

int a, b;
// 乘法前先转long long
long long c = 1LL * a * b;  // 1LL * a先转long long,再乘b

// 或者在定义时转
long long c = static_cast<long long>(a) * b;
原则3:函数返回值和参数

// 任何可能计算大数的函数,返回类型用long long
long long calc(int n) {
    long long res = 0;
    // ...
    return res;
}

// 参数也可以用long long,避免传入int溢出
void func(long long x) { ... }
原则4:数组大小计算

int n;
cin >> n;
// 如果n最大1e5,且数组存的是累加值,开long long
vector<long long> a(n + 1);

6. 竞赛场真实案例

2023年CSP-J复赛 第3题n ≤ 10⁵a[i] ≤ 10⁹,求和。

  • int选手:sum开出int,60%数据通过,40%大数据WA,总分60分。

  • long long选手:sum开long long,100分。

2022年某省赛:组合数模运算,未开long long的选手全军覆没。


7. 这句话的幽默性与警示性

  • 幽默:用夸张的"见祖宗"形容回家种田,戏谑中带着自嘲

  • 警示:用最直白的方式告诫后人——细节决定成败

  • 传承:老选手用血泪总结的教训,新选手听到就刻进DNA

现代版本
"十年OI一场空,不开long long见祖宗,加速cin两行泪,忘记freopen两行泪"


8. 终极解决方案:无脑模板

#include <bits/stdc++.h>
using namespace std;

// 看到题目,先写这三行:
ios::sync_with_stdio(false);
cin.tie(nullptr);

// 然后定义变量:
int n;  // 下标用int
long long ans = 0;  // 答案一律long long
vector<long long> a(n + 1);  // 数据数组一律long long

// 计算时:
for (int i = 1; i <= n; i++) {
    ans += 1LL * a[i - 1] * a[i];  // 乘法先转long long
}

cout << ans << endl;  // 输出long long

9. 一句话总结

在CSP-J中,当你犹豫"这个变量要不要开long long"时,答案永远是:开!
因为开大了只是浪费8字节内存(可忽略),开小了这个变量可能让你"见祖宗"。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值