想要OI学得好,数学肯定不能少 看来我是永远也学不好了
此篇博文较长 废话较多 ,您可以选择观看
写在最前面:其实不需要用 long long 类型,只需在两数相乘时先取模一次就行了(为什么可行,请参见取模的百科 又是该死的数学知识 )
在下面的代码中,既有用 long long 的,又有用 int 的,也是因为当时并不知道这一点。
-
依照题意,模拟。三重循环枚举 x,y,z,复杂度约为 O(n^3)
代码略(预估20~30左右)
-
.不难发现,其实三元组与中间的 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;
}
-
考虑优化(其实考场上写一个暴力就行了)。思考:既然三元组与中间的 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;
}
- 这是一个不太理解具体原因的优化:把 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;
}
-
那么,如何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 快。所以,推测可能是由于评测机的波动造成的。