7.11考试总结(NOIP模拟11)[math·biology·english]
吾于冥河沉浮,受尽命运捉弄,纵然汝将忘吾,吾亦伴汝身旁。
前言
考试的时候本来一看 T2 一见如故,决定 231 开题,然后瞅了一眼 T3 的题面,似曾相识。
仔细看了一眼,这,这不是差异吗,然后果断开码 time-=1.5h ,后果会在下面提到。
然后就 312 开题了,然后我就快乐地给了最简单的 T1 最少的 0.5h ,然后这次我就凉了。
改题历程:
T1 math
背景
首先这个题是三个题里最简单的。。
所以我花了最少的时间,搞到了最少的分,一开始还想怎么 \(\gcd\) 来着,后来就干脆各种骗分了。
解题思路
首先,我表示我没有学过裴蜀定理,然后这个题用到了一点相关知识,然后我就止步于 \(\gcd\) 了。
对任何整数a、b和它们的最大公约数d,关于未知数x和y的线性不定方程(称为裴蜀等式):
若a,b是整数,且gcd(a,b)=d,那么对于任意的整数x,y,ax+by都一定是d的倍数。
特别地,一定存在整数x,y,使ax+by=d成立。
还有一个重要推论 (没啥意义)
a,b互质的充分必要条件是存在整数x,y使ax+by=1.
来自百度百科。
因此,我们对于每一个数列里的数取 \(\gcd\) ,然后 \(\gcd \bmod\;k\) 在 k范围内的数就是我们要求的(需要枚举到重复部分)。
当然也可以求一下所有数列里的数与 k 的 \(\gcd\) 直接在 k 范围内取就好了。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+10,M=1e6+10;
int n,m,ans,gcd,s[N],t[N];
bool vis[M];
int GCD(int x,int y)
{
if(!y) return x;
return GCD(y,x%y);
}
inline void work2()
{
for(int i=0;i<m;i++)
if(i%gcd==0)
t[++ans]=i;
printf("%lld\n",ans);
for(int i=1;i<=ans;i++)
printf("%lld ",t[i]);
}
signed main()
{
scanf("%lld%lld",&n,&m);
gcd=m;
for(int i=1;i<=n;i++)
{
scanf("%lld",&s[i]);
gcd=GCD(gcd,s[i]);
}
work2();
return 0;
}
T2 biology
背景
这个题是给人感觉最友善的,因为是令人心爱的矩阵,因此我们感觉这个提非常能拿分。
然后我就在 T3 废掉之后主攻 T2 ,放掉了 T1 。
解题思路
考场上,我非常快地想出了暴力的 DFS 思路,大概能拿到 40pts,但是这并不能满足我。
于是,我想到了最长路,然后我就开始建边,并且巧妙的避开了错误,选择用 SPFA 求最长路。
然后我们愉快地建边 MLE20pts(\(code\)) 。
考完之后,我们老老实实去颓题解了,然后我看着正解码完了 40pts 的做法(\(code\))
对于正解的话,官方的题解涉及到了曼哈顿距离与切比雪夫距离的互化
我觉得,啥也不是,直接曼哈顿距离搞就可以了。
首先,对于 a 数组相等的划分成一个组,然后对于 a 从大到小进行排序,状态从上一个组里转移过来。
转移的时候分为四种情况,类似于四个象限,如果要更新的点(假设坐标为 \((x,y)\) )的在第一象限的话,转移的权值为 \(x+y\) 以此类推,第二三四象限分别为\(-x+y,-x-y,x-y\)。
根据上面的方法进行更新 DP 就好 (。♥‿♥。)
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e3+10,M=N*N;
int n,m,tot,ans,k1,k2,k3,k4,s[N][N],val[N][N],num[M],id[M],f[N][N];
bool vis[M];
vector<pair<int,int> > v[M];
signed main()
{
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%lld",&s[i][j]);
if(s[i][j]&&!vis[s[i][j]])
{
vis[s[i][j]]=true;
num[++tot]=s[i][j];
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%lld",&val[i][j]);
sort(num+1,num+tot+1);
for(int i=1;i<=tot;i++)
id[num[i]]=i;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(s[i][j]) v[id[s[i][j]]].push_back(make_pair(i,j));
for(int i=0;i<v[1].size();i++)
{
int x=v[1][i].first,y=v[1][i].second;
k1=max(k1,val[x][y]+x+y);
k2=max(k2,val[x][y]-x+y);
k3=max(k3,val[x][y]-x-y);
k4=max(k4,val[x][y]+x-y);
}
for(int i=2;i<=tot;i++)
{
for(int j=0;j<v[i].size();j++)
{
int x=v[i][j].first,y=v[i][j].second;
int maxn=max(k1-x-y,max(k2+x-y,max(k3+x+y,k4-x+y)));
f[x][y]=maxn+val[x][y];
}
for(int j=0;j<v[i].size();j++)
{
int x=v[i][j].first,y=v[i][j].second;
k1=max(k1,f[x][y]+x+y);
k2=max(k2,f[x][y]-x+y);
k3=max(k3,f[x][y]-x-y);
k4=max(k4,f[x][y]+x-y);
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
ans=max(ans,f[i][j]);
printf("%lld\n",ans);
return 0;
}
T3 english
背景
这个题是最令人感到最亲切的。
当我对于这题有一个初步的思路之后,我直接就开始单调队列优化求左右区间了。
当我花了将近 1h 成功把自己造的样例搞过之后,我发现题目的样例1还没有试。
然后,我就被卡掉了,但是灵光一现,我发现这个题应该是单调栈,10min之后我成功码出来了。
在然后我寻思这,这左右端点不知一个前缀和就能搞定的?随后我又看了一眼题面,万恶的\(xor\)。
我想了一下拆位,后来又想了想,以我的实力,好像。。。
于是果断的暴力,然后就是正解的前半部分+暴力=20pts。
别人的普通暴力都 40pts ,我。。。。
解题思路
正解的第一步就是运用单调栈求出来以序列的每一个数为最大值的区间的左右边界。
接下来的操作需要用到启发式合并的思想,暴力枚举较小的区间,理智处理较大区间的贡献。
为了方便讲述,接下来我们默认暴力操作左区间。
操作一
首先对于序列中数的每一位开一个前缀和,枚举左区间的各个数,然后计算它的每一位对于右区间的贡献。
显然 \(x\;\operatorname{xor}\;0=x\)
因此,假设我们当前扫到了 x ,我们先计算上 x 贡献,然后在对于其他的进行处理。
对于每一位,如果 x 的这一位是 0 的话,直接加上右侧区间内的数量的\(2^i\)。
否则,直接减去就好了。
操作二
运用可持久化 Tire 树进行优化,对于左区间里的任何一个树通过 Tire 树计算出右区间里与左端点 \(\operatorname{xor}\) 后大于最大值的数的个数。
再乘上最大值就好了,至于可持久化 Tire 树,几乎就是板子了。。
其它
另外,运用 01Tire 树可以轻松搞掉普通平衡树,并且比 Splay 快,问题就是无法实现区间翻转以及空间大一点而已。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+10,mxlg=25,mod=1e9+7;
int n,task,top,ans1,ans2,f[N][mxlg],l[N],r[N],s[N],sta[N];
int tot,root[N],ch[N*mxlg][2],siz[N*mxlg];
inline void Sta_Init()
{
sta[++top]=1;
l[1]=1;
for(int i=2;i<=n;i++)
{
while(top&&s[sta[top]]<=s[i])
{
r[sta[top]]=i-1;
top--;
}
l[i]=sta[top]+1;
sta[++top]=i;
}
while(top) r[sta[top--]]=n;
}
inline void insert(int pre,int &pos,int x,int lg)
{
pos=++tot;
siz[pos]=siz[pre];
if(lg<0)
{
siz[pos]++;
return ;
}
int num=(x>>lg)&1;
if(pre) ch[pos][num^1]=ch[pre][num^1];
insert(ch[pre][num],ch[pos][num],x,lg-1);
siz[pos]=siz[ch[pos][0]]+siz[ch[pos][1]];
}
inline int query(int x,int y,int pos)
{
int rt=root[pos],sum=0;
for(int i=mxlg-1;i>=0;i--)
{
int xnum=(x>>i)&1,ynum=(y>>i)&1;
if(!ynum)
{
sum+=siz[ch[rt][xnum^1]];
rt=ch[rt][xnum];
}
else rt=ch[rt][xnum^1];
}
return sum;
}
inline void work1l(int pos,int li,int ri)
{
for(int i=li;i<=pos;i++)
{
ans1=(ans1+s[i]*s[pos]*(ri-pos+1))%mod;
for(int j=mxlg-1;j>=0;j--)
{
int num=(s[i]>>j)&1;
if(!num) ans1=(ans1+(f[ri][j]-f[pos-1][j])*s[pos]%mod*(1<<j)%mod)%mod;
else ans1=(ans1-(f[ri][j]-f[pos-1][j])*s[pos]%mod*(1<<j)%mod+mod)%mod;
}
}
}
inline void work1r(int pos,int li,int ri)
{
for(int i=pos;i<=ri;i++)
{
ans1=(ans1+s[i]*s[pos]*(pos-li+1))%mod;
for(int j=mxlg-1;j>=0;j--)
{
int num=(s[i]>>j)&1;
if(!num) ans1=(ans1+(f[pos][j]-f[li-1][j])*s[pos]%mod*(1<<j)%mod)%mod;
else ans1=(ans1-(f[pos][j]-f[li-1][j])*s[pos]%mod*(1<<j)%mod+mod)%mod;
}
}
}
inline void work2l(int pos,int li,int ri)
{
for(int i=li;i<=pos;i++)
ans2=(ans2+(query(s[i],s[pos],ri)-query(s[i],s[pos],pos-1))*s[pos]%mod)%mod;
}
inline void work2r(int pos,int li,int ri)
{
for(int i=pos;i<=ri;i++)
ans2=(ans2+(query(s[i],s[pos],pos)-query(s[i],s[pos],li-1))*s[pos]%mod)%mod;
}
signed main()
{
scanf("%lld%lld",&n,&task);
for(int i=1;i<=n;i++)
scanf("%lld",&s[i]);
Sta_Init();
for(int i=1;i<=n;i++)
for(int j=0;j<mxlg;j++)
f[i][j]=f[i-1][j]+((s[i]>>j)&1);
for(int i=1;i<=n;i++)
insert(root[i-1],root[i],s[i],mxlg-1);
if(task==1||task==3)
for(int i=1;i<=n;i++)
if(i-l[i]<r[i]-i) work1l(i,l[i],r[i]);
else work1r(i,l[i],r[i]);
if(task==2||task==3)
for(int i=1;i<=n;i++)
if(i-l[i]<r[i]-i) work2l(i,l[i],r[i]);
else work2r(i,l[i],r[i]);
if(task==1) printf("%lld",ans1);
else if(task==2) printf("%lld",ans2);
else printf("%lld\n%lld",ans1,ans2);
return 0;
}