T1
题目大意
有两个长为N的序列A和B,让你反转A的一段区间[l,r],贡献F(l,r)为A与B相同的位数。
你要求对于每一个可能的区间[l,r]的F(l,r)之和。
N<=5e5 1<=Ai,Bi<=N
算法分析
不枚举每个区间怎么求?枚举一个端点?
我们先来发现一个性质,当反转的区间和序列中的两个元素中心点一样,那么这两个元素将会交换。
我在比赛中是这样做的:
枚举i从1-N,统计Ax=By=i的贡献
考虑有 Ax=By
当x=y,贡献为 min(x,n-x+1) + x*(x-1)/2 + (n-x)*(n-x+1)/2
即与x不交的区间数加上以x为中心点的区间数。
当x<y,贡献为 min(x,n-y+1)
当x>y 贡献 min(y,n-x+1)
这样当出现大量相同数时,容易卡到 N方。
于是我们可以对于每个Ai,两次二分,先二分出Ai=Bj且 j>i 的所有元素,顺带可以求出Ai=Bj且j<=i的元素。
对于Ai=Bj且j>i的所有元素再次二分出 n-y+1<=x的元素,这一段直接用前缀和算贡献
对于Ai=Bj且j<i同理
对于Ax=Bx输入时加上x*(x-1)/2 + (n-x)*(n-x+1)/2即可。
绿题差不多吧
赛时代码
#include <iostream>
#include <vector>
using namespace std;
#define int long long
const int NR=5e5+5;
int n,a[NR],b[NR],pre[NR],pg[NR],ans=0;
vector<int> e[NR],e2[NR];
signed main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
e[a[i]].push_back(i);
}
for(int i=1;i<=n;i++){
cin>>b[i];
if(a[i]==b[i])
ans+=i*(i-1)/2+(n-i)*(n-i+1)/2;
e2[b[i]].push_back(i);
}
for(int i=1;i<=n;i++){
if(e[i].empty()||e2[i].empty())continue;
int len=e2[i].size();
pre[0]=e2[i][0];
for(int j=1;j<len;j++)pre[j]=pre[j-1]+e2[i][j];
pg[len]=0;
for(int j=len-1;j>=0;j--)pg[j]=pg[j+1]+n-e2[i][j]+1;
for(int j:e[i]){
int l=0,r=len-1;
while(l<r){
int mid=(l+r+1)>>1;
if(e2[i][mid]<=j) l=mid;
else r=mid-1;
}
if(e2[i][l]<=j){
int L=0,R=l;
while(L<R){
int mid=(L+R+1)>>1;
if(e2[i][mid]<=n-j+1) L=mid;
else R=mid-1;
}
if(e2[i][L]<=n-j+1){
ans+=pre[L]+(n-j+1)*(l-L);
}
else
ans+=(n-j+1)*(l+1);
} else l--;
if(l+1<len){
int ll=l+1,rr=len-1;
while(ll<rr){
int mid=(ll+rr)>>1;
if(n-e2[i][mid]+1<=j)rr=mid;
else ll=mid+1;
}
if(n-e2[i][ll]+1<=j){
ans+=pg[ll]+j*(len-1-l-(len-ll));
} else ans+=j*(len-1-l);
}
}
}
cout<<ans;
return 0;
}
T2
题目大意
有一个长为N的序列A,定义一次操作为选定一个1<=i<=N,让Ai+1或-1。
求最小的操作使得A序列 mod M同余。
算法分析
先把整个A序列全部 mod M,然后事情变成了环形的输油管道问题。
但环形问题和线性问题不同的一点在于:点点平等(每个点都可能是中心点)
于是枚举每一个点算一下与其他点的总距离和取最小值即可。
算总距离用双指针就行了,要注意维护|Ai-Aj|>M/2的数特殊算贡献。
赛时代码
#include <iostream>
#include <algorithm>
using namespace std;
#define int long long
const int NR=5e5+5,inf=1e18+7;
int T,n,m,a[NR],pre[NR];
int qry(int x,int y){
if(x>y)return 0;
return pre[y]-pre[x-1];
}
signed main(){
cin>>T;
while(T--){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++){a[i]%=m;}
sort(a+1,a+n+1);
for(int i=1;i<=n;i++)pre[i]=pre[i-1]+a[i];
int l=1,r=1,ans=inf;
for(int i=1;i<=n;i++){
while(a[i]-a[l]>m/2) l++;
while(a[r]-a[i]<=m/2&&r<=n) r++;
int sum=a[i]*(i-l)-qry(l,i-1)+(m-a[i])*(l-1)+qry(1,l-1);
sum+=qry(i+1,r-1)-a[i]*(r-i-1)+(m+a[i])*(n-r+1)-qry(r,n);
ans=min(ans,sum);
}
cout<<ans<<'\n';
}
return 0;
}
T3
题目大意
不想说了,到时候等锣鼓吧。
思路
纯思维题警告!!!
先枚举2所在的位置,然后发现最小的行和最小的列的交叉点就是2,于是有开开心心地填了2N-1个空位,其他的点的大小都只会受到最小的行和最小的列的限制,注意尽量往小了填。
我的结论全是猜的,请勿学习,在我心里这题直接想出来思维难度还是很高的。
赛时代码
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
const int NR=3005,inf=1e9+7;
int n,a[NR][NR],cnt[NR*3],b[NR][NR],c[NR][NR],daan[NR],mxb[NR],mnb[NR],mxc[NR],mnc[NR];
int ans[NR][NR],us[NR],ot[NR][NR];
int main(){
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
cin>>a[i][j];
cnt[a[i][j]]++;
mnb[i]=mnc[i]=ot[i][j]=inf;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
int h=a[i][j],g=cnt[a[i][j]]+1;
int g2=2*n+2-g;
c[i][j]=g2;b[i][j]=g;
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(b[i][j]==2){
memset(ans,0,sizeof(ans));
memset(mxb,0,sizeof(mxb));
memset(us,0,sizeof(us));
for(int k=1;k<=n;k++) ans[i][k]=b[i][k];
for(int k=1;k<=n;k++) ans[k][j]=b[k][j];
for(int l=1;l<=n;l++){
for(int h=1;h<=n;h++){
if(l!=i&&j!=h)
mxb[a[l][h]]=max(mxb[a[l][h]],max(ans[l][j],ans[i][h]));
}
}
for(int l=1;l<=n;l++){
for(int h=1;h<=n;h++){
if(l==i||h==j)continue;
if(mxb[a[l][h]]<b[l][h]&&(us[b[l][h]]==0||us[b[l][h]]==a[l][h])){
ans[l][h]=b[l][h]; us[b[l][h]]=a[l][h];
}
else {
us[c[l][h]]=a[l][h];
ans[l][h]=c[l][h];
}
}
}
for(int i=1;i<=n;i++){
int f=0;
for(int j=1;j<=n;j++){
if(ans[i][j]<ot[i][j]){
f=1;memcpy(ot,ans,sizeof(ot));break;
}else if(ans[i][j]>ot[i][j]){
f=1;break;
}
}
if(f)break;
}
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cout<<ot[i][j];
if(j!=n)cout<<' ';
}if(i!=n)cout<<'\n';}
return 0;
}
好吧,其实代码难度也不小。
总结
这场比赛是三道养生思维题的集合。也是我这种护肝的养生选手的福音。
大家想要AK这种比赛,要大胆地去猜结论哦!
难度预测:绿-橙-蓝