阿巴巴巴,今天又考了一次模拟赛。

不得不说,自己学校出的题就是香,做的最爽的一次.

但是一点也不妨碍我挂掉了60分(局部变量手残不定义为0)。

无语,要不然就是195分了。哎~~~

第一题:

大水题,难度大概是是普及组吧!没错我还是挂掉了,而且还做了将近两个多小时。

思路:

对于每一个操作都设一个函数。比较难的就是九宫格的访问。于是遍历每一个点,然后预处理出它所在的九宫格,放进一个二维数组维护一下。

点击查看代码

#include<bits/stdc++.h>
using namespace std;
char Map[30][30];
int temp[110][30][30];
int Q;
void print(int cnt){
for(int i=1;i<=9;i++){
printf("+-+-+-+-+-+-+-+-+-+\n");
for(int j=1;j<=9;j++){
printf("|%d",temp[cnt][i][j]);
}
printf("|");
printf("\n");
}
printf("+-+-+-+-+-+-+-+-+-+\n");
return ;
}
int hang[101],lie[101],square[101][101];
int from[101][101],sit[101][101];
int check(int x,int y,int val,int cnt){
for(int i=1;i<=9;i++){
if(temp[cnt][x][i]==val){
return 2;
}
}
for(int i=1;i<=9;i++){
if(temp[cnt][i][y]==val){
return 3;
}
}
int a=from[x][y]/10;
int b=from[x][y]%10;
for(int i=(a-1)*3+1;i<=a*3;i++){
for(int j=(b-1)*3+1;j<=b*3;j++){
if(temp[cnt][i][j]==val){
return 4;
}
}
}
return 0;
}
void Insert(int x,int y,int val,int cnt){
if(temp[cnt][x][y]){
printf("Error!\n");
return ;
}
int rs=check(x,y,val,cnt);
if(rs==2){
printf("Error:row!\n");
return ;
}else if(rs==3){
printf("Error:column!\n");
return ;
}else if(rs==4){
printf("Error:square!\n");
}else if(rs==0){
printf("OK!\n");
temp[cnt][x][y]=val;
}
}
void Delete(int x,int y,int cnt)
{
if(temp[cnt][x][y]==0){
printf("Error!\n");
return ;
}else{
temp[cnt][x][y]=0;
printf("OK!\n");
}
return ;
}
void Query(int x,int y,int cnt){
if(temp[cnt][x][y]!=0){
printf("Error!\n");
return ;
}
int tot=0;//这里我挂了60分
int ans[101];
for(int i=1;i<=9;i++){
if(check(x,y,i,cnt)==0){
ans[++tot]=i;
}
}
printf("%d\n",tot);
for(int i=1;i<=tot;i++){
printf("%d\n",ans[i]);
}
return ;
}
void Merge(int a,int b,int cnt){
int tot1=0,tot2=0;
for(int i=1;i<=9;i++){
for(int j=1;j<=9;j++){
if(temp[a][i][j]!=0){
if(check(i,j,temp[a][i][j],cnt)==0){
temp[cnt][i][j]=temp[a][i][j];
tot1++;
}
}else if(temp[b][i][j]!=0){
if(check(i,j,temp[b][i][j],cnt)==0){
temp[cnt][i][j]=temp[b][i][j];
tot2++;
}
}
}
}
printf("%d %d\n",tot1,tot2);
return ;
}
int main()
{
freopen("sudoku.in","r",stdin);
freopen("sudoku.out","w",stdout);
for(int i=1;i<=19;i++){
cin>>(Map[i]+1);
}
for(int i=1;i<=19;i++){
for(int j=1;j<=19;j++){
if(i%2==0&&j%2==0){
temp[0][(i)/2][(j)/2]=Map[i][j]-'0';
}
}
}
//下面都不用看了,之前写的状态压缩懒得删,唯一有用的就是from
for(int i=1;i<=9;i++){
for(int j=1;j<=9;j++){
hang[i]=hang[i]*10+temp[0][i][j];
lie[j]=lie[j]*10+temp[0][i][j];
}
}
for(int i=1;i<=3;i++){
for(int j=1;j<=9;j++){
if(j<=3){
square[1][1]*=10;
square[1][1]+=temp[0][i][j];
from[i][j]=11;
}else if(j>3&&j<=6){
square[1][2]*=10;
square[1][2]+=temp[0][i][j];
from[i][j]=12;
}else{
if(j>6){
square[1][3]*=10;
square[1][3]+=temp[0][i][j];
from[i][j]=13;
}
}
}
}
for(int i=4;i<=6;i++){
for(int j=1;j<=9;j++){
if(j<=3){
square[2][1]*=10;
square[2][1]+=temp[0][i][j];
from[i][j]=21;
}else if(j>3&&j<=6){
square[2][2]*=10;
square[2][2]+=temp[0][i][j];
from[i][j]=22;
}else{
if(j>6){
square[2][3]*=10;
square[2][3]+=temp[0][i][j];
from[i][j]=23;
}
}
}
}
for(int i=7;i<=9;i++){
for(int j=1;j<=9;j++){
if(j<=3){
square[3][1]*=10;
square[3][1]+=temp[0][i][j];
from[i][j]=31;
}else if(j>3&&j<=6){
square[3][2]*=10;
square[3][2]+=temp[0][i][j];
from[i][j]=32;
}else{
if(j>6){
square[3][3]*=10;
square[3][3]+=temp[0][i][j];
from[i][j]=33;
}
}
}
}
scanf("%d",&Q);
for(int i=1;i<=Q;i++){
for(int k=1;k<=9;k++){
for(int j=1;j<=9;j++){
temp[i][k][j]=temp[i-1][k][j];
}
}
string op;
int a,b,c;
cin>>op;
if(op[0]=='I'){
scanf("%d %d %d",&a,&b,&c);
Insert(a,b,c,i);
}else if(op[0]=='D'){
scanf("%d %d",&a,&b);
Delete(a,b,i);
}else if(op[0]=='Q'){
scanf("%d %d",&a,&b);
Query(a,b,i);
}else if(op[0]=='M'){
scanf("%d %d",&a,&b);
for(int k=1;k<=9;k++){
for(int j=1;j<=9;j++){
temp[i][k][j]=0;
}
}
Merge(a,b,i);
}else if(op[0]=='P'){
print(i);
}
}
return 0;
}

第二题:

算法一:

暴力,使用一个线段树或者优先队列委会区间最大值。然后贪心模拟每一tick。

期望得分:20~30分

算法二:

考虑最大值最小最小值最大的通法二分答案。

在\(1~1e18\)的范围内二分最大值,然后用一个\(check\)函数判断当前最大值是否可以在\(S\)次之类出现。

时间复杂度\(O(m nlog1*10^{18})\)

期望得分:60分

点击查看代码

#include<bits/stdc++.h>
#define int long long
#define in read()
int n,m,x,s;
int a[100001];
inline int read()
{
static char ch;
int res=0,sign=1;
while((ch=getchar())<'0'||ch>'9'){
if(ch=='-'){
sign=-1;
}
}
res=ch-'0';
while((ch=getchar())>='0'&&ch<='9'){
res=res*10+ch-'0';
}
return res*sign;
}
int ans=0;
inline bool check(int t,int s){
int cnt=0;
for(register int i=1;i<=n;i++){
if(a[i]-s>t) cnt+=(a[i]-s-t-1)/x+1;
if(cnt>s) return 0;
}
return 1;
}
signed main()
{
n=in,m=in,x=in;
for(register int i=1;i<=n;i++){
a[i]=in;
}
std::sort(a+1,a+n+1);
for(register int i=1;i<=m;i++){
s=in;
int l=0,r=1e18,mid;
while(l<r){
mid=(l+r)>>1;
if(check(mid,s)){
ans=mid,r=mid;
}else{
l=mid+1;
}
}
printf("%lld\n",ans);
}



return 0;
}

算法三:

考虑继续优化算法二。使用主席树维护区间模数,在\(O(n)\)的时间找到比\(v\)大数。

我们再来变一下式子:

由于蒟蒻不会LaTeX所以只好上图:

20211102NOIP模拟赛_主席树

大家将就一下:)

由图得知:

我们的\(cnt\)就是\((t i-v)/x\)向上取整的和,所以变成上图的式子。

考虑先将小麦成熟天数排序,然后再维护整除\(x\)的后缀和,倒序插入主席树中。

然后再二分答案就好做了,时间复杂度为\(O(nlogn+mlog^2n)\)。

\(QWQ\)

点击查看代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MX=100005;
const ll oo=1231231231231231;
ll read(){
static char ch;
ll res=0,sign=1ll;
while((ch=getchar())<'0'||ch>'9'){
if(ch=='-')sign=-1;
}
res=ch-'0';
while((ch=getchar())>='0'&&ch<='9'){
res=res*10+ch-'0';
}
return res*sign;
}
struct node{
int ls,rs;
ll x;
};
struct SEGT//主席树封装
{
#define mid ((l+r)>>1)
int cnt;
node tre[MX*75];
void upd(int a){
tre[a].x=tre[tre[a].ls].x+tre[tre[a].rs].x;
}
void add(int &a,ll l,ll r,ll p,ll x){
tre[++cnt]=tre[a];//新建节点
a=cnt;
if(l==r) tre[a].x+=x;
else if(p<=mid) add(tre[a].ls,l,mid,p,x),upd(a);
else add(tre[a].rs,mid+1,r,p,x),upd(a);
}
ll qur(int a,ll l,ll r,ll ql,ll qr){//询问
if(!a) return 0;
else if(l>=ql&&r<=qr){
return tre[a].x;
}else if(qr<l||ql>r){
return 0;
}else {
return qur(tre[a].ls,l,mid,ql,qr)+qur(tre[a].rs,mid+1,r,ql,qr);
}
}
#undef mid
}T;
int n,m;
ll mns;
ll val[MX],id_block[MX],sum_id_block[MX];
int rot[MX];
void init()
{
for(int i=1;i<=n;i++){
id_block[i]=val[i]/mns;//用x的次数
}
for(int i=n;i>=1;i--){
sum_id_block[i]=sum_id_block[i+1]+id_block[i];//计算后缀
rot[i]=rot[i+1];//倒着转入主席树中 ,记录比val[i]大的数中的模数量
T.add(rot[i],0,mns,val[i]%mns,1);//一颗区间树 ,装区间里面的模数
}
}
void input()
{
n=read(),m=read(),mns=read();
for(int i=1;i<=n;i++){
val[i]=read();
}
sort(val+1,val+n+1);//顺便排序
}
ll check(ll tar){
ll ret=0;
int p=lower_bound(val+1,val+n+1,tar)-val;//nlogn找到大等于他的最小数
ret=sum_id_block[p]-(tar/mns)*(n-p+1);//ti/x+v/x 的和
ret+=T.qur(rot[p],0,mns,tar%mns+1,mns);// 比他模数大的数
return ret;
}
ll work(ll t){
ll l=0,r=val[n],ans=0,mid;
while(l<r)
{
mid=(l+r)>>1;
if(check(mid)>t) l=ans=mid+1;
else r=ans=mid;
}
return max(0ll,ans-t);
}
void work_big()
{
for(int i=1;i<=m;i++){
ll t;
t=read();
printf("%lld\n",work(t));
}

}
int main(){
input();//输入
init();//装入主席树中
work_big();

return 0;
}


第三题:

算法一:用\(LCA\)预处理出每一个点对的距离,然后\(n^2\)查询相加。复杂度\(O(n^2logn)\)

期望得分:40分

算法二:考虑将每一个点作为根节点,\(O(n)\)遍历整棵树,\(n^2\)求出点对距离。

期望得分: 40分

算法三:点分治(好吧逼装多了好像我会正解一样\(QWQ\))

第四题:期望毒瘤题(然鹅洛谷上是蓝题??)

算法一:机智如我之\(n^2\)枚举,35分到手。

算法二:手摸样例打表,发现\(n=2^k\)时当\(p=0,ans=(n-1)/2\) 当\(p=1,ans=n-1\)。

\(So\) 50分到手\(QWQ\).