洛谷 P2671 求和

想要OI学得好,数学肯定不能少 看来我是永远也学不好了

此篇博文较长 废话较多 ,您可以选择观看

写在最前面:其实不需要用 long long 类型,只需在两数相乘时先取模一次就行了(为什么可行,请参见取模的百科 又是该死的数学知识
在下面的代码中,既有用 long long 的,又有用 int 的,也是因为当时并不知道这一点。

  1. 依照题意,模拟。三重循环枚举 x,y,z,复杂度约为 O(n^3)

    代码略(预估20~30左右)

  2. .不难发现,其实三元组与中间的 y没有关系。所以,我们只需枚举 x 与 z 即可,复杂度约为 O(n^2) 。附代码:(40分)

#include<iostream>
using namespace std;
int n,m;
int ans;
struct node{
	int clo;//颜色
	int num;//序号
}a[100007];
int Read(){//快读部分,其实加不加结果差不多
	char ch=getchar();
	bool flag=false;
	int res=0;
	while(ch<'0'||ch>'9'){
		if(ch=='-')flag=true;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		res=res*10+ch-'0';
		ch=getchar();
	}
	if(flag)return -res;
	else return res;
}
int main(){
	n=Read();m=Read();
	for(int i=1;i<=n;++i)
		a[i].num=Read();
	for(int i=1;i<=n;++i)
		a[i].clo=Read();
	for(int i=1;i<=n-2;++i){//至少有三个数,所以到 n-2
		for(int len=1;len<=(n-i)/2;++len){//确保 z 为整数,且中间的 y 也为整数
			if(a[i].clo==a[i+len+len].clo){
				ans=(ans+(i+len+len+i)*(a[i].num+a[i+len+len].num))%10007;
			}
		}
	}
	cout<<ans<<endl;
	return 0;
}
  1. 考虑优化(其实考场上写一个暴力就行了)。思考:既然三元组与中间的 y 其实并没有关系,而三元组又需要颜色相同,那么为什么不按照颜色来分排序呢?

    首先按照颜色的递增顺序排序,然后依次判断。判断时要注意颜色相同,序号的间隔要大于1(这样才有三元)

    排序算法为 O(nlogn),判断部分看上去为 O(n^2),实则大大减小。因为一种颜色不可能非常多,所以内部循环的次数相对较少(如果数据里出现某种颜色特别多,也没有办法了,出题人这就太毒瘤了)。附代码:(70分)

#include<iostream>
#include<vector>
#include<set>
#include<algorithm>
using namespace std;
long long n,m;
long long ans;
struct node{
	long long clo;
	long long sum;
	long long num;
}a[100007];//其实也不需要 long long
bool cmp(node x,node y){
	if(x.clo!=y.clo)return x.clo<y.clo;//优先按颜色排序
	else return x.num<y.num;
}
int Read(){
	char ch=getchar();
	bool flag=false;
	int res=0;
	while(ch<'0'||ch>'9'){
		if(ch=='-')flag=true;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		res=res*10+ch-'0';
		ch=getchar();
	}
	if(flag)return -res;
	else return res;
}
int main(){
	n=Read();m=Read();
	for(int i=1;i<=n;++i)
		a[i].sum=Read(),a[i].num=i;//记得保留序号
	for(int i=1;i<=n;++i)
		a[i].clo=Read();
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;++i){
		for(int j=i+1;j<=n;++j){
			if(a[i].clo!=a[j].clo)break;//颜色不一样,当前的元就不可能在作为 x 了
			if((a[j].num-a[i].num)%2==0&&a[i].num+1<a[j].num)//要判断间隔是偶数才能有 y (1和5中间的元为3)
				ans=(ans+(a[i].num+a[j].num)*(a[i].sum+a[j].sum))%10007;
		}
	}
	cout<<ans<<endl;
	return 0;
}
  1. 这是一个不太理解具体原因的优化:把 if 语句改为 continue 语句后,可以加快运行速度。附代码:(80分)
for(int i=1;i<=n-1;++i){
		for(int j=i+1;j<=n;++j){
			if(a[i].clo!=a[j].clo)break;
			if((a[j].num-a[i].num)%2==1)continue;
			if(a[i].num+1>=a[j].num)continue;
			ans=(ans+(a[i].num+a[j].num)*(a[i].sum+a[j].sum))%10007;
		}
	}

5.还可以继续优化。发现:其实排序是多余的,我们只要用一个二维数组,第一维储存颜色,第二维储存数字和原本的序号(结构体)。这样就省去了排序的时间。为了防止爆内存,明显不能直接定义数组为100000*100000,所以使用STL中的 vector

另:在扫描颜色时,不应当直接 O(n) 扫,而应当提前存储出现过的颜色

其实可以继续优化内存,通过 set 来自动对颜色排序,然而,因为笔者暂时无法写出 set 与 vector 的嵌套(懒得调 bug ),所以本篇不表(倘使以后会了,如果还能想起来这篇博文,那么再来补充一下)。

理论上来说,此程序避免了排序用时,应当更快一些,然而在实际效率中却不尽然,不知是因代码常数太大,还是因为STL太慢;但:部分资料表明,开启O2、3系列优化后的STL效率极高,所以只是OI这方面的规定对发挥STL比较不利而已,STL相当快(不过 STL 的 sort 是真的厉害,基本是没有人能写过。P.S.网上有篇博文貌似手写超过了 sort,笔者也不知道是因为测试数据还是真的手写超过了)。

在不开O2情况下(使用cin),超时四个点,在开启O2后,在cin的情况下,只会超时两个点(并且一个点是超时0.04s),而奇怪的是,若改用 scanf,则超时四个点。若使用快读,则只会超时一个点。

附代码:(cin版本)

#include<bits/stdc++.h>
using namespace std;
int m,n;
struct node{
    int sum;
    int num;
}a[100007];
vector<node> s[100007];//相当于二维数组
int vis[100007];//存放出现过的颜色,其实也可以用vector
int tot;
int ans;
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;++i){
	    cin>>a[i].sum;
	    a[i].num=i;
    }
    for(int i=1;i<=n;++i){
        int x;cin>>x;
        if(s[x].size()==0)vis[++tot]=x;
        s[x].push_back(a[i]);
    }
    for(int i=1;i<=tot;++i){
        int x=vis[i];//当前的颜色
        //因为要反复用到,这样可以减小代码的长度,也减少打错字母的可能
        for(int j=0;j<s[x].size()-1;++j){
            for(int k=j+1;k<s[x].size();++k){
            //记得是j+1,而非1(其实就是防止反复加上分数),一开始写错了
                if(abs(s[x][j].num-s[x][k].num)%2==1)continue;
                //最好带abs,负数的取模与正数不太一样
                ans=(ans+(s[x][j].num+s[x][k].num)%10007*(s[x][j].sum+s[x][k].sum))%10007;
                //两数相乘的时候先取模
            }
        }
    }
    cout<<ans<<endl;
    return 0;
}
  1. 那么,如何AC呢?笔者并不会(不愧是我 ),采用的是第一篇题解的方法。由于该篇题解说明较少,估计会有人看不懂推导的过程 其实是我这个弱智 ,这里做一些更具体、更易懂的解释。

    其方法如下:按颜色分组,再按其数字的奇偶性分组(即同组的元的序号奇偶性相同)。

    设第 i 个分组里有 k 个元,其数字为 x[1],x[2],x[3]……x[k],其原来的序号为 y[1],y[2],y[3]……y[k]。

    ans=

    (x[1]+x[2])*(y[1]+y[2])+(x[1]+x[3])*(y[1]+y[3])+……+(x[1]+x[k])*(y[1]+y[k])

    +(x[2]+x[3])*(y[2]+y[3])+(x[2]+x[4])*(y[2]+y[4])+……+(x[2]+x[k])*(y[2]+y[k])
    ……

    +(x[k-1]+x[k])*(y[k-1]+y[k])

    //观察到,第一行有 x[1]*(n-1)*y[1],第二行有 x[2]*(n-2)*[y2],第三行有 x[3]*(n-3)*y[3]……

    //但不要忘了前面的行。第一行有 1*x[2]*y[2],1*x[3]*y[3]……第二行有 1*x[3]*y[3]……

    //所以,对于第 j 个元,有 x[j]*(n-1)*(y[j])

    //观察到,第一行有 x[1]*(y[2]+y[3]+……+y[k]),第二行有 x[2]*(y[3]+……+y[k])……

    //但不要忘了前面的行。第一行有 x[2]*[y1],x[3]*y[1]……第二行有 x[3]*y[2]……

    //所以,对于第 j 个元,有 x[j]*(y[1]+y[2]+……+y[k]),但其中没有 y[j]

    //所以,考虑从 n-1 个 y[j] 中拿出来一个,使得变为:

    //对于第 j 个元,有 x[j]*(n-2)*(y[j])+x[j]*(y[1]+y[2]+……+y[k]),这次其中就包括 y[j] 了

    //所以,可以将 y[1]+y[2]+y[3]+……+y[k]提前算出,然后就大大降低复杂度

    附代码:

#include<bits/stdc++.h>
using namespace std;
const int wzr=10007;//某位同学,就当没看见(wzr我估计你也不会看见的)
int n,m,ans;
int lat_num[100007],per_num[100007][2],sum[100007][2],clo[100007];
//   元的数字   每种颜色,每种奇偶性的个数  每种颜色,每种奇偶性的累加和(即y[1]+y[2]+y[3]+……+y[k])  
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)scanf("%d",&lat_num[i]);
	for(int i=1;i<=n;++i){
	    scanf("%d",&clo[i]);
	    int x=i%2;
	    ++per_num[clo[i]][x];
	    sum[clo[i]][x]=(sum[clo[i]][x]+lat_num[i])%wzr;//防止爆int,换long long应该就不用%了
	    //这样便输入边算
    }
    for(int i=1;i<=n;++i){
        int x=i%2;
        ans=(ans+i*((per_num[clo[i]][x]-2)*lat_num[i]%wzr+sum[clo[i]][x]))%wzr;//记得两数相乘时先%一下,不然爆int,要换long long
    }
    printf("%d\n",ans);
	return 0}

附:关于 continue 语句和 if 语句运行速度的解释:在某网站笔者发布了讨论,一名热心提供了自己的测试结果为 continue 快,而笔者的测试结果为 if 快。所以,推测可能是由于评测机的波动造成的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值