2018提高组模拟8
————————————————————————————————————————————20181002
T1
聚会
(WOJ4018)
描述
在成都的一条街道上,一共有 NN户人家,每个家庭有 XiXi 个人,他们和谐的生活在 一起,作为全国和谐街道,他们经常会小范围组织活动,每次活动会选择一户作为聚点, 并要求某些家庭参加,为了方便通知,村长每次邀请位置连续的家庭。因为每户人数不 同,每个家庭之间有一定距离,村长希望你计算出每次邀请的家庭的移动代价。第 ii个家 庭移动到家庭 jj的代价是:Xi∗dis(i,j)Xi∗dis(i,j)
dis(i,j)dis(i,j)表示ii 到jj的距离,村长一共安排了 mm 次聚会,每次邀请[Li,Ri][Li,Ri]的家庭参加
输入
第一行两个数表示 n,m
第二行 n-1 个数,第 i 个数表示第 i 个家庭与第 i+1 个家庭的距离 Di
第三行 n 个数,表示每个家庭的人数 Xi
之后 m 行每行三个数 x l r,表示查询要把区间 [l,r]的家庭移动到 x 点的代价和
输出
对于每个询问输出一个数表示答案,对 19260817 取模
样例输入
5 5
2 3 4 5
1 2 3 4 5
1 1 5
3 1 5
2 3 3
3 3 3
1 5 5
样例输出
125
72
9
0
70
提示
对于 30%的数据, n,m≤1000
对于另外 20%的数据,所有家庭间的距离都为 1
对于另外 20%的数据,所有家庭人数都为 1
对于 100%的数据 , n,m≤200000;Xi,Di <=2*10^9
题解
(数学推理,前缀和,较容易看懂)
##!!!一定乘的时候要计算long long!!!
减后可能为负数,要加一个mod再求余 (ans+mod)%mod; !!!!!
#include<bits/stdc++.h>
using namespace std;
int read(){
int x=0;char c=getchar();
while(!isdigit(c))c=getchar();
while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x;
}
#define ll long long
const int mod=19260817;
int n,m;
ll s[200010],s1[200010],s2[200010];
int main(){
n=read();m=read();
for(int i=1;i<n;i++)
s[i+1]=(s[i]+read())%mod;
for(int i=1;i<=n;i++){
s1[i]=(s1[i-1]+read())%mod;
s2[i]=(s2[i-1]+(s1[i]-s1[i-1]+mod)%mod*s[i]%mod)%mod;
}
int c,l,r;ll e,f;
while(m--){
c=read();l=read();r=read();
e=s[c]*(s1[r]-s1[l-1]+mod)%mod-(s2[r]-s2[l-1]+mod)%mod;
if(l<=c&&r<=c)printf("%lld\n",(e+mod)%mod);//区间在点左边
else if(l>=c&&r>=c)printf("%lld\n",(-e+mod)%mod);//区间在点右边
else{//区间在点两侧
e=s[c]*(s1[c]-s1[l-1]+mod)%mod-(s2[c]-s2[l-1]+mod)%mod;
f=(s2[r]-s2[c]+mod)%mod-s[c]*(s1[r]-s1[c]+mod)%mod;
printf("%lld\n",(e+f+2*mod)%mod);//
}
}
return 0;
}
T2
矩阵分组
描述
有N行M列的矩阵,每个格子中有一个数字,现在需要你将格子的数字分为A,B两部分
要求:
1、每个数字恰好属于两部分的其中一个部分
2、每个部分内部方块之间,可以上下左右相互到达,且每个内部方块之间可以相互到达,且最多拐一次弯
如:
AAAAA AAAAA AAAAA
AABAA BaAAA AAABB
ABBBA BBAAA AAABB
AABAA BaAAA ABBBB
AAAAA AAAAA BBBBB
(1) (2) (3)
其中(1)(2)是不允许的分法,(3)是允许的分法。在(2)中,a属于A区域,这两个a元素之间互相到达,但是不满足只拐一次弯到达。
问:对于所有合法的分组中,A区域和B区域的极差,其中极差较大的一个区域最小值是多少
提示:极差就是区域内最大值减去最小值。
输入
第一行两个正整数n,m
接下来n 行,每行m个自然数A_{i,j}表示权值
输出
输出一行表示答案
样例输入
4 4
1 12 6 11
11 4 2 14
10 1 9 20
4 17 13 10
样例输出
11
提示
【样例解释】 1 12 6 11 11 4 2 14 10 1 9 20 4 17 13 10
分法不唯一,如图是一种合法的分法。左边部分极差12-1=11,右边一块极差20-10=10,所以答案取这两个中较大者11。没有别的分法,可以使答案更小。
测试点 | N,m范围 |
---|---|
1,2 | n<=10,m<=10 |
3-4 | n=1,m<=2000 |
5-7 | n<=200,m<=200 |
8-10 | n<=2000,m<=2000 |
所有权值1<=a_ij<=10^9
题解
(二分+奇葩check, WOJ4019)
最大的最小,一定为二分。
本题关键在于check函数。
容易想到:阶梯状的分法是符合条件的。
A和B 可以互换,我们不妨设最大极值在A中,那么A 这样一个梯状的区域的顶角处于某个角落,
我们不妨设为左上角,然后将矩阵做 3 次旋转就可以考虑到所有情况。
二分极差值mid,
设最大值在A区域,那么我们从第一行开始找到第一个与最大值差值不在 check 范围内的位置p
在第二行不超过p-1的位置同样找一个这样的边界,
这样子我们就将矩阵划分为了两个区域:
我们枚举出来的左边区域一定满足条件,那么我们再判断右区域满不满足【与最小值的差值在条件范围内】
思想难点
为什么最大值一定在A里?
若最大值在B里:
1.最小值在A里,则gmax-最小值一定大于当前枚举的值,否则答案为gmax-gmin。
2.最小值不在A里,则B里有gmax-gmin,一定大于当前枚举的值,否则答案为gmax-gmin。
所以最大值一定在A里。
#include<bits/stdc++.h>
using namespace std;
int read(){
int x=0;char c=getchar();
while(!isdigit(c))c=getchar();
while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x;
}
#define ll long long
const int inf=2000000000;
int n,m,a[4][2005][2005];//四个方向的矩阵
int gmax=-inf,gmin=inf;//总最大值与最小值
int endi[2005];//记录每一行的最后一位
//-----------------------------------------------check函数
bool check__(int u,int d){//不同方向的矩阵编号
if(u&1)swap(n,m);//u为奇数-> 1,3也就是90度,n,m要换一下
endi[0]=m;
for(int i=1,j;i<=n;i++){//设最大值在A区域
for(j=1;j<=endi[i-1];j++)
if(gmax-a[u][i][j]>d)break;
endi[i]=j-1;
}
//A部分已处理好 判断B部分是否合法
for(int i=1;i<=n;i++){
for(int j=endi[i]+1;j<=m;j++)
if(a[u][i][j]-gmin>d){
if(u&1)swap(n,m);//换回
return 0;
}
}
if(u&1)swap(n,m);//换回
return 1;//满足条件,返回真
}
bool check(int d){
if(check__(0,d))return 1;
if(check__(1,d))return 1;
if(check__(2,d))return 1;
if(check__(3,d))return 1;
return 0;
}
int main(){
//----------------------------------------------初始化
n=read();m=read();
int x=1,x1=1,x2=n,x3=m;//矩阵绕中心顺时针旋转
int y=1,y1=n,y2=m,y3=1,t;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
t=a[0][x][y]=a[1][x1][y1]=a[2][x2][y2]=a[3][x3][y3]=read();
++y;++x1;--y2;--x3;
if(t>gmax)gmax=t;
if(t<gmin)gmin=t;
}
++x;y=1;
--y1;x1=1;//
--x2;y2=m;
++y3;x3=m;
}
//-----------------------------------------------二分
int l=0,r=gmax-gmin;
while(l<r){
int mid=(l+r)>>1;
if(check(mid))r=mid;
else l=mid+1;
}
printf("%d",l);
return 0;
}